Example #1
0
 /// <summary>
 /// Builds a DDS image from an existing mipmap.
 /// </summary>
 /// <param name="mip">Mip to base image on.</param>
 /// <param name="DDSFormat">Format of mipmap.</param>
 public ImageEngineImage(MipMap mip, ImageEngineFormat DDSFormat)
 {
     Format  = new Format(DDSFormat);
     MipMaps = new List <MipMap>()
     {
         mip
     };
     header = DDSGeneral.Build_DDS_Header(1, mip.Height, mip.Width, DDSFormat);
 }
Example #2
0
        protected override void _Read(BinaryReader reader)
        {
            long baseOffset = reader.BaseStream.Position;

            byte[] buffer = reader.ReadBytes((int)reader.BaseStream.Length);

            MemoryStream memoryStream = new MemoryStream(buffer, 0, buffer.Length, true, true);
            DDS_Header   header       = new DDS_Header(memoryStream);

            FormatDetails = new DDSFormatDetails(header.Format, header.DX10_DXGI_AdditionalHeader.dxgiFormat);
            MipMaps       = DDSGeneral.LoadDDS(memoryStream, header, 0, FormatDetails);
            Width         = header.Width;
            Height        = header.Height;
            memoryStream.Close();
        }
Example #3
0
        internal static List <MipMap> LoadImage(byte[] rawDDSData, ImageEngineFormat surfaceFormat, int width, int height, out DDSGeneral.DDS_HEADER header)
        {
            header = DDSGeneral.Build_DDS_Header(1, height, width, surfaceFormat);
            List <MipMap> MipMaps = null;

            // Create new fully formatted DDS i.e. one with a header.
            MemoryStream stream = new MemoryStream();
            BinaryWriter bw     = new BinaryWriter(stream);

            DDSGeneral.Write_DDS_Header(header, bw);
            bw.Write(rawDDSData);

            switch (surfaceFormat)
            {
            case ImageEngineFormat.DDS_DXT1:
            case ImageEngineFormat.DDS_DXT2:
            case ImageEngineFormat.DDS_DXT3:
            case ImageEngineFormat.DDS_DXT4:
            case ImageEngineFormat.DDS_DXT5:
                if (WindowsWICCodecsAvailable)
                {
                    MipMaps = WIC_Codecs.LoadWithCodecs(stream, 0, 0, true);
                }
                else
                {
                    MipMaps = DDSGeneral.LoadDDS(stream, header, new Format(surfaceFormat), 0);
                }
                break;

            case ImageEngineFormat.DDS_ARGB:
            case ImageEngineFormat.DDS_A8L8:
            case ImageEngineFormat.DDS_RGB:
            case ImageEngineFormat.DDS_ATI1:
            case ImageEngineFormat.DDS_ATI2_3Dc:
            case ImageEngineFormat.DDS_G8_L8:
            case ImageEngineFormat.DDS_V8U8:
                MipMaps = DDSGeneral.LoadDDS(stream, header, new Format(surfaceFormat), 0);
                break;

            default:
                throw new InvalidDataException("Image format is unknown.");
            }
            bw.Dispose(); // Also disposes MemoryStream
            return(MipMaps);
        }
        internal static void TestDDSMipSize(List <MipMap> newMips, ImageFormats.ImageEngineFormatDetails destFormatDetails, int width, int height, out double fixXScale, out double fixYScale, MipHandling mipChoice)
        {
            fixXScale = 0;
            fixYScale = 0;
            if (destFormatDetails.IsBlockCompressed && (!UsefulThings.General.IsPowerOfTwo(width) || !UsefulThings.General.IsPowerOfTwo(height)))
            {
                // If only keeping top mip, and that mip is divisible by 4, it's ok.
                if ((mipChoice == MipHandling.KeepTopOnly || mipChoice == MipHandling.KeepExisting) &&
                    DDSGeneral.CheckSize_DXT(width, height))
                {
                    return;
                }


                double newWidth  = 0;
                double newHeight = 0;

                // Takes into account aspect ratio (a little bit)
                double aspect = width / height;
                if (aspect > 1)
                {
                    newWidth = UsefulThings.General.RoundToNearestPowerOfTwo(width);
                    var tempScale = newWidth / width;
                    newHeight = UsefulThings.General.RoundToNearestPowerOfTwo((int)(height * tempScale));
                }
                else
                {
                    newHeight = UsefulThings.General.RoundToNearestPowerOfTwo(height);
                    var tempScale = newHeight / height;
                    newWidth = UsefulThings.General.RoundToNearestPowerOfTwo((int)(width * tempScale));
                }


                // Little extra bit to allow integer cast from Double with the correct answer. Occasionally dimensions * scale would be 511.99999999998 instead of 512, so adding a little allows the integer cast to return correct value.
                fixXScale  = 1d * newWidth / width + 0.001;
                fixYScale  = 1d * newHeight / height + 0.001;
                newMips[0] = Resize(newMips[0], fixXScale, fixYScale);
            }
        }
        private MemoryStream buildDdsImage(int mipMapIndex, out ImageEngineFormat imageFormat)
        {
            DomainPropertyByteValue formatProp = PropertyHeader.GetProperty("Format").FirstOrDefault()?.Value as DomainPropertyByteValue;

            imageFormat = ImageEngineFormat.Unknown;

            if (formatProp == null)
            {
                return(null);
            }

            string format = formatProp.PropertyString.Replace("PF_", null);

            switch (format)
            {
            case "DXT1":
            {
                imageFormat = ImageEngineFormat.DDS_DXT1;

                break;
            }

            case "DXT5":
            {
                imageFormat = ImageEngineFormat.DDS_DXT5;

                break;
            }

            case "G8":
            {
                imageFormat = ImageEngineFormat.DDS_G8_L8;

                break;
            }

            case "A8R8G8B8":
            {
                imageFormat = ImageEngineFormat.DDS_ARGB;

                break;
            }

            default:
            {
                return(null);
            }
            }

            DomainMipMap mipMap = MipMaps[mipMapIndex];

            DDSGeneral.DDS_HEADER header = DDSGeneral.Build_DDS_Header(0, mipMap.Height, mipMap.Width, imageFormat);

            MemoryStream stream = new MemoryStream();

            BinaryWriter writer = new BinaryWriter(stream);

            DDSGeneral.Write_DDS_Header(header, writer);

            stream.Write(mipMap.ImageData, 0, mipMap.ImageData.Length);

            stream.Flush();

            stream.Position = 0;

            return(stream);
        }
Example #6
0
        /// <summary>
        /// Save mipmaps as given format to stream.
        /// </summary>
        /// <param name="MipMaps">List of Mips to save.</param>
        /// <param name="format">Desired format.</param>
        /// <param name="destination">Stream to save to.</param>
        /// <param name="mipChoice">Determines how to handle mipmaps.</param>
        /// <param name="maxDimension">Maximum value for either image dimension.</param>
        /// <param name="mergeAlpha">True = alpha flattened down, directly affecting RGB.</param>
        /// <param name="mipToSave">0 based index on which mipmap to make top of saved image.</param>
        /// <returns>True on success.</returns>
        internal static bool Save(List <MipMap> MipMaps, ImageEngineFormat format, Stream destination, MipHandling mipChoice, bool mergeAlpha, int maxDimension = 0, int mipToSave = 0)
        {
            Format        temp    = new Format(format);
            List <MipMap> newMips = new List <MipMap>(MipMaps);

            if ((temp.IsMippable && mipChoice == MipHandling.GenerateNew) || (temp.IsMippable && newMips.Count == 1 && mipChoice == MipHandling.Default))
            {
                DDSGeneral.BuildMipMaps(newMips, mergeAlpha);
            }

            // KFreon: Resize if asked
            if (maxDimension != 0 && maxDimension < newMips[0].Width && maxDimension < newMips[0].Height)
            {
                if (!UsefulThings.General.IsPowerOfTwo(maxDimension))
                {
                    throw new ArgumentException($"{nameof(maxDimension)} must be a power of 2. Got {nameof(maxDimension)} = {maxDimension}");
                }


                // KFreon: Check if there's a mipmap suitable, removes all larger mipmaps
                var validMipmap = newMips.Where(img => (img.Width == maxDimension && img.Height <= maxDimension) || (img.Height == maxDimension && img.Width <= maxDimension));  // Check if a mip dimension is maxDimension and that the other dimension is equal or smaller
                if (validMipmap?.Count() != 0)
                {
                    int index = newMips.IndexOf(validMipmap.First());
                    newMips.RemoveRange(0, index);
                }
                else
                {
                    // KFreon: Get the amount the image needs to be scaled. Find largest dimension and get it's scale.
                    double scale = maxDimension * 1f / (newMips[0].Width > newMips[0].Height ? newMips[0].Width: newMips[0].Height);

                    // KFreon: No mip. Resize.
                    newMips[0] = Resize(newMips[0], scale, mergeAlpha);
                }
            }

            // KFreon: Ensure we have a power of two for dimensions
            double fixScale = 0;

            if (!UsefulThings.General.IsPowerOfTwo(newMips[0].Width) || !UsefulThings.General.IsPowerOfTwo(newMips[0].Height))
            {
                int newWidth = UsefulThings.General.RoundToNearestPowerOfTwo(newMips[0].Width);
                int newHeigh = UsefulThings.General.RoundToNearestPowerOfTwo(newMips[0].Height);

                // KFreon: Assuming same scale in both dimensions...
                fixScale = 1.0 * newWidth / newMips[0].Width;

                newMips[0] = Resize(newMips[0], fixScale, mergeAlpha);
            }


            if (fixScale != 0 || mipChoice == MipHandling.KeepTopOnly)
            {
                DestroyMipMaps(newMips, mipToSave);
            }

            if (fixScale != 0 && temp.IsMippable && mipChoice != MipHandling.KeepTopOnly)
            {
                DDSGeneral.BuildMipMaps(newMips, mergeAlpha);
            }


            bool result = false;

            if (temp.SurfaceFormat.ToString().Contains("DDS"))
            {
                result = DDSGeneral.Save(newMips, destination, temp);
            }
            else
            {
                // KFreon: Try saving with built in codecs
                var mip = newMips[0];
                if (WindowsWICCodecsAvailable)
                {
                    result = WIC_Codecs.SaveWithCodecs(mip.BaseImage, destination, format);
                }
            }

            if (mipChoice != MipHandling.KeepTopOnly && temp.IsMippable)
            {
                // KFreon: Necessary. Must be how I handle the lowest mip levels. i.e. WRONGLY :(
                // Figure out how big the file should be and make it that size

                int size   = 0;
                int width  = newMips[0].Width;
                int height = newMips[0].Height;

                int divisor = 1;
                if (temp.IsBlockCompressed)
                {
                    divisor = 4;
                }

                while (width >= 1 && height >= 1)
                {
                    int tempWidth  = width;
                    int tempHeight = height;

                    if (temp.IsBlockCompressed)
                    {
                        if (tempWidth < 4)
                        {
                            tempWidth = 4;
                        }
                        if (tempHeight < 4)
                        {
                            tempHeight = 4;
                        }
                    }


                    size   += tempWidth / divisor * tempHeight / divisor * temp.BlockSize;
                    width  /= 2;
                    height /= 2;
                }

                if (size > destination.Length - 128)
                {
                    byte[] blanks = new byte[size - (destination.Length - 128)];
                    destination.Write(blanks, 0, blanks.Length);
                }
            }

            return(result);
        }
Example #7
0
        /// <summary>
        /// Loads image from stream.
        /// </summary>
        /// <param name="stream">Full image stream.</param>
        /// <param name="Format">Detected Format.</param>
        /// <param name="extension">File Extension. Used to determine format more easily.</param>
        /// <param name="maxWidth">Maximum width to allow when loading. Resized if enforceResize = true.</param>
        /// <param name="maxHeight">Maximum height to allow when loading. Resized if enforceResize = true.</param>
        /// <param name="enforceResize">True = Resizes image to match either maxWidth or maxHeight.</param>
        /// <param name="header">DDS header of image.</param>
        /// <param name="mergeAlpha">ONLY valid when enforceResize is true. True = Flattens alpha down, directly affecting RGB.</param>
        /// <returns>List of Mipmaps.</returns>
        internal static List <MipMap> LoadImage(Stream stream, out Format Format, string extension, int maxWidth, int maxHeight, bool enforceResize, out DDSGeneral.DDS_HEADER header, bool mergeAlpha)
        {
            // KFreon: See if image is built-in codec agnostic.
            header = null;
            Format = ImageFormats.ParseFormat(stream, extension, ref header);
            List <MipMap> MipMaps = null;

            switch (Format.SurfaceFormat)
            {
            case ImageEngineFormat.BMP:
            case ImageEngineFormat.JPG:
            case ImageEngineFormat.PNG:
                MipMaps = WIC_Codecs.LoadWithCodecs(stream, maxWidth, maxHeight, false);
                break;

            case ImageEngineFormat.DDS_DXT1:
            case ImageEngineFormat.DDS_DXT2:
            case ImageEngineFormat.DDS_DXT3:
            case ImageEngineFormat.DDS_DXT4:
            case ImageEngineFormat.DDS_DXT5:
                if (WindowsWICCodecsAvailable)
                {
                    MipMaps = WIC_Codecs.LoadWithCodecs(stream, maxWidth, maxHeight, true);
                }
                else
                {
                    MipMaps = DDSGeneral.LoadDDS(stream, header, Format, maxHeight > maxWidth ? maxHeight : maxWidth);
                }
                break;

            case ImageEngineFormat.DDS_ARGB:
            case ImageEngineFormat.DDS_A8L8:
            case ImageEngineFormat.DDS_RGB:
            case ImageEngineFormat.DDS_ATI1:
            case ImageEngineFormat.DDS_ATI2_3Dc:
            case ImageEngineFormat.DDS_G8_L8:
            case ImageEngineFormat.DDS_V8U8:
                MipMaps = DDSGeneral.LoadDDS(stream, header, Format, maxHeight > maxWidth ? maxHeight : maxWidth);
                break;

            case ImageEngineFormat.TGA:
                var             img    = new TargaImage(stream);
                byte[]          pixels = UsefulThings.WinForms.Imaging.GetPixelDataFromBitmap(img.Image);
                WriteableBitmap wbmp   = UsefulThings.WPF.Images.CreateWriteableBitmap(pixels, img.Image.Width, img.Image.Height);
                var             mip1   = new MipMap(wbmp);
                MipMaps = new List <MipMap>()
                {
                    mip1
                };
                img.Dispose();
                break;

            default:
                throw new InvalidDataException("Image format is unknown.");
            }

            if (MipMaps == null || MipMaps.Count == 0)
            {
                throw new InvalidDataException("No mipmaps loaded.");
            }


            // KFreon: No resizing requested
            if (maxHeight == 0 && maxWidth == 0)
            {
                return(MipMaps);
            }

            // KFreon: Test if we need to resize
            var top = MipMaps.First();

            if (top.Width == maxWidth || top.Height == maxHeight)
            {
                return(MipMaps);
            }

            int max = maxWidth > maxHeight ? maxWidth : maxHeight;

            // KFreon: Attempt to resize
            var sizedMips = MipMaps.Where(m => m.Width > m.Height ? m.Width <= max : m.Height <= max);

            if (sizedMips != null && sizedMips.Any())  // KFreon: If there's already a mip, return that.
            {
                MipMaps = sizedMips.ToList();
            }
            else if (enforceResize)
            {
                // Get top mip and clear others.
                var mip = MipMaps[0];
                MipMaps.Clear();
                MipMap output = null;

                int divisor = mip.Width > mip.Height ? mip.Width / max : mip.Height / max;

                output = Resize(mip, 1f / divisor, mergeAlpha);

                MipMaps.Add(output);
            }
            return(MipMaps);
        }
        /// <summary>
        /// Save mipmaps as given format to stream.
        /// </summary>
        /// <param name="MipMaps">List of Mips to save.</param>
        /// <param name="mipChoice">Determines how to handle mipmaps.</param>
        /// <param name="maxDimension">Maximum value for either image dimension.</param>
        /// <param name="alphaSetting">Determines how to handle alpha.</param>
        /// <param name="mipToSave">0 based index on which mipmap to make top of saved image.</param>
        /// <param name="destFormatDetails">Details about the destination format.</param>
        /// <returns>True on success.</returns>
        internal static byte[] Save(List <MipMap> MipMaps, ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling mipChoice, AlphaSettings alphaSetting, int maxDimension = 0, int mipToSave = 0)
        {
            List <MipMap> newMips = new List <MipMap>(MipMaps);

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

            if ((destFormatDetails.IsMippable && mipChoice == MipHandling.GenerateNew) || (destFormatDetails.IsMippable && newMips.Count == 1 && mipChoice == MipHandling.Default))
            {
                DDSGeneral.BuildMipMaps(newMips);
            }

            // KFreon: Resize if asked
            if (maxDimension != 0 && maxDimension < width && maxDimension < height)
            {
                if (!UsefulThings.General.IsPowerOfTwo(maxDimension))
                {
                    throw new ArgumentException($"{nameof(maxDimension)} must be a power of 2. Got {nameof(maxDimension)} = {maxDimension}");
                }

                // KFreon: Check if there's a mipmap suitable, removes all larger mipmaps
                var validMipmap = newMips.Where(img => (img.Width == maxDimension && img.Height <= maxDimension) || (img.Height == maxDimension && img.Width <= maxDimension));  // Check if a mip dimension is maxDimension and that the other dimension is equal or smaller
                if (validMipmap?.Count() != 0)
                {
                    int index = newMips.IndexOf(validMipmap.First());
                    newMips.RemoveRange(0, index);
                }
                else
                {
                    // KFreon: Get the amount the image needs to be scaled. Find largest dimension and get it's scale.
                    double scale = maxDimension * 1d / (width > height ? width : height);

                    // KFreon: No mip. Resize.
                    newMips[0] = Resize(newMips[0], scale);
                }
            }

            // KFreon: Ensure we have a power of two for dimensions FOR DDS ONLY
            TestDDSMipSize(newMips, destFormatDetails, width, height, out double fixXScale, out double fixYScale, mipChoice);

            if (fixXScale != 0 || fixYScale != 0 || mipChoice == MipHandling.KeepTopOnly)
            {
                DestroyMipMaps(newMips, mipToSave);
            }

            if ((fixXScale != 0 || fixXScale != 0) && destFormatDetails.IsMippable && mipChoice != MipHandling.KeepTopOnly)
            {
                DDSGeneral.BuildMipMaps(newMips);
            }


            byte[] destination = null;
            if (destFormatDetails.IsDDS)
            {
                destination = DDSGeneral.Save(newMips, destFormatDetails, alphaSetting);
            }
            else
            {
                // KFreon: Try saving with built in codecs
                var mip = newMips[0];


                // Fix formatting
                byte[] newPixels = new byte[mip.Width * mip.Height * 4];
                for (int i = 0, j = 0; i < newPixels.Length; i++, j += mip.LoadedFormatDetails.ComponentSize)
                {
                    newPixels[i] = mip.LoadedFormatDetails.ReadByte(mip.Pixels, j);
                }

                destination = WIC_Codecs.SaveWithCodecs(newPixels, destFormatDetails.Format, mip.Width, mip.Height, alphaSetting);
            }

            return(destination);
        }
        internal static List <MipMap> LoadImage(Stream imageStream, AbstractHeader header, int maxDimension, double scale, ImageFormats.ImageEngineFormatDetails formatDetails)
        {
            imageStream.Seek(0, SeekOrigin.Begin);
            List <MipMap> MipMaps = null;

            int decodeWidth  = header.Width > header.Height ? maxDimension : 0;
            int decodeHeight = header.Width < header.Height ? maxDimension : 0;

            switch (header.Format)
            {
            case ImageEngineFormat.DDS_DXT1:
            case ImageEngineFormat.DDS_DXT2:
            case ImageEngineFormat.DDS_DXT3:
            case ImageEngineFormat.DDS_DXT4:
            case ImageEngineFormat.DDS_DXT5:
                MipMaps = WIC_Codecs.LoadWithCodecs(imageStream, decodeWidth, decodeHeight, scale, true, formatDetails);
                if (MipMaps == null)
                {
                    // Windows codecs unavailable/failed. Load with mine.
                    MipMaps = DDSGeneral.LoadDDS((MemoryStream)imageStream, (DDS_Header)header, maxDimension, formatDetails);
                }
                break;

            case ImageEngineFormat.DDS_G8_L8:
            case ImageEngineFormat.DDS_ARGB_4:
            case ImageEngineFormat.DDS_RGB_8:
            case ImageEngineFormat.DDS_V8U8:
            case ImageEngineFormat.DDS_A8L8:
            case ImageEngineFormat.DDS_ARGB_8:
            case ImageEngineFormat.DDS_ARGB_32F:
            case ImageEngineFormat.DDS_ABGR_8:
            case ImageEngineFormat.DDS_G16_R16:
            case ImageEngineFormat.DDS_R5G6B5:
            case ImageEngineFormat.DDS_ATI1:
            case ImageEngineFormat.DDS_ATI2_3Dc:
            case ImageEngineFormat.DDS_CUSTOM:
            case ImageEngineFormat.DDS_DX10:
                MipMaps = DDSGeneral.LoadDDS((MemoryStream)imageStream, (DDS_Header)header, maxDimension, formatDetails);
                break;

            case ImageEngineFormat.GIF:
            case ImageEngineFormat.JPG:
            case ImageEngineFormat.PNG:
            case ImageEngineFormat.BMP:
            case ImageEngineFormat.TIF:
                MipMaps = WIC_Codecs.LoadWithCodecs(imageStream, decodeWidth, decodeHeight, scale, false, formatDetails);
                break;

            case ImageEngineFormat.TGA:
                using (var tga = new TargaImage(imageStream, ((TGA_Header)header).header))
                    MipMaps = new List <MipMap>()
                    {
                        new MipMap(tga.ImageData, tga.Header.Width, tga.Header.Height, formatDetails)
                    };
                break;

            default:
                throw new FormatException($"Format unknown: {header.Format}.");
            }

            return(MipMaps);
        }
Example #10
0
 protected override void _Write(BinaryWriter writer)
 {
     byte[] ddsData = DDSGeneral.Save(MipMaps, FormatDetails, AlphaSettings, MipHandling);
     writer.Write(ddsData);
 }
Example #11
0
        /// <summary>
        /// Internal write implementation of the sub classes.
        /// </summary>
        /// <param name="writer"></param>
        /// <exception cref="Exception">Expected DDS RGB24 or RGBA32 color format!</exception>
        /// <exception cref="InvalidOperationException">
        /// </exception>
        protected override void _Write(BinaryWriter writer)
        {
            long baseOffset = writer.BaseStream.Position;

            Bitmap bmp = CreateBitmap();

            //bmp.RotateFlip(RotateFlipType.RotateNoneFlipY);
            if (DataFormat == PvrDataFormat.DDS || DataFormat == PvrDataFormat.DDS_2)
            {
                if (!(PixelFormat == PvrPixelFormat.DDS_DXT1_RGB24 || PixelFormat == PvrPixelFormat.DDS_DXT3_RGBA32))
                {
                    throw new Exception("Expected DDS RGB24 or RGBA32 color format!");
                }

                byte[] ddsBuffer = null;
                if (PixelFormat == PvrPixelFormat.DDS_DXT1_RGB24)
                {
                    DDSFormatDetails ddsFormatDetails = new DDSFormatDetails(DDSFormat.DDS_DXT1);
                    ddsBuffer = DDSGeneral.Save(MipMaps, ddsFormatDetails, DDSGeneral.AlphaSettings.KeepAlpha, DDSGeneral.MipHandling.Default);
                }
                else if (PixelFormat == PvrPixelFormat.DDS_DXT3_RGBA32)
                {
                    DDSFormatDetails ddsFormatDetails = new DDSFormatDetails(DDSFormat.DDS_DXT3);
                    ddsBuffer = DDSGeneral.Save(MipMaps, ddsFormatDetails, DDSGeneral.AlphaSettings.KeepAlpha, DDSGeneral.MipHandling.Default);
                }
                if (HasGlobalIndex)
                {
                    writer.Write(m_gbix);
                    writer.Write(GlobalIndexSize);
                    writer.Write(GlobalIndex);
                }

                writer.Write(m_pvrt);
                writer.Write(ddsBuffer.Length + 16);
                writer.Write((byte)PixelFormat);
                writer.Write((byte)DataFormat);
                writer.Write((ushort)0);
                writer.Write((ushort)Width);
                writer.Write((ushort)Height);
                writer.Write(ddsBuffer);
            }
            else
            {
                // Set the data format and pixel format and load the appropiate codecs
                PixelCodec = PvrPixelCodec.GetPixelCodec(PixelFormat);
                DataCodec  = PvrDataCodec.GetDataCodec(DataFormat);

                // Make sure the pixel and data codecs exists and we can encode to it
                if (PixelCodec == null || !PixelCodec.CanEncode)
                {
                    throw new InvalidOperationException();
                }
                if (DataCodec == null || !DataCodec.CanEncode)
                {
                    throw new InvalidOperationException();
                }
                DataCodec.PixelCodec = PixelCodec;

                byte[] decodedData = null;
                if (DataCodec.PaletteEntries != 0)
                {
                    if (DataCodec.VQ)
                    {
                        decodedData = BitmapToRawVQ(bmp, DataCodec.PaletteEntries, out m_texturePalette);
                    }
                    else
                    {
                        // Convert the bitmap to an array containing indicies.
                        decodedData = BitmapToRawIndexed(bmp, DataCodec.PaletteEntries, out m_texturePalette);

                        // If this texture has an external palette file, set up the palette encoder
                        if (DataCodec.NeedsExternalPalette)
                        {
                            PaletteEncoder = new PvpPaletteEncoder(m_texturePalette, (ushort)DataCodec.PaletteEntries, PixelFormat, PixelCodec);
                        }
                    }
                }
                else
                {
                    decodedData = BitmapToRaw(bmp);
                }

                // Calculate what the length of the texture will be
                int textureLength = 16 + (int)(Width * Height * (DataCodec.Bpp / 8.0));
                if (HasGlobalIndex)
                {
                    textureLength += 16;
                }
                if (DataCodec.PaletteEntries != 0 && !DataCodec.NeedsExternalPalette)
                {
                    textureLength += (DataCodec.PaletteEntries * PixelCodec.Bpp / 8);
                }

                // Calculate the mipmap padding (if the texture contains mipmaps)
                int mipmapPadding = 0;
                if (DataCodec.HasMipmaps)
                {
                    if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP)
                    {
                        // A 1x1 mipmap takes up as much space as a 2x1 mipmap
                        // There are also 4 extra bytes at the end of the file
                        mipmapPadding  = (DataCodec.Bpp) >> 3;
                        textureLength += 4;
                    }
                    else if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP_ALT)
                    {
                        // A 1x1 mipmap takes up as much space as a 2x2 mipmap
                        mipmapPadding = (3 * DataCodec.Bpp) >> 3;
                    }

                    textureLength += mipmapPadding;
                    for (int size = 1; size < Width; size <<= 1)
                    {
                        textureLength += Math.Max((size * size * DataCodec.Bpp) >> 3, 1);
                    }
                }

                MemoryStream output       = new MemoryStream(textureLength);
                BinaryWriter outputWriter = new BinaryWriter(output);

                // Write out the GBIX header (if we are including one)
                if (HasGlobalIndex)
                {
                    outputWriter.Write(m_gbix);
                    outputWriter.Write(GlobalIndexSize);
                    outputWriter.Write(GlobalIndex);
                }

                // Write out the PVRT header
                outputWriter.Write(m_pvrt);
                if (HasGlobalIndex)
                {
                    outputWriter.Write(textureLength - 24);
                }
                else
                {
                    outputWriter.Write(textureLength - 8);
                }
                outputWriter.Write((byte)PixelFormat);
                outputWriter.Write((byte)DataFormat);
                outputWriter.Write((ushort)0);
                outputWriter.Write((ushort)Width);
                outputWriter.Write((ushort)Height);

                // If we have an internal palette, write it
                if (DataCodec.PaletteEntries != 0 && !DataCodec.NeedsExternalPalette)
                {
                    byte[] palette = PixelCodec.EncodePalette(m_texturePalette, DataCodec.PaletteEntries);
                    output.Write(palette, 0, palette.Length);
                }

                // Write out any mipmaps
                if (DataCodec.HasMipmaps)
                {
                    // Write out any padding bytes before the 1x1 mipmap
                    for (int i = 0; i < mipmapPadding; i++)
                    {
                        output.WriteByte(0);
                    }
                    for (int size = 1; size < Width; size <<= 1)
                    {
                        byte[] mipmapDecodedData = null;
                        if (DataCodec.NeedsExternalPalette)
                        {
                            if (DataCodec.VQ)
                            {
                                mipmapDecodedData = BitmapToRawVQResized(bmp, size, 1, m_codeBook);
                            }
                            else
                            {
                                mipmapDecodedData = BitmapToRawIndexedResized(bmp, size, 1, m_palette);
                            }
                        }
                        else
                        {
                            mipmapDecodedData = BitmapToRawResized(bmp, size, 1);
                        }
                        byte[] mipmapTextureData = DataCodec.Encode(mipmapDecodedData, 0, size, size);
                        output.Write(mipmapTextureData, 0, mipmapTextureData.Length);
                    }
                }

                // Write the texture data
                byte[] textureData = DataCodec.Encode(decodedData, Width, Height, null);
                output.Write(textureData, 0, textureData.Length);

                // If the data format is square twiddled with mipmaps, write out the extra bytes.
                if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP)
                {
                    output.Write(new byte[] { 0, 0, 0, 0 }, 0, 4);
                }

                // Compress the texture
                if (CompressionFormat != PvrCompressionFormat.NONE)
                {
                    CompressionCodec = PvrCompressionCodec.GetCompressionCodec(CompressionFormat);
                    if (CompressionCodec != null)
                    {
                        // Ok, we need to convert the current stream to an array, compress it, then write it back to a new stream
                        byte[] buffer = output.ToArray();
                        buffer = CompressionCodec.Compress(buffer, (HasGlobalIndex ? 0x20 : 0x10), PixelCodec, DataCodec);
                        writer.Write(buffer);
                    }
                }
                else
                {
                    writer.Write(output.GetBuffer());
                }
            }
        }
Example #12
0
        /// <summary>
        /// Internal read implementation of the sub classes.
        /// </summary>
        /// <param name="reader"></param>
        /// <exception cref="Exception">Expected DDS RGB24 or RGBA32 color format!</exception>
        /// <exception cref="NotImplementedException">TODO</exception>
        protected override void _Read(BinaryReader reader)
        {
            long baseOffset = reader.BaseStream.Position;

            int gbixOffset = 0;
            int pvrtOffset = 0;

            uint identifier = reader.ReadUInt32();

            if (identifier == m_gbix) //"GBIX"
            {
                HasGlobalIndex  = true;
                GlobalIndexSize = reader.ReadUInt32();
                GlobalIndex     = reader.ReadBytes((int)GlobalIndexSize);

                reader.BaseStream.Seek(4, SeekOrigin.Current); //Skip "PVRT"
                gbixOffset = 0x00;
                pvrtOffset = 0x08 + (int)GlobalIndexSize;
            }
            else
            {
                identifier = reader.ReadUInt32();
                if (identifier == m_gbix)
                {
                    HasGlobalIndex  = true;
                    GlobalIndexSize = reader.ReadUInt32();
                    GlobalIndex     = reader.ReadBytes((int)GlobalIndexSize);
                    gbixOffset      = 0x04;
                    pvrtOffset      = 0x0C + (int)GlobalIndexSize;
                }
                else if (identifier == m_pvrt)
                {
                    gbixOffset = -1;
                    pvrtOffset = 0x04;
                }
                else
                {
                    gbixOffset = -1;
                    pvrtOffset = 0x00;
                    reader.BaseStream.Seek(-4, SeekOrigin.Current);
                }
            }

            // Read information about the texture
            ContentSize = reader.ReadUInt32();
            PixelFormat = (PvrPixelFormat)reader.ReadByte();
            DataFormat  = (PvrDataFormat)reader.ReadByte();
            reader.BaseStream.Seek(2, SeekOrigin.Current);
            Width  = reader.ReadUInt16();
            Height = reader.ReadUInt16();

            if (DataFormat == PvrDataFormat.DDS || DataFormat == PvrDataFormat.DDS_2)
            {
                if (!(PixelFormat == PvrPixelFormat.DDS_DXT1_RGB24 || PixelFormat == PvrPixelFormat.DDS_DXT3_RGBA32))
                {
                    throw new Exception("Expected DDS RGB24 or RGBA32 color format!");
                }

                long             ddsOffset = reader.BaseStream.Position;
                DDS_Header       header    = new DDS_Header(reader.BaseStream);
                DDSFormatDetails format    = new DDSFormatDetails(header.Format, header.DX10_DXGI_AdditionalHeader.dxgiFormat);
                reader.BaseStream.Seek(ddsOffset, SeekOrigin.Begin);

                byte[] ddsBuffer = reader.ReadBytes(header.dwPitchOrLinearSize + header.dwSize + 128);

                MemoryStream memoryStream = new MemoryStream(ddsBuffer, 0, ddsBuffer.Length, true, true);
                MipMaps = DDSGeneral.LoadDDS(memoryStream, header, 0, format);
                memoryStream.Close();

                Width  = header.Width;
                Height = header.Height;
            }
            else
            {
                // Get the codecs and make sure we can decode using them
                PixelCodec = PvrPixelCodec.GetPixelCodec(PixelFormat);
                DataCodec  = PvrDataCodec.GetDataCodec(DataFormat);
                if (DataCodec != null && PixelCodec != null)
                {
                    DataCodec.PixelCodec = PixelCodec;
                }

                // Set the number of palette entries
                int paletteEntries = DataCodec.PaletteEntries;
                if (DataFormat == PvrDataFormat.VECTOR_QUANTIZATION_SMALL || DataFormat == PvrDataFormat.VECTOR_QUANTIZATION_SMALL_MIPMAP)
                {
                    if (Width <= 16)
                    {
                        paletteEntries = 64; // Actually 16
                    }
                    else if (Width <= 32)
                    {
                        paletteEntries = 256; // Actually 64
                    }
                    else if (Width <= 64)
                    {
                        paletteEntries = 512; // Actually 128
                    }
                    else
                    {
                        paletteEntries = 1024; // Actually 256
                    }
                }

                // Set the palette and data offsets
                int paletteOffset = 0;
                int dataOffset    = 0;
                if (paletteEntries == 0 || DataCodec.NeedsExternalPalette)
                {
                    paletteOffset = -1;
                    dataOffset    = pvrtOffset + 0x10;
                }
                else
                {
                    paletteOffset = pvrtOffset + 0x10;
                    dataOffset    = paletteOffset + (paletteEntries * (PixelCodec.Bpp >> 3));
                }

                // Get the compression format and determine if we need to decompress this texture
                reader.BaseStream.Seek(baseOffset, SeekOrigin.Begin);
                uint first = reader.ReadUInt32();
                reader.BaseStream.Seek(baseOffset + pvrtOffset + 4, SeekOrigin.Begin);
                uint second = reader.ReadUInt32();
                if (first == second - pvrtOffset + dataOffset + 8)
                {
                    CompressionFormat = PvrCompressionFormat.RLE;
                }
                else
                {
                    CompressionFormat = PvrCompressionFormat.NONE;
                }
                CompressionCodec = PvrCompressionCodec.GetCompressionCodec(CompressionFormat);

                if (CompressionFormat != PvrCompressionFormat.NONE && CompressionCodec != null)
                {
                    //TODO: Convert to stream compatible code
                    throw new NotImplementedException("TODO");
                    //m_encodedData = CompressionCodec.Decompress(m_encodedData, dataOffset, PixelCodec, DataCodec);

                    // Now place the offsets in the appropiate area
                    if (CompressionFormat == PvrCompressionFormat.RLE)
                    {
                        if (gbixOffset != -1)
                        {
                            gbixOffset -= 4;
                        }
                        pvrtOffset -= 4;
                        if (paletteOffset != -1)
                        {
                            paletteOffset -= 4;
                        }
                        dataOffset -= 4;
                    }
                }

                // If the texture contains mipmaps, gets the offsets of them
                int[] mipmapOffsets;
                if (DataCodec.HasMipmaps)
                {
                    int mipmapOffset = 0;
                    mipmapOffsets = new int[(int)Math.Log(Width, 2) + 1];

                    // Calculate the padding for the first mipmap offset
                    if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP)
                    {
                        mipmapOffset = (DataCodec.Bpp) >> 3; // A 1x1 mipmap takes up as much space as a 2x1 mipmap
                    }
                    else if (DataFormat == PvrDataFormat.SQUARE_TWIDDLED_MIPMAP_ALT)
                    {
                        mipmapOffset = (3 * DataCodec.Bpp) >> 3; // A 1x1 mipmap takes up as much space as a 2x2 mipmap
                    }
                    for (int i = mipmapOffsets.Length - 1, size = 1; i >= 0; i--, size <<= 1)
                    {
                        mipmapOffsets[i] = mipmapOffset;
                        mipmapOffset    += Math.Max((size * size * DataCodec.Bpp) >> 3, 1);
                    }
                }
                else
                {
                    mipmapOffsets = new int[1] {
                        0
                    };
                }

                //DecodeMipmaps()
                if (paletteOffset != -1) // The texture contains an embedded palette
                {
                    reader.BaseStream.Seek(baseOffset + paletteOffset, SeekOrigin.Begin);
                    DataCodec.SetPalette(reader, paletteEntries);
                }

                MipMaps = new List <MipMap>();
                if (DataCodec.HasMipmaps)
                {
                    for (int i = 0, size = Width; i < mipmapOffsets.Length; i++, size >>= 1)
                    {
                        reader.BaseStream.Seek(baseOffset + dataOffset + mipmapOffsets[i], SeekOrigin.Begin);
                        byte[] pixels = DataCodec.Decode(reader, size, size, PixelCodec);
                        MipMaps.Add(new MipMap(pixels, size, size));
                    }
                }
                else
                {
                    reader.BaseStream.Seek(baseOffset + dataOffset + mipmapOffsets[0], SeekOrigin.Begin);
                    byte[] pixels = DataCodec.Decode(reader, Width, Height, PixelCodec);
                    MipMaps.Add(new MipMap(pixels, Width, Height));
                }
            }
            if (HasGlobalIndex)
            {
                reader.BaseStream.Seek(baseOffset + ContentSize + 0xC, SeekOrigin.Begin);
            }
            else
            {
                reader.BaseStream.Seek(baseOffset + ContentSize, SeekOrigin.Begin);
            }
        }
        byte[] AttemptSaveUsingOriginalData(ImageFormats.ImageEngineFormatDetails destFormatDetails, MipHandling GenerateMips, int desiredMaxDimension, int mipToSave, AlphaSettings alphaSetting)
        {
            int        start      = 0;
            int        destStart  = 0;
            int        length     = OriginalData.Length;
            int        newWidth   = Width;
            int        newHeight  = Height;
            DDS_Header tempHeader = null;

            byte[] data             = null;
            byte[] tempOriginalData = OriginalData;

            if (destFormatDetails.IsDDS)
            {
                destStart = destFormatDetails.HeaderSize;
                start     = destStart;

                int mipCount = 0;

                if (mipToSave != 0)
                {
                    mipCount  = 1;
                    newWidth  = MipMaps[mipToSave].Width;
                    newHeight = MipMaps[mipToSave].Height;

                    start  = ImageFormats.GetCompressedSize(mipToSave, destFormatDetails, Width, Height);
                    length = ImageFormats.GetCompressedSize(1, destFormatDetails, newWidth, newHeight);
                }
                else if (desiredMaxDimension != 0 && desiredMaxDimension < Width && desiredMaxDimension < Height)
                {
                    int index = MipMaps.FindIndex(t => t.Width < desiredMaxDimension && t.Height < desiredMaxDimension);

                    // If none found, do a proper save and see what happens.
                    if (index == -1)
                    {
                        return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
                    }

                    mipCount -= index;
                    newWidth  = MipMaps[index].Width;
                    newHeight = MipMaps[index].Height;

                    start  = ImageFormats.GetCompressedSize(index, destFormatDetails, Width, Height);
                    length = ImageFormats.GetCompressedSize(mipCount, destFormatDetails, newWidth, newHeight);
                }
                else
                {
                    if (alphaSetting == AlphaSettings.RemoveAlphaChannel)
                    {
                        // Can't edit alpha directly in premultiplied formats. Not easily anyway.
                        if (destFormatDetails.IsPremultipliedFormat)
                        {
                            return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
                        }


                        // DDS Formats only
                        switch (destFormatDetails.Format)
                        {
                        // Excluded cos they have no true alpha
                        case ImageEngineFormat.DDS_A8:
                        case ImageEngineFormat.DDS_A8L8:
                        case ImageEngineFormat.DDS_ATI1:
                        case ImageEngineFormat.DDS_ATI2_3Dc:
                        case ImageEngineFormat.DDS_V8U8:
                        case ImageEngineFormat.DDS_G16_R16:
                        case ImageEngineFormat.DDS_G8_L8:
                        case ImageEngineFormat.DDS_R5G6B5:
                        case ImageEngineFormat.DDS_RGB_8:
                        case ImageEngineFormat.DDS_DXT1:
                            break;

                        // Exluded cos they're alpha isn't easily edited
                        case ImageEngineFormat.DDS_DXT2:
                        case ImageEngineFormat.DDS_DXT4:
                            break;

                        // Excluded cos they're currently unsupported
                        case ImageEngineFormat.DDS_CUSTOM:
                        case ImageEngineFormat.DDS_DX10:
                        case ImageEngineFormat.DDS_ARGB_4:
                            break;

                        case ImageEngineFormat.DDS_ABGR_8:
                        case ImageEngineFormat.DDS_ARGB_32F:
                        case ImageEngineFormat.DDS_ARGB_8:
                        case ImageEngineFormat.DDS_DXT3:
                        case ImageEngineFormat.DDS_DXT5:
                            tempOriginalData = new byte[OriginalData.Length];
                            Array.Copy(OriginalData, tempOriginalData, OriginalData.Length);

                            // Edit alpha values
                            int    alphaStart = 128;
                            int    alphaJump  = 0;
                            byte[] alphaBlock = null;
                            if (destFormatDetails.IsBlockCompressed)
                            {
                                alphaJump  = 16;
                                alphaBlock = new byte[8];
                                for (int i = 0; i < 8; i++)
                                {
                                    alphaBlock[i] = 255;
                                }
                            }
                            else
                            {
                                alphaJump  = destFormatDetails.ComponentSize * 4;
                                alphaBlock = new byte[destFormatDetails.ComponentSize];

                                switch (destFormatDetails.ComponentSize)
                                {
                                case 1:
                                    alphaBlock[0] = 255;
                                    break;

                                case 2:
                                    alphaBlock = BitConverter.GetBytes(ushort.MaxValue);
                                    break;

                                case 4:
                                    alphaBlock = BitConverter.GetBytes(1f);
                                    break;
                                }
                            }

                            for (int i = alphaStart; i < OriginalData.Length; i += alphaJump)
                            {
                                Array.Copy(alphaBlock, 0, tempOriginalData, i, alphaBlock.Length);
                            }

                            break;
                        }
                    }


                    switch (GenerateMips)
                    {
                    case MipHandling.KeepExisting:
                        mipCount = NumMipMaps;
                        break;

                    case MipHandling.Default:
                        if (NumMipMaps > 1)
                        {
                            mipCount = NumMipMaps;
                        }
                        else
                        {
                            goto case MipHandling.GenerateNew;      // Eww goto...
                        }
                        break;

                    case MipHandling.GenerateNew:
                        ImageEngine.DestroyMipMaps(MipMaps);
                        ImageEngine.TestDDSMipSize(MipMaps, destFormatDetails, Width, Height, out double fixXScale, out double fixYScale, GenerateMips);

                        // Wrong sizing, so can't use original data anyway.
                        if (fixXScale != 0 || fixYScale != 0)
                        {
                            return(ImageEngine.Save(MipMaps, destFormatDetails, GenerateMips, alphaSetting, desiredMaxDimension, mipToSave));
                        }


                        mipCount = DDSGeneral.BuildMipMaps(MipMaps);

                        // Compress mipmaps excl top
                        byte[] formattedMips = DDSGeneral.Save(MipMaps.GetRange(1, MipMaps.Count - 1), destFormatDetails, alphaSetting);
                        if (formattedMips == null)
                        {
                            return(null);
                        }

                        // Get top mip size and create destination array
                        length = ImageFormats.GetCompressedSize(0, destFormatDetails, newWidth, newHeight);     // Should be the length of the top mipmap.
                        data   = new byte[formattedMips.Length + length];

                        // Copy smaller mips to destination
                        Array.Copy(formattedMips, destFormatDetails.HeaderSize, data, length, formattedMips.Length - destFormatDetails.HeaderSize);
                        break;

                    case MipHandling.KeepTopOnly:
                        mipCount = 1;
                        length   = ImageFormats.GetCompressedSize(1, destFormatDetails, newWidth, newHeight);
                        break;
                    }
                }

                // Header
                tempHeader = new DDS_Header(mipCount, newHeight, newWidth, destFormatDetails.Format, destFormatDetails.DX10Format);
            }

            // Use existing array, otherwise create one.
            data = data ?? new byte[length];
            Array.Copy(tempOriginalData, start, data, destStart, length - destStart);

            // Write header if existing (DDS Only)
            if (tempHeader != null)
            {
                tempHeader.WriteToArray(data, 0);
            }

            return(data);
        }