示例#1
0
        private static bool ReadHeader(Stream input, StreamTransferBuffer buffer, out Header header, out Header10?headerExt)
        {
            headerExt = null;

            //Validate that this is a DDS file and can at a minimum read (basic) header info from it
            if (!IsDDSFile(input))
            {
                header = new Header();
                return(false);
            }

            //Magic word read, advance by size of the magic word
            input.Position += FourCC.SizeInBytes;

            //Read primary header
            buffer.Read <Header>(input, out header);

            //Verify header
            if (header.Size != MemoryHelper.SizeOf <Header>() || header.PixelFormat.Size != MemoryHelper.SizeOf <PixelFormat>())
            {
                return(false);
            }

            //Possibly read extended header
            if (header.PixelFormat.IsDX10Extended)
            {
                //Check if we can read the header
                long minSize = MemoryHelper.SizeOf <Header10>();
                if (!StreamHelper.CanReadBytes(input, minSize))
                {
                    return(false);
                }

                Header10 header10;
                buffer.Read <Header10>(input, out header10);

                //Check array size
                if (header10.ArraySize == 0)
                {
                    return(false);
                }

                headerExt = header10;
            }

            //Ensure have at least one miplevel, seems like sometimes this will be zero even though there is one mip surface (the main image)
            if (header.MipMapCount == 0)
            {
                header.MipMapCount = 1;
            }

            return(true);
        }
示例#2
0
        private static bool WriteHeader(Stream output, StreamTransferBuffer buffer, TextureDimension texDim, DXGIFormat format, int width, int height, int depth, int arrayCount, int mipCount, DDSFlags flags)
        {
            //Force the DX10 header...
            bool writeDX10Header = (flags & DDSFlags.ForceExtendedHeader) == DDSFlags.ForceExtendedHeader;

            //Or do DX10 if the following is true...1D textures or 2D texture arrays that aren't cubemaps...
            if (!writeDX10Header)
            {
                switch (texDim)
                {
                case TextureDimension.One:
                    writeDX10Header = true;
                    break;

                case TextureDimension.Two:
                    writeDX10Header = arrayCount > 1;
                    break;
                }
            }

            //Figure out pixel format, if not writing DX10 header...
            PixelFormat pixelFormat;

            if (!writeDX10Header)
            {
                switch (format)
                {
                case DXGIFormat.R8G8B8A8_UNorm:
                    pixelFormat = PixelFormat.A8B8G8R8;
                    break;

                case DXGIFormat.R16G16_UNorm:
                    pixelFormat = PixelFormat.G16R16;
                    break;

                case DXGIFormat.R8G8_UNorm:
                    pixelFormat = PixelFormat.A8L8;
                    break;

                case DXGIFormat.R16_UNorm:
                    pixelFormat = PixelFormat.L16;
                    break;

                case DXGIFormat.R8_UNorm:
                    pixelFormat = PixelFormat.L8;
                    break;

                case DXGIFormat.A8_UNorm:
                    pixelFormat = PixelFormat.A8;
                    break;

                case DXGIFormat.R8G8_B8G8_UNorm:
                    pixelFormat = PixelFormat.R8G8_B8G8;
                    break;

                case DXGIFormat.G8R8_G8B8_UNorm:
                    pixelFormat = PixelFormat.G8R8_G8B8;
                    break;

                case DXGIFormat.BC1_UNorm:
                    pixelFormat = PixelFormat.DXT1;
                    break;

                case DXGIFormat.BC2_UNorm:
                    pixelFormat = PixelFormat.DXT3;
                    break;

                case DXGIFormat.BC3_UNorm:
                    pixelFormat = PixelFormat.DXT5;
                    break;

                case DXGIFormat.BC4_UNorm:
                    pixelFormat = PixelFormat.BC4_UNorm;
                    break;

                case DXGIFormat.BC4_SNorm:
                    pixelFormat = PixelFormat.BC4_SNorm;
                    break;

                case DXGIFormat.BC5_UNorm:
                    pixelFormat = PixelFormat.BC5_UNorm;
                    break;

                case DXGIFormat.BC5_SNorm:
                    pixelFormat = PixelFormat.BC5_SNorm;
                    break;

                case DXGIFormat.B5G6R5_UNorm:
                    pixelFormat = PixelFormat.R5G6B5;
                    break;

                case DXGIFormat.B5G5R5A1_UNorm:
                    pixelFormat = PixelFormat.A1R5G5B5;
                    break;

                case DXGIFormat.B8G8R8A8_UNorm:
                    pixelFormat = PixelFormat.A8R8G8B8;
                    break;

                case DXGIFormat.B8G8R8X8_UNorm:
                    pixelFormat = PixelFormat.X8R8G8B8;
                    break;

                case DXGIFormat.B4G4R4A4_UNorm:
                    pixelFormat = PixelFormat.A4R4G4B4;
                    break;

                case DXGIFormat.R32G32B32A32_Float:
                    pixelFormat = PixelFormat.R32G32B32A32_Float;
                    break;

                case DXGIFormat.R16G16B16A16_Float:
                    pixelFormat = PixelFormat.R16G16B16A16_Float;
                    break;

                case DXGIFormat.R16G16B16A16_UNorm:
                    pixelFormat = PixelFormat.R16G16B16A16_UNorm;
                    break;

                case DXGIFormat.R16G16B16A16_SNorm:
                    pixelFormat = PixelFormat.R16G16B16A16_SNorm;
                    break;

                case DXGIFormat.R32G32_Float:
                    pixelFormat = PixelFormat.R32G32_Float;
                    break;

                case DXGIFormat.R16G16_Float:
                    pixelFormat = PixelFormat.R16G16_Float;
                    break;

                case DXGIFormat.R32_Float:
                    pixelFormat = PixelFormat.R32_Float;
                    break;

                case DXGIFormat.R16_Float:
                    pixelFormat = PixelFormat.R16_Float;
                    break;

                default:
                    pixelFormat     = PixelFormat.DX10Extended;
                    writeDX10Header = true;
                    break;
                }
            }
            else
            {
                pixelFormat = PixelFormat.DX10Extended;
            }

            Header header = new Header();

            header.Size        = (uint)MemoryHelper.SizeOf <Header>();
            header.PixelFormat = pixelFormat;
            header.Flags       = HeaderFlags.Caps | HeaderFlags.Width | HeaderFlags.Height | HeaderFlags.PixelFormat;
            header.Caps        = HeaderCaps.Texture;

            Header10?header10 = null;

            if (mipCount > 0)
            {
                header.Flags      |= HeaderFlags.MipMapCount;
                header.MipMapCount = (uint)mipCount;
                header.Caps       |= HeaderCaps.MipMap;
            }

            switch (texDim)
            {
            case TextureDimension.One:
                header.Width  = (uint)width;
                header.Height = 1;
                header.Depth  = 1;

                //Should always be writing out extended header for 1D textures
                System.Diagnostics.Debug.Assert(writeDX10Header);

                header10 = new Header10(format, D3D10ResourceDimension.Texture1D, Header10Flags.None, (uint)arrayCount, Header10Flags2.None);

                break;

            case TextureDimension.Two:
                header.Width  = (uint)width;
                header.Height = (uint)height;
                header.Depth  = 1;

                if (writeDX10Header)
                {
                    header10 = new Header10(format, D3D10ResourceDimension.Texture2D, Header10Flags.None, (uint)arrayCount, Header10Flags2.None);
                }

                break;

            case TextureDimension.Cube:
                header.Width  = (uint)width;
                header.Height = (uint)height;
                header.Depth  = 1;
                header.Caps  |= HeaderCaps.Complex;
                header.Caps2 |= HeaderCaps2.Cubemap_AllFaces;

                //can support array tex cubes, so must be multiples of 6
                if (arrayCount % 6 != 0)
                {
                    return(false);
                }

                if (writeDX10Header)
                {
                    header10 = new Header10(format, D3D10ResourceDimension.Texture2D, Header10Flags.TextureCube, (uint)arrayCount / 6, Header10Flags2.None);
                }

                break;

            case TextureDimension.Three:
                header.Width  = (uint)width;
                header.Height = (uint)height;
                header.Depth  = (uint)depth;
                header.Flags |= HeaderFlags.Depth;
                header.Caps2 |= HeaderCaps2.Volume;

                if (arrayCount != 1)
                {
                    return(false);
                }

                if (writeDX10Header)
                {
                    header10 = new Header10(format, D3D10ResourceDimension.Texture3D, Header10Flags.None, 1, Header10Flags2.None);
                }

                break;
            }

            int realWidth, realHeight, rowPitch, slicePitch;

            ImageHelper.ComputePitch(format, width, height, out rowPitch, out slicePitch, out realWidth, out realHeight);

            if (FormatConverter.IsCompressed(format))
            {
                header.Flags            |= HeaderFlags.LinearSize;
                header.PitchOrLinearSize = (uint)slicePitch;
            }
            else
            {
                header.Flags            |= HeaderFlags.Pitch;
                header.PitchOrLinearSize = (uint)rowPitch;
            }

            //Write out magic word, DDS header, and optionally extended header
            buffer.Write <FourCC>(output, DDS_MAGIC);
            buffer.Write <Header>(output, header);

            if (header10.HasValue)
            {
                System.Diagnostics.Debug.Assert(header.PixelFormat.IsDX10Extended);
                buffer.Write <Header10>(output, header10.Value);
            }

            return(true);
        }
示例#3
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);
        }
示例#4
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));
        }