/// <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); }
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(); }
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); }
/// <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); }
/// <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); }
protected override void _Write(BinaryWriter writer) { byte[] ddsData = DDSGeneral.Save(MipMaps, FormatDetails, AlphaSettings, MipHandling); writer.Write(ddsData); }
/// <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()); } } }
/// <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); }