Contains header information about a DDS File.
Inheritance: AbstractHeader
Exemplo n.º 1
0
        /// <summary>
        /// Determines DDS Surface Format given the header.
        /// </summary>
        /// <param name="ddspf">DDS PixelFormat structure.</param>
        /// <returns>Friendly format.</returns>
        public static ImageEngineFormat DetermineDDSSurfaceFormat(DDS_Header.DDS_PIXELFORMAT ddspf)
        {
            ImageEngineFormat format = ParseFourCC(ddspf.dwFourCC);

            if (format == ImageEngineFormat.Unknown)
            {
                // KFreon: Apparently all these flags mean it's a V8U8 image...
                if (ddspf.dwRGBBitCount == 16 &&
                           ddspf.dwRBitMask == 0x00FF &&
                           ddspf.dwGBitMask == 0xFF00 &&
                           ddspf.dwBBitMask == 0x00 &&
                           ddspf.dwABitMask == 0x00 &&
                           (ddspf.dwFlags & DDS_PFdwFlags.DDPF_SIGNED) == DDS_PFdwFlags.DDPF_SIGNED)
                    format = ImageEngineFormat.DDS_V8U8;

                // KFreon: Test for L8/G8
                else if (ddspf.dwABitMask == 0 &&
                        ddspf.dwBBitMask == 0 &&
                        ddspf.dwGBitMask == 0 &&
                        ddspf.dwRBitMask == 0xFF &&
                        ddspf.dwFlags == DDS_PFdwFlags.DDPF_LUMINANCE &&
                        ddspf.dwRGBBitCount == 8)
                    format = ImageEngineFormat.DDS_G8_L8;

                // KFreon: A8L8. This can probably be something else as well, but it seems to work for now
                else if (ddspf.dwRGBBitCount == 16 &&
                        ddspf.dwFlags == (DDS_PFdwFlags.DDPF_ALPHAPIXELS | DDS_PFdwFlags.DDPF_LUMINANCE))
                    format = ImageEngineFormat.DDS_A8L8;

                // KFreon: RGB. RGB channels have something in them, but alpha doesn't.
                else if (((ddspf.dwFlags & DDS_PFdwFlags.DDPF_RGB) == DDS_PFdwFlags.DDPF_RGB && !((ddspf.dwFlags & DDS_PFdwFlags.DDPF_ALPHAPIXELS) == DDS_PFdwFlags.DDPF_ALPHAPIXELS)) ||
                        ddspf.dwABitMask == 0 &&
                        ddspf.dwBBitMask != 0 &&
                        ddspf.dwGBitMask != 0 &&
                        ddspf.dwRBitMask != 0)
                    format = ImageEngineFormat.DDS_RGB;

                // KFreon: RGB and A channels are present.
                else if (((ddspf.dwFlags & (DDS_PFdwFlags.DDPF_RGB | DDS_PFdwFlags.DDPF_ALPHAPIXELS)) == (DDS_PFdwFlags.DDPF_RGB | DDS_PFdwFlags.DDPF_ALPHAPIXELS)) ||
                        ddspf.dwABitMask != 0 &&
                        ddspf.dwBBitMask != 0 &&
                        ddspf.dwGBitMask != 0 &&
                        ddspf.dwRBitMask != 0)
                    format = ImageEngineFormat.DDS_ARGB;

                // KFreon: If nothing else fits, but there's data in one of the bitmasks, assume it can be read.
                else if (ddspf.dwABitMask != 0 || ddspf.dwRBitMask != 0 || ddspf.dwGBitMask != 0 || ddspf.dwBBitMask != 0)
                    format = ImageEngineFormat.DDS_CUSTOM;
                else
                    throw new FormatException("DDS Format is unknown.");
            }

            return format;
        }
Exemplo n.º 2
0
        internal static void ReadUncompressed(byte[] source, int sourceStart, byte[] destination, int pixelCount, DDS_Header.DDS_PIXELFORMAT ddspf)
        {
            int signedAdjustment = ((ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_SIGNED) == DDS_Header.DDS_PFdwFlags.DDPF_SIGNED) ? SignedAdjustment : 0;
            int sourceIncrement = ddspf.dwRGBBitCount / 8;  // /8 for bits to bytes conversion
            bool oneChannel = (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE) == DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE;
            bool twoChannel = (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS) == DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS && oneChannel;

            uint AMask = ddspf.dwABitMask;
            uint RMask = ddspf.dwRBitMask;
            uint GMask = ddspf.dwGBitMask;
            uint BMask = ddspf.dwBBitMask;

            ///// Figure out channel existance and ordering.
            // Setup array that indicates channel offset from pixel start.
            // e.g. Alpha is usually first, and is given offset 0.
            // NOTE: Ordering array is in ARGB order, and the stored indices change depending on detected channel order.
            // A negative index indicates channel doesn't exist in data and sets channel to 0xFF.
            List<uint> maskOrder = new List<uint>(4) { AMask, RMask, GMask, BMask };
            maskOrder.Sort();
            maskOrder.RemoveAll(t => t == 0);  // Required, otherwise indicies get all messed up when there's only two channels, but it's not indicated as such.

            int AIndex = 0;
            int RIndex = 0;
            int GIndex = 0;
            int BIndex = 0;

            if (twoChannel)  // Note: V8U8 does not come under this one.
            {
                // Intensity is first byte, then the alpha. Set all RGB to intensity for grayscale.
                // Second mask is always RMask as determined by the DDS Spec.
                AIndex = AMask > RMask ? 1 : 0;
                RIndex = AMask > RMask ? 0 : 1;
                GIndex = AMask > RMask ? 0 : 1;
                BIndex = AMask > RMask ? 0 : 1;
            }
            else if (oneChannel)
            {
                // Decide whether it's alpha or not.
                AIndex = AMask == 0 ? -1 : 0;
                RIndex = AMask == 0 ? 0 : -1;
                GIndex = AMask == 0 ? 0 : -1;
                BIndex = AMask == 0 ? 0 : -1;
            }
            else
            {
                // Set default ordering
                AIndex = AMask == 0 ? -1 : maskOrder.IndexOf(AMask);
                RIndex = RMask == 0 ? -1 : maskOrder.IndexOf(RMask);
                GIndex = GMask == 0 ? -1 : maskOrder.IndexOf(GMask);
                BIndex = BMask == 0 ? -1 : maskOrder.IndexOf(BMask);
            }

            for (int i = 0, j = sourceStart; i < pixelCount * 4; i += 4, j += sourceIncrement)
            {
                destination[i] = BIndex == -1 ? (byte)0xFF : (byte)(source[j + BIndex] - signedAdjustment);
                destination[i + 1] = GIndex == -1 ? (byte)0xFF : (byte)(source[j + GIndex] - signedAdjustment);
                destination[i + 2] = RIndex == -1 ? (byte)0xFF : (byte)(source[j + RIndex] - signedAdjustment);
                destination[i + 3] = AIndex == -1 ? (byte)0xFF : (source[j + AIndex]);
            }
        }
Exemplo n.º 3
0
 static int WriteUncompressedMipMap(byte[] destination, int mipOffset, MipMap mipmap, ImageEngineFormat saveFormat, DDS_Header.DDS_PIXELFORMAT ddspf)
 {
     return DDS_Encoders.WriteUncompressed(mipmap.Pixels, destination, mipOffset, ddspf);
 }
Exemplo n.º 4
0
        private static MipMap ReadUncompressedMipMap(MemoryStream stream, int mipOffset, int mipWidth, int mipHeight, DDS_Header.DDS_PIXELFORMAT ddspf)
        {
            byte[] data = stream.GetBuffer();
            byte[] mipmap = new byte[mipHeight * mipWidth * 4];
            DDS_Decoders.ReadUncompressed(data, mipOffset, mipmap, mipWidth * mipHeight, ddspf);

            return new MipMap(mipmap, mipWidth, mipHeight);
        }
Exemplo n.º 5
0
        internal static byte[] Save(List<MipMap> mipMaps, ImageEngineFormat saveFormat, AlphaSettings alphaSetting, List<uint> customMasks = null)
        {
            // Set compressor for Block Compressed textures
            Action<byte[], int, int, byte[], int, AlphaSettings> compressor = null;

            bool needCheckSize = saveFormat.ToString().Contains("DXT") || saveFormat.ToString().Contains("ATI");

            switch (saveFormat)
            {
                case ImageEngineFormat.DDS_ATI1:
                    compressor = DDS_Encoders.CompressBC4Block;
                    break;
                case ImageEngineFormat.DDS_ATI2_3Dc:
                    compressor = DDS_Encoders.CompressBC5Block;
                    break;
                case ImageEngineFormat.DDS_DX10:
                    Debugger.Break();
                    break; // TODO: NOT SUPPORTED YET. DX10
                case ImageEngineFormat.DDS_DXT1:
                    compressor = DDS_Encoders.CompressBC1Block;
                    break;
                case ImageEngineFormat.DDS_DXT2:
                case ImageEngineFormat.DDS_DXT3:
                    compressor = DDS_Encoders.CompressBC2Block;
                    break;
                case ImageEngineFormat.DDS_DXT4:
                case ImageEngineFormat.DDS_DXT5:
                    compressor = DDS_Encoders.CompressBC3Block;
                    break;
            }

            int height = mipMaps[0].Height;
            int width = mipMaps[0].Width;

            if (needCheckSize && !CheckSize_DXT(width, height))
                throw new InvalidOperationException($"DXT compression formats require dimensions to be multiples of 4. Got: {width}x{height}.");

            int fullSize = GetCompressedSizeOfImage(mipMaps.Count, saveFormat, width, height);
            // +1 to get the full size, not just the offset of the last mip.
            //int fullSize = GetMipOffset(mipMaps.Count + 1, saveFormat, mipMaps[0].Width, mipMaps[0].Height);

            byte[] destination = new byte[fullSize];

            // Create header and write to destination
            DDS_Header header = new DDS_Header(mipMaps.Count, height, width, saveFormat, customMasks);
            header.WriteToArray(destination, 0);

            int blockSize = ImageFormats.GetBlockSize(saveFormat);

            if (ImageFormats.IsBlockCompressed(saveFormat))
            {
                int mipOffset = 128;
                foreach (MipMap mipmap in mipMaps)
                    mipOffset = WriteCompressedMipMap(destination, mipOffset, mipmap, blockSize, compressor, alphaSetting);
            }
            else
            {
                // UNCOMPRESSED
                var action = new Action<int>(mipIndex =>
                {
                    if (alphaSetting == AlphaSettings.RemoveAlphaChannel)
                    {
                        // Remove alpha by setting AMask = 0
                        var ddspf = header.ddspf;
                        ddspf.dwABitMask = 0;
                        header.ddspf = ddspf;
                    }

                    // Get MipOffset
                    int offset = GetMipOffset(mipIndex, saveFormat, width, height);

                    WriteUncompressedMipMap(destination, offset, mipMaps[mipIndex], saveFormat, header.ddspf);
                });

                if (ImageEngine.EnableThreading)
                    Parallel.For(0, mipMaps.Count, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, action);
                else
                    for (int i = 0; i < mipMaps.Count; i++)
                        action(i);
            }

            return destination;
        }
Exemplo n.º 6
0
        internal static List<MipMap> LoadDDS(MemoryStream compressed, DDS_Header header, int desiredMaxDimension)
        {
            MipMap[] MipMaps = null;

            int mipWidth = header.Width;
            int mipHeight = header.Height;
            ImageEngineFormat format = header.Format;
            int blockSize = ImageFormats.GetBlockSize(format);

            int estimatedMips = header.dwMipMapCount;
            int mipOffset = 128;  // Includes header.
            // TODO: Incorrect mip offset for DX10

            if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, 4, format, out mipOffset))  // Update number of mips too
                estimatedMips = 1;

            if (estimatedMips == 0)
                estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight);

            mipOffset = 128;  // Needs resetting after checking there's mips in this image.

            // TESTUNIG
            if (estimatedMips == 0)
                estimatedMips = 1;

            int orig_estimatedMips = estimatedMips; // Store original count for later (uncompressed only I think)

            // KFreon: Decide which mip to start loading at - going to just load a few mipmaps if asked instead of loading all, then choosing later. That's slow.
            if (desiredMaxDimension != 0 && estimatedMips > 1)
            {
                if (!EnsureMipInImage(compressed.Length, mipWidth, mipHeight, desiredMaxDimension, format, out mipOffset))  // Update number of mips too
                    throw new InvalidDataException($"Requested mipmap does not exist in this image. Top Image Size: {mipWidth}x{mipHeight}, requested mip max dimension: {desiredMaxDimension}.");

                // Not the first mipmap.
                if (mipOffset > 128)
                {

                    double divisor = mipHeight > mipWidth ? mipHeight / desiredMaxDimension : mipWidth / desiredMaxDimension;
                    mipHeight = (int)(mipHeight / divisor);
                    mipWidth = (int)(mipWidth / divisor);

                    if (mipWidth == 0 || mipHeight == 0)  // Reset as a dimension is too small to resize
                    {
                        mipHeight = header.Height;
                        mipWidth = header.Width;
                        mipOffset = 128;
                    }
                    else
                    {
                        // Update estimated mips due to changing dimensions.
                        estimatedMips = EstimateNumMipMaps(mipWidth, mipHeight);
                    }
                }
                else  // The first mipmap
                    mipOffset = 128;

            }

            // Move to requested mipmap
            compressed.Position = mipOffset;

            // Block Compressed texture chooser.
            Action<byte[], int, byte[], int, int, bool> DecompressBCBlock = null;
            switch (format)
            {
                case ImageEngineFormat.DDS_DXT1:
                    DecompressBCBlock = DDS_Decoders.DecompressBC1Block;
                    break;
                case ImageEngineFormat.DDS_DXT2:
                case ImageEngineFormat.DDS_DXT3:
                    DecompressBCBlock = DDS_Decoders.DecompressBC2Block;
                    break;
                case ImageEngineFormat.DDS_DXT4:
                case ImageEngineFormat.DDS_DXT5:
                    DecompressBCBlock = DDS_Decoders.DecompressBC3Block;
                    break;
                case ImageEngineFormat.DDS_ATI1:
                    DecompressBCBlock = DDS_Decoders.DecompressATI1;
                    break;
                case ImageEngineFormat.DDS_ATI2_3Dc:
                    DecompressBCBlock = DDS_Decoders.DecompressATI2Block;
                    break;
            }

            MipMaps = new MipMap[estimatedMips];

            // KFreon: Read mipmaps
            if (ImageFormats.IsBlockCompressed(format))    // Threading done in the decompression, not here.
            {
                for (int m = 0; m < estimatedMips; m++)
                {
                    // KFreon: If mip is too small, skip out. This happens most often with non-square textures. I think it's because the last mipmap is square instead of the same aspect.
                    if (mipWidth <= 0 || mipHeight <= 0)  // Needed cos it doesn't throw when reading past the end for some reason.
                    {
                        break;
                    }
                    MipMap mipmap = ReadCompressedMipMap(compressed, mipWidth, mipHeight, blockSize, mipOffset, (format == ImageEngineFormat.DDS_DXT2 || format == ImageEngineFormat.DDS_DXT4), DecompressBCBlock);

                    MipMaps[m] = mipmap;
                    mipOffset += (int)(mipWidth * mipHeight * (blockSize / 16d)); // Also put the division in brackets cos if the mip dimensions are high enough, the multiplications can exceed int.MaxValue)
                    mipWidth /= 2;
                    mipHeight /= 2;
                }
            }
            else
            {
                int startMip = orig_estimatedMips - estimatedMips;

                // UNCOMPRESSED - Can't really do threading in "decompression" so do it for the mipmaps.
                var action = new Action<int>(mipIndex =>
                {
                    // Calculate mipOffset and dimensions
                    int offset, width, height;
                    offset = GetMipOffset(mipIndex, format, header.Width, header.Height);

                    double divisor = mipIndex == 0 ? 1d : 2 << (mipIndex - 1);   // Divisor represents 2^mipIndex - Math.Pow seems very slow.
                    width = (int)(header.Width / divisor);
                    height = (int)(header.Height / divisor);

                    MipMap mipmap = null;
                    try
                    {
                        mipmap = ReadUncompressedMipMap(compressed, offset, width, height, header.ddspf);
                    }
                    catch (Exception e)
                    {
                        Debug.WriteLine(e.ToString());
                    }

                    MipMaps[mipIndex] = mipmap;
                });

                if (ImageEngine.EnableThreading)
                    Parallel.For(startMip, orig_estimatedMips, new ParallelOptions { MaxDegreeOfParallelism = ImageEngine.NumThreads }, action);
                else
                    for (int i = startMip; i < orig_estimatedMips; i++)
                        action(i);
            }

            List<MipMap> mips = new List<MipMap>(MipMaps.Where(t => t != null));
            if (mips.Count == 0)
                throw new InvalidOperationException($"No mipmaps loaded. Estimated mips: {estimatedMips}, mip dimensions: {mipWidth}x{mipHeight}");
            return mips;
        }
Exemplo n.º 7
0
        internal static int WriteUncompressed(byte[] source, byte[] destination, int destStart, DDS_Header.DDS_PIXELFORMAT ddspf)
        {
            int byteCount = ddspf.dwRGBBitCount / 8;
            byte signedAdjust = (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_SIGNED) == DDS_Header.DDS_PFdwFlags.DDPF_SIGNED ? SignedAdjustment : (byte)0;
            bool oneChannel = (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE) == DDS_Header.DDS_PFdwFlags.DDPF_LUMINANCE;
            bool twoChannel = oneChannel && (ddspf.dwFlags & DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS) == DDS_Header.DDS_PFdwFlags.DDPF_ALPHAPIXELS;

            uint AMask = ddspf.dwABitMask;
            uint RMask = ddspf.dwRBitMask;
            uint GMask = ddspf.dwGBitMask;
            uint BMask = ddspf.dwBBitMask;

            ///// Figure out channel existance and ordering.
            // Setup array that indicates channel offset from pixel start.
            // e.g. Alpha is usually first, and is given offset 0.
            // NOTE: Ordering array is in ARGB order, and the stored indices change depending on detected channel order.
            // A negative index indicates channel doesn't exist in data and sets channel to 0xFF.
            List<uint> maskOrder = new List<uint>(4) { AMask, RMask, GMask, BMask };
            maskOrder.Sort();
            maskOrder.RemoveAll(t => t == 0);  // Required, otherwise indicies get all messed up when there's only two channels, but it's not indicated as such.

            int AIndex = 0;
            int RIndex = 0;
            int GIndex = 0;
            int BIndex = 0;

            // Set default ordering
            AIndex = AMask == 0 ? -1 : maskOrder.IndexOf(AMask);
            RIndex = RMask == 0 ? -1 : maskOrder.IndexOf(RMask);
            GIndex = GMask == 0 ? -1 : maskOrder.IndexOf(GMask);
            BIndex = BMask == 0 ? -1 : maskOrder.IndexOf(BMask);

            for (int i = 0; i < source.Length; i+=4, destStart += byteCount)
            {
                byte blue = (byte)(source[i] + signedAdjust);
                byte green = (byte)(source[i + 1] + signedAdjust);
                byte red = (byte)(source[i + 2] + signedAdjust);
                byte alpha = (byte)(source[i + 3]);

                if (twoChannel)
                {
                    destination[destStart] = AMask > RMask ? red : alpha;
                    destination[destStart + 1] = AMask > RMask ? alpha : red;
                }
                else if (oneChannel)
                    destination[destStart] = (byte)(blue * 0.082 + green * 0.6094 + blue * 0.3086); // Weightings taken from ATI Compressonator. Dunno if this changes things much.
                else
                {
                    if (AMask != 0)
                        destination[destStart + AIndex] = alpha;

                    if (RMask != 0)
                        destination[destStart + RIndex] = red;

                    if (GMask != 0)
                        destination[destStart + GIndex] = green;

                    if (BMask != 0)
                        destination[destStart + BIndex] = blue;
                }
            }

            return destStart;
        }