Example #1
0
        /// <summary>
        /// Writes DDS formatted data to a stream. Image data is expected to be DXGI-compliant data, but an effort is made to write out D3D9-compatible headers when possible.
        /// </summary>
        /// <param name="output">Output stream.</param>
        /// <param name="mipChains">Mipmap chains to write. Each mipmap chain represents a single face (so > 1 represents an array texture or a Cubemap). All faces must have
        /// equivalent dimensions and each chain must have the same number of mipmaps.</param>
        /// <param name="format">DXGI format the image data is stored as.</param>
        /// <param name="texDim">Dimension of the texture to write.</param>
        /// <param name="flags">Flags to control how the DDS data is saved.</param>
        /// <returns>True if writing the data was successful, false if otherwise.</returns>
        public static bool Write(Stream output, List <MipChain> mipChains, DXGIFormat format, TextureDimension texDim, DDSFlags flags = DDSFlags.None)
        {
            if (output == null || !output.CanWrite || mipChains == null || mipChains.Count == 0 || mipChains[0].Count == 0 || format == DXGIFormat.Unknown)
            {
                return(false);
            }

            //Extract details
            int     width, height, depth, arrayCount, mipCount;
            MipData firstMip = mipChains[0][0];

            width      = firstMip.Width;
            height     = firstMip.Height;
            depth      = firstMip.Depth;
            arrayCount = mipChains.Count;
            mipCount   = mipChains[0].Count;

            if (!ValidateInternal(mipChains, format, texDim))
            {
                return(false);
            }

            //Setup a transfer buffer
            StreamTransferBuffer buffer = new StreamTransferBuffer(firstMip.RowPitch, false);

            //Write out header
            if (!WriteHeader(output, buffer, texDim, format, width, height, depth, arrayCount, mipCount, flags))
            {
                return(false);
            }

            //Iterate over each array face...
            for (int i = 0; i < arrayCount; i++)
            {
                MipChain mipChain = mipChains[i];

                //Iterate over each mip face...
                for (int mipLevel = 0; mipLevel < mipCount; mipLevel++)
                {
                    MipData mip = mipChain[mipLevel];

                    //Compute pitch, based on MSDN programming guide. We will write out these pitches rather than the supplied in order to conform to the recomendation
                    //that we compute pitch based on format
                    int realMipWidth, realMipHeight, dstRowPitch, dstSlicePitch, bytesPerPixel;
                    ImageHelper.ComputePitch(format, mip.Width, mip.Height, out dstRowPitch, out dstSlicePitch, out realMipWidth, out realMipHeight, out bytesPerPixel);

                    //Ensure write buffer is sufficiently sized for a single scanline
                    if (buffer.Length < dstRowPitch)
                    {
                        buffer.Resize(dstRowPitch, false);
                    }

                    //Sanity check
                    if (dstRowPitch < mip.RowPitch)
                    {
                        return(false);
                    }

                    IntPtr srcPtr = mip.Data;

                    //Advance stream one slice at a time...
                    for (int slice = 0; slice < mip.Depth; slice++)
                    {
                        int    bytesToWrite = dstSlicePitch;
                        IntPtr sPtr         = srcPtr;

                        //Copy scanline into temp buffer, write to output
                        for (int row = 0; row < realMipHeight; row++)
                        {
                            MemoryHelper.CopyMemory(buffer.Pointer, sPtr, dstRowPitch);
                            buffer.WriteBytes(output, dstRowPitch);
                            bytesToWrite -= dstRowPitch;

                            //Advance to next scanline in source data
                            sPtr = MemoryHelper.AddIntPtr(sPtr, mip.RowPitch);
                        }

                        //Pad slice if necessary
                        if (bytesToWrite > 0)
                        {
                            MemoryHelper.ClearMemory(buffer.Pointer, 0, bytesToWrite);
                            buffer.WriteBytes(output, bytesToWrite);
                        }

                        //Advance source pointer to next slice
                        srcPtr = MemoryHelper.AddIntPtr(srcPtr, mip.SlicePitch);
                    }
                }
            }

            return(true);
        }
Example #2
0
        /// <summary>
        /// Reads DDS formatted data from a stream. Image data is always returned as DXGI-Compliant
        /// format, therefore some old legacy formats will automatically be converted.
        /// </summary>
        /// <param name="input">Input stream.</param>
        /// <param name="flags">Flags to control how the DDS data is loaded.</param>
        /// <returns>Loaded image data, or null if the data failed to load.</returns>
        public static DDSContainer Read(Stream input, DDSFlags flags = DDSFlags.None)
        {
            StreamTransferBuffer buffer = new StreamTransferBuffer();

            Header   header;
            Header10?headerExt;

            //Reads + validates header(s)
            if (!ReadHeader(input, buffer, out header, out headerExt))
            {
                return(null);
            }

            //Gather up metadata
            List <MipChain>  mipChains   = null;
            DXGIFormat       format      = DXGIFormat.Unknown;
            TextureDimension texDim      = TextureDimension.Two;
            ConversionFlags  convFlags   = ConversionFlags.None;
            bool             legacyDword = (flags & DDSFlags.LegacyDword) == DDSFlags.LegacyDword ? true : false;

            int width      = Math.Max((int)header.Width, 1);
            int height     = Math.Max((int)header.Height, 1);
            int depth      = Math.Max((int)header.Depth, 1);
            int mipCount   = (int)header.MipMapCount;
            int arrayCount = 1;

            //Has extended header, a modern DDS
            if (headerExt.HasValue)
            {
                Header10 extendedHeader = headerExt.Value;
                arrayCount = (int)extendedHeader.ArraySize;
                format     = extendedHeader.Format;

                switch (extendedHeader.ResourceDimension)
                {
                case D3D10ResourceDimension.Texture1D:
                {
                    texDim = TextureDimension.One;

                    if (height > 1 || depth > 1)
                    {
                        return(null);
                    }
                }
                break;

                case D3D10ResourceDimension.Texture2D:
                {
                    if ((extendedHeader.MiscFlags & Header10Flags.TextureCube) == Header10Flags.TextureCube)
                    {
                        //Specifies # of cubemaps, so to get total # of faces must multiple by 6
                        arrayCount *= 6;

                        texDim = TextureDimension.Cube;
                    }
                    else
                    {
                        texDim = TextureDimension.Two;
                    }

                    if (depth > 1)
                    {
                        return(null);
                    }
                }
                break;

                case D3D10ResourceDimension.Texture3D:
                {
                    texDim = TextureDimension.Three;

                    if (arrayCount > 1 || (header.Caps2 & HeaderCaps2.Volume) != HeaderCaps2.Volume)
                    {
                        return(null);
                    }
                }
                break;
                }
            }
            else
            {
                //Otherwise, read legacy DDS and possibly convert data

                //Check volume flag
                if ((header.Caps2 & HeaderCaps2.Volume) == HeaderCaps2.Volume)
                {
                    texDim = TextureDimension.Three;
                }
                else
                {
                    //legacy DDS could not express 1D textures, so either a cubemap or a 2D non-array texture

                    if ((header.Caps2 & HeaderCaps2.Cubemap) == HeaderCaps2.Cubemap)
                    {
                        //Must have all six faces. DirectX 8 and above always would write out all 6 faces
                        if ((header.Caps2 & HeaderCaps2.Cubemap_AllFaces) != HeaderCaps2.Cubemap_AllFaces)
                        {
                            return(null);
                        }

                        arrayCount = 6;
                        texDim     = TextureDimension.Cube;
                    }
                    else
                    {
                        texDim = TextureDimension.Two;
                    }
                }

                format = FormatConverter.DetermineDXGIFormat(header.PixelFormat, flags, out convFlags);
            }

            //Modify conversion flags, if necessary
            FormatConverter.ModifyConversionFormat(ref format, ref convFlags, flags);

            //If palette image, the palette will be the first thing
            int[] palette = null;
            if (FormatConverter.HasConversionFlag(convFlags, ConversionFlags.Pal8))
            {
                palette = new int[256];
                int palSize = palette.Length * sizeof(int);
                buffer.ReadBytes(input, palSize);

                if (buffer.LastReadByteCount != palSize)
                {
                    return(null);
                }

                MemoryHelper.CopyBytes <int>(buffer.ByteArray, 0, palette, 0, palette.Length);
            }

            //Now read data based on available mip/arrays
            mipChains = new List <MipChain>(arrayCount);

            byte[] scanline     = buffer.ByteArray;
            IntPtr scanlinePtr  = buffer.Pointer;
            bool   noPadding    = (flags & DDSFlags.NoPadding) == DDSFlags.NoPadding ? true : false;
            bool   isCompressed = FormatConverter.IsCompressed(format);
            bool   errored      = false;

            try
            {
                //Iterate over each array face...
                for (int i = 0; i < arrayCount; i++)
                {
                    MipChain mipChain = new MipChain(mipCount);
                    mipChains.Add(mipChain);

                    //Iterate over each mip face...
                    for (int mipLevel = 0; mipLevel < mipCount; mipLevel++)
                    {
                        //Calculate mip dimensions
                        int mipWidth  = width;
                        int mipHeight = height;
                        int mipDepth  = depth;
                        ImageHelper.CalculateMipmapLevelDimensions(mipLevel, ref mipWidth, ref mipHeight, ref mipDepth);

                        //Compute pitch, based on MSDN programming guide which says PitchOrLinearSize is unreliable and to calculate based on format.
                        //"real" mip width/height is the given mip width/height for all non-compressed, compressed images it will be smaller since each block
                        //is a 4x4 region of pixels.
                        int realMipWidth, realMipHeight, dstRowPitch, dstSlicePitch, bytesPerPixel;
                        ImageHelper.ComputePitch(format, mipWidth, mipHeight, out dstRowPitch, out dstSlicePitch, out realMipWidth, out realMipHeight, out bytesPerPixel, legacyDword);

                        int srcRowPitch   = dstRowPitch;
                        int srcSlicePitch = dstSlicePitch;

                        //Are we converting from a legacy format, possibly?
                        if (!headerExt.HasValue)
                        {
                            int legacySize = FormatConverter.LegacyFormatBitsPerPixelFromConversionFlag(convFlags);
                            if (legacySize != 0)
                            {
                                srcRowPitch   = (realMipWidth * legacySize + 7) / 8;
                                srcSlicePitch = srcRowPitch * realMipHeight;
                            }
                        }

                        //If output data is requested not to have padding, recompute destination pitches
                        if (noPadding)
                        {
                            dstRowPitch   = bytesPerPixel * realMipWidth;
                            dstSlicePitch = dstRowPitch * realMipHeight;
                        }

                        //Setup memory to hold the loaded image
                        MipData mipSurface = new MipData(mipWidth, mipHeight, mipDepth, dstRowPitch, dstSlicePitch);
                        mipChain.Add(mipSurface);

                        //Ensure read buffer is sufficiently sized for a single scanline
                        if (buffer.Length < srcRowPitch)
                        {
                            buffer.Resize(srcRowPitch, false);
                        }

                        IntPtr dstPtr = mipSurface.Data;

                        //Advance stream one slice at a time...
                        for (int slice = 0; slice < mipDepth; slice++)
                        {
                            long   slicePos = input.Position;
                            IntPtr dPtr     = dstPtr;

                            //Copy scanline into temp buffer, do any conversions, copy to output
                            for (int row = 0; row < realMipHeight; row++)
                            {
                                int numBytesRead = input.Read(scanline, 0, srcRowPitch);
                                if (numBytesRead != srcRowPitch)
                                {
                                    errored = true;
                                    System.Diagnostics.Debug.Assert(false);
                                    return(null);
                                }

                                //Copy scanline, optionally convert data
                                FormatConverter.CopyScanline(dPtr, dstRowPitch, scanlinePtr, srcRowPitch, format, convFlags, palette);

                                //Increment dest pointer to next row
                                dPtr = MemoryHelper.AddIntPtr(dPtr, dstRowPitch);
                            }

                            //Advance stream and destination pointer to the next slice
                            input.Position = slicePos + srcSlicePitch;
                            dstPtr         = MemoryHelper.AddIntPtr(dstPtr, dstSlicePitch);
                        }
                    }
                }
            }
            finally
            {
                //If errored, clean up any mip surfaces we allocated...no null entries should have been made either
                if (errored)
                {
                    DisposeMipChains(mipChains);
                }
            }

            if (!ValidateInternal(mipChains, format, texDim))
            {
                System.Diagnostics.Debug.Assert(false);
                return(null);
            }

            return(new DDSContainer(mipChains, format, texDim));
        }
Example #3
0
        /// <summary>
        /// Writes a DDS file to a stream. Image data is expected to be 32-bit color data, if not then mipmaps are converted as necessary automatically without modifying input data.
        /// </summary>
        /// <param name="output">Output stream.</param>
        /// <param name="mipChains">Mipmap chains to write. Each mipmap chain represents a single face (so > 1 represents an array texture or a Cubemap). All faces must have
        /// equivalent dimensions and each chain must have the same number of mipmaps.</param>
        /// <param name="texDim">Dimension of the texture to write.</param>
        /// <param name="flags">Flags to control how the DDS data is saved.</param>
        /// <returns>True if writing the data was successful, false if otherwise.</returns>
        public static bool Write(Stream output, List <List <Surface> > mipChains, TextureDimension texDim, DDSFlags flags = DDSFlags.None)
        {
            if (mipChains == null || mipChains.Count == 0 || mipChains[0] == null || mipChains[0].Count == 0)
            {
                return(false);
            }

            //FreeImage doesn't support volume textures.
            if (texDim == TextureDimension.Three)
            {
                return(false);
            }

            //If texcube, must have multiples of 6, every 6 mipchains are a complete cubemap
            if (texDim == TextureDimension.Cube && (mipChains.Count % 6 != 0))
            {
                return(false);
            }

            //FreeImage surfaces are always uncompressed and we expect 32-bit color, if not we'll convert. We'll export in whatever color order freeimage is in,
            //but we can force RGBA based on the flags
            List <MipChain> ddsMipChains = new List <MipChain>(mipChains.Count);

            bool       forceRGBA     = (flags & DDSFlags.ForceRgb) == DDSFlags.ForceRgb;
            bool       isBGRAOrder   = Surface.IsBGRAOrder;
            bool       needToSwizzle = isBGRAOrder && forceRGBA;
            DXGIFormat format        = (isBGRAOrder) ? DXGIFormat.B8G8R8A8_UNorm : DXGIFormat.R8G8B8A8_UNorm;

            if (forceRGBA)
            {
                format = DXGIFormat.R8G8B8A8_UNorm;
            }

            try
            {
                int mipCount = -1;

                foreach (List <Surface> fiMipChain in mipChains)
                {
                    MipChain ddsMipChain = new MipChain(fiMipChain.Count);
                    ddsMipChains.Add(ddsMipChain);

                    if (mipCount == -1)
                    {
                        mipCount = fiMipChain.Count;
                    }

                    //All chains must have same # of mips
                    if (mipCount != fiMipChain.Count)
                    {
                        return(false);
                    }

                    foreach (Surface fiMip in fiMipChain)
                    {
                        if (fiMip == null)
                        {
                            return(false);
                        }

                        //Validate dimension
                        switch (texDim)
                        {
                        case TextureDimension.One:
                            if (fiMip.Height > 1)
                            {
                                return(false);
                            }
                            break;

                        case TextureDimension.Cube:
                            if (fiMip.Width != fiMip.Height)
                            {
                                return(false);
                            }
                            break;
                        }

                        bool is32BitBitmap = fiMip.ImageType == ImageType.Bitmap && fiMip.ColorType == ImageColorType.RGBA && fiMip.BitsPerPixel == 32;

                        if (is32BitBitmap)
                        {
                            //If no swizzling...just use the data directly
                            if (!needToSwizzle)
                            {
                                ddsMipChain.Add(new MipData(fiMip));
                            }
                            else
                            {
                                MipData newMip = new MipData(fiMip.Width, fiMip.Height, fiMip.Pitch);
                                ImageHelper.CopyColorImageData(newMip.Data, newMip.RowPitch, 0, fiMip.DataPtr, fiMip.Pitch, 0, newMip.Width, newMip.Height, 1, true);
                                ddsMipChain.Add(newMip);
                            }
                        }
                        else
                        {
                            //Need to convert. Possible to map other DXGI formats to free image bitmaps (most likely RGBA floats), but we're keeping it simple. User can wrap surfaces
                            //and use the general write method
                            using (Surface converted = fiMip.Clone())
                            {
                                if (!converted.ConvertTo(ImageConversion.To32Bits))
                                {
                                    return(false);
                                }

                                MipData newMip = new MipData(converted.Width, converted.Height, converted.Pitch);
                                ImageHelper.CopyColorImageData(newMip.Data, newMip.RowPitch, 0, converted.DataPtr, converted.Pitch, 0, newMip.Width, newMip.Height, 1, needToSwizzle);
                                ddsMipChain.Add(newMip);
                            }
                        }
                    }
                }

                //Write out DDS
                return(Write(output, ddsMipChains, format, texDim, flags));
            }
            finally
            {
                //Dispose of mip surfaces. If they own any data, it'll be cleaned up
                DisposeMipChains(ddsMipChains);
            }
        }
Example #4
0
        internal static bool ValidateInternal(List <MipChain> mipChains, DXGIFormat format, TextureDimension texDim)
        {
            if (format == DXGIFormat.Unknown)
            {
                return(false);
            }

            //Mipchains must exist, must have at least one, and chain must have mipmaps.
            if (mipChains == null || mipChains.Count == 0 || mipChains[0].Count == 0)
            {
                return(false);
            }

            //Validate cubemap...must have multiples of 6 faces (can be an array of cubes).
            if (texDim == TextureDimension.Cube && (mipChains.Count % 6) != 0)
            {
                return(false);
            }

            //Validate 3d texture..can't have arrays
            if (texDim == TextureDimension.Three && mipChains.Count > 1)
            {
                return(false);
            }

            int width, height, depth, rowPitch, slicePitch;

            //Save the first image dimensions
            MipData firstSurface = mipChains[0][0];

            width      = firstSurface.Width;
            height     = firstSurface.Height;
            depth      = firstSurface.Depth;
            rowPitch   = firstSurface.RowPitch;
            slicePitch = firstSurface.SlicePitch;

            //Validate first surface
            if (width < 1 || height < 1 || depth < 1 || rowPitch < 1 || slicePitch < 1)
            {
                return(false);
            }

            //Validate 1D texture...must only have 1 height
            if (texDim == TextureDimension.One && height > 1)
            {
                return(false);
            }

            //Validate cubemap...width/height must be same
            if (texDim == TextureDimension.Cube && (width != height))
            {
                return(false);
            }

            //Only 3d textures have depth
            if (texDim != TextureDimension.Three && depth > 1)
            {
                return(false);
            }

            //Go through each chain and validate against the first texture and ensure mipmaps are progressively smaller
            int mipCount = -1;

            for (int i = 0; i < mipChains.Count; i++)
            {
                MipChain mipmaps = mipChains[i];

                //Mips must exist...
                if (mipmaps == null || mipmaps.Count == 0)
                {
                    return(false);
                }

                //Grab a mip count from first chain
                if (mipCount == -1)
                {
                    mipCount = mipmaps.Count;
                }

                //Each chain must have the same number of mip surfaces
                if (mipmaps.Count != mipCount)
                {
                    return(false);
                }

                //Each mip surface must have data and check sizes
                MipData prevMip = mipmaps[0];

                //Check against the first main image we looked at earlier
                if (prevMip.Width != width || prevMip.Height != height || prevMip.Depth != depth || prevMip.Data == IntPtr.Zero || prevMip.RowPitch != rowPitch || prevMip.SlicePitch != slicePitch)
                {
                    return(false);
                }

                for (int mipLevel = 1; mipLevel < mipmaps.Count; mipLevel++)
                {
                    MipData nextMip = mipmaps[mipLevel];

                    //Ensure each mipmap is progressively smaller or same at the least
                    if (nextMip.Width > prevMip.Width || nextMip.Height > prevMip.Height || nextMip.Depth > prevMip.Depth || nextMip.Data == IntPtr.Zero ||
                        nextMip.RowPitch > prevMip.RowPitch || nextMip.SlicePitch > prevMip.SlicePitch || nextMip.RowPitch == 0 || nextMip.SlicePitch == 0)
                    {
                        return(false);
                    }

                    prevMip = nextMip;
                }
            }

            return(true);
        }