/// <summary> /// Decompresses a mipmap in the file at the specified level from the specified data. /// </summary> /// <returns>The mipmap.</returns> /// <param name="inData">ExtendedData containing the mipmap level.</param> /// <param name="mipLevel">The mipmap level of the data.</param> private Image <Rgba32> DecompressMipMap(byte[] inData, uint mipLevel) { if (inData == null || inData.Length <= 0) { throw new ArgumentException("No image data provided.", nameof(inData)); } Image <Rgba32> map = null; var targetXRes = GetLevelAdjustedResolutionValue(GetResolution().X, mipLevel); var targetYRes = GetLevelAdjustedResolutionValue(GetResolution().Y, mipLevel); if (targetXRes <= 0 || targetYRes <= 0) { throw new ArgumentException ( $"The input mipmap level produced an invalid resolution: {mipLevel}", nameof(mipLevel) ); } switch (Header.CompressionType) { case TextureCompressionType.Palettized: { map = new Image <Rgba32>((int)targetXRes, (int)targetYRes); using (var ms = new MemoryStream(inData)) { using (var br = new BinaryReader(ms)) { // Read colour information for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { var colorIndex = br.ReadByte(); var paletteColor = _palette[colorIndex]; map[x, y] = paletteColor; } } // Read Alpha information var alphaValues = new List <byte>(); if (GetAlphaBitDepth() > 0) { if (GetAlphaBitDepth() == 1) { var alphaByteCount = (int)Math.Ceiling((double)(targetXRes * targetYRes) / 8); alphaValues = Decode1BitAlpha(br.ReadBytes(alphaByteCount)); } else if (GetAlphaBitDepth() == 4) { var alphaByteCount = (int)Math.Ceiling((double)(targetXRes * targetYRes) / 2); alphaValues = Decode4BitAlpha(br.ReadBytes(alphaByteCount)); } else if (GetAlphaBitDepth() == 8) { // Directly read the alpha values for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { var alphaValue = br.ReadByte(); alphaValues.Add(alphaValue); } } } } else { // The map is fully opaque for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { alphaValues.Add(255); } } } // Build the final map for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { var valueIndex = (int)(x + (targetXRes * y)); var alphaValue = alphaValues[valueIndex]; var pixelColor = map[x, y]; var finalPixel = new Rgba32(pixelColor.R, pixelColor.G, pixelColor.B, alphaValue); map[x, y] = finalPixel; } } } } break; } case TextureCompressionType.DXTC: { var squishOptions = SquishOptions.DXT1; if (Header.PixelFormat == BLPPixelFormat.DXT3) { squishOptions = SquishOptions.DXT3; } else if (Header.PixelFormat == BLPPixelFormat.DXT5) { squishOptions = SquishOptions.DXT5; } map = SquishCompression.DecompressToImage(inData, (int)targetXRes, (int)targetYRes, squishOptions); break; } case TextureCompressionType.Uncompressed: { map = new Image <Rgba32>((int)targetXRes, (int)targetYRes); using (var ms = new MemoryStream(inData)) { using (var br = new BinaryReader(ms)) { for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { var a = br.ReadByte(); var r = br.ReadByte(); var g = br.ReadByte(); var b = br.ReadByte(); var pixelColor = new Rgba32(r, g, b, a); map[x, y] = pixelColor; } } } } break; } case TextureCompressionType.JPEG: { // Merge the JPEG header with the data in the mipmap var jpegImage = new byte[_jpegHeaderSize + inData.Length]; Buffer.BlockCopy(_jpegHeader, 0, jpegImage, 0, (int)_jpegHeaderSize); Buffer.BlockCopy(inData, 0, jpegImage, (int)_jpegHeaderSize, inData.Length); using (var ms = new MemoryStream(jpegImage)) { map = Image.Load <Rgba32>(ms).Clone(cx => cx.Invert()); } break; } } return(map); }
/// <summary> /// Compresses in input bitmap into a single mipmap at the specified mipmap level, where a mip level is a /// bisection of the resolution. /// For instance, a mip level of 2 applied to a 64x64 image would produce an image with a resolution of 16x16. /// This function expects the mipmap level to be reasonable (i.e, not a level which would produce a mip smaller /// than 1x1). /// </summary> /// <returns>The image.</returns> /// <param name="inImage">Image.</param> /// <param name="mipLevel">Mip level.</param> private byte[] CompressImage(Image <Rgba32> inImage, uint mipLevel) { var targetXRes = GetLevelAdjustedResolutionValue(GetResolution().X, mipLevel); var targetYRes = GetLevelAdjustedResolutionValue(GetResolution().Y, mipLevel); var colourData = new List <byte>(); var alphaData = new List <byte>(); using (var resizedImage = ResizeImage(inImage, (int)targetXRes, (int)targetYRes)) { if (Header.CompressionType == TextureCompressionType.Palettized) { // Generate the colour data for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { var nearestColor = FindClosestMatchingColor(resizedImage[x, y]); var paletteIndex = (byte)_palette.IndexOf(nearestColor); colourData.Add(paletteIndex); } } // Generate the alpha data if (GetAlphaBitDepth() > 0) { if (GetAlphaBitDepth() == 1) { // We're going to be attempting to map 8 pixels on each X iteration for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; x += 8) { // The alpha value is stored per-bit in the byte (8 alpha values per byte) byte alphaByte = 0; for (byte i = 0; (i < 8) && (i < targetXRes); ++i) { var pixelAlpha = resizedImage[x + i, y].A; if (pixelAlpha > 0) { pixelAlpha = 1; } // Shift the value into the correct position in the byte pixelAlpha = (byte)(pixelAlpha << (7 - i)); alphaByte = (byte)(alphaByte | pixelAlpha); } alphaData.Add(alphaByte); } } } else if (GetAlphaBitDepth() == 4) { // We're going to be attempting to map 2 pixels on each X iteration for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; x += 2) { // The alpha value is stored as half a byte (2 alpha values per byte) // Extract these two values and map them to a byte size (4 bits can hold 0 - 15 // alpha) byte alphaByte = 0; for (byte i = 0; (i < 2) && (i < targetXRes); ++i) { // Get the value from the image var pixelAlpha = resizedImage[x + i, y].A; // Map the value to a 4-bit integer pixelAlpha = (byte)ExtendedMath.Map(pixelAlpha, 0, 255, 0, 15); // Shift the value to the upper bits on the first iteration, and leave it where // it is on the second one pixelAlpha = (byte)(pixelAlpha << (4 * (1 - i))); alphaByte = (byte)(alphaByte | pixelAlpha); } alphaData.Add(alphaByte); } } } else if (GetAlphaBitDepth() == 8) { for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { // The alpha value is stored as a whole byte var alphaValue = resizedImage[x, y].A; alphaData.Add(alphaValue); } } } } else { // The map is fully opaque for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { alphaData.Add(255); } } } } else if (Header.CompressionType == TextureCompressionType.DXTC) { using (var rgbaStream = new MemoryStream()) { using (var bw = new BinaryWriter(rgbaStream)) { for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { bw.Write(resizedImage[x, y].R); bw.Write(resizedImage[x, y].G); bw.Write(resizedImage[x, y].B); bw.Write(resizedImage[x, y].A); } } // Finish writing the data bw.Flush(); var rgbaBytes = rgbaStream.ToArray(); var squishOptions = SquishOptions.DXT1; if (Header.PixelFormat == BLPPixelFormat.DXT3) { squishOptions = SquishOptions.DXT3; } else if (Header.PixelFormat == BLPPixelFormat.DXT5) { squishOptions = SquishOptions.DXT5; } // TODO: Implement squish compression colourData = new List <byte> ( SquishCompression.CompressImage ( rgbaBytes, (int)targetXRes, (int)targetYRes, squishOptions ) ); } } } else if (Header.CompressionType == TextureCompressionType.Uncompressed) { using (var argbStream = new MemoryStream()) { using (var bw = new BinaryWriter(argbStream)) { for (var y = 0; y < targetYRes; ++y) { for (var x = 0; x < targetXRes; ++x) { bw.Write(resizedImage[x, y].A); bw.Write(resizedImage[x, y].R); bw.Write(resizedImage[x, y].G); bw.Write(resizedImage[x, y].B); } } // Finish writing the data bw.Flush(); var argbBytes = argbStream.ToArray(); colourData = new List <byte>(argbBytes); } } } } // After compression of the data, merge the color data and alpha data var compressedMipMap = new byte[colourData.Count + alphaData.Count]; Buffer.BlockCopy(colourData.ToArray(), 0, compressedMipMap, 0, colourData.ToArray().Length); Buffer.BlockCopy ( alphaData.ToArray(), 0, compressedMipMap, colourData.ToArray().Length, alphaData.ToArray().Length ); return(compressedMipMap); }
/// <summary> /// Decompresses a mipmap in the file at the specified level from the specified data. /// </summary> /// <returns>The mipmap.</returns> /// <param name="inData">Data containing the mipmap level.</param> /// <param name="mipLevel">The mipmap level of the data</param> private Bitmap DecompressMipMap(byte[] inData, uint mipLevel) { Bitmap map = null; uint targetXRes = GetLevelAdjustedResolutionValue(GetResolution().X, mipLevel); uint targetYRes = GetLevelAdjustedResolutionValue(GetResolution().Y, mipLevel); if (inData.Length > 0 && targetXRes > 0 && targetYRes > 0) { if (this.Header.CompressionType == TextureCompressionType.Palettized) { map = new Bitmap((int)targetXRes, (int)targetYRes, PixelFormat.Format32bppArgb); using (MemoryStream ms = new MemoryStream(inData)) { using (BinaryReader br = new BinaryReader(ms)) { // Read colour information for (int y = 0; y < targetYRes; ++y) { for (int x = 0; x < targetXRes; ++x) { byte colorIndex = br.ReadByte(); Color paletteColor = this.Palette[colorIndex]; map.SetPixel(x, y, paletteColor); } } // Read Alpha information List <byte> alphaValues = new List <byte>(); if (GetAlphaBitDepth() > 0) { if (GetAlphaBitDepth() == 1) { int alphaByteCount = (int)Math.Ceiling(((double)(targetXRes * targetYRes) / 8)); alphaValues = Decode1BitAlpha(br.ReadBytes(alphaByteCount)); } else if (GetAlphaBitDepth() == 4) { int alphaByteCount = (int)Math.Ceiling(((double)(targetXRes * targetYRes) / 2)); alphaValues = Decode4BitAlpha(br.ReadBytes(alphaByteCount)); } else if (GetAlphaBitDepth() == 8) { // Directly read the alpha values for (int y = 0; y < targetYRes; ++y) { for (int x = 0; x < targetXRes; ++x) { byte alphaValue = br.ReadByte(); alphaValues.Add(alphaValue); } } } } else { // The map is fully opaque for (int y = 0; y < targetYRes; ++y) { for (int x = 0; x < targetXRes; ++x) { alphaValues.Add(255); } } } // Build the final map for (int y = 0; y < targetYRes; ++y) { for (int x = 0; x < targetXRes; ++x) { int valueIndex = (int)(x + (targetXRes * y)); byte alphaValue = alphaValues[valueIndex]; Color pixelColor = map.GetPixel(x, y); Color finalPixel = Color.FromArgb(alphaValue, pixelColor.R, pixelColor.G, pixelColor.B); map.SetPixel(x, y, finalPixel); } } } } } else if (this.Header.CompressionType == TextureCompressionType.DXTC) { SquishOptions squishOptions = SquishOptions.DXT1; if (this.Header.PixelFormat == BLPPixelFormat.DXT3) { squishOptions = SquishOptions.DXT3; } else if (this.Header.PixelFormat == BLPPixelFormat.DXT5) { squishOptions = SquishOptions.DXT5; } map = (Bitmap)SquishCompression.DecompressToBitmap(inData, (int)targetXRes, (int)targetYRes, squishOptions); } else if (this.Header.CompressionType == TextureCompressionType.Uncompressed) { map = new Bitmap((int)targetXRes, (int)targetYRes, PixelFormat.Format32bppArgb); using (MemoryStream ms = new MemoryStream(inData)) { using (BinaryReader br = new BinaryReader(ms)) { for (int y = 0; y < targetYRes; ++y) { for (int x = 0; x < targetXRes; ++x) { byte a = br.ReadByte(); byte r = br.ReadByte(); byte g = br.ReadByte(); byte b = br.ReadByte(); Color pixelColor = Color.FromArgb(a, r, g, b); map.SetPixel(x, y, pixelColor); } } } } } else if (this.Header.CompressionType == TextureCompressionType.JPEG) { // Merge the JPEG header with the data in the mipmap byte[] jpegImage = new byte[this.JPEGHeaderSize + inData.Length]; Buffer.BlockCopy(this.JPEGHeader, 0, jpegImage, 0, (int)this.JPEGHeaderSize); Buffer.BlockCopy(inData, 0, jpegImage, (int)this.JPEGHeaderSize, inData.Length); using (MemoryStream ms = new MemoryStream(jpegImage)) { map = new Bitmap(ms).Invert(); } } } return(map); }