/// <summary> /// Saves the currently loaded image to a uncompressed or paletted Blp file. /// </summary> /// <param name="filename">The path to the output file.</param> /// <param name="format">Raw format. See <see cref="RawFormat"/></param> /// <param name="resizeMethod">Method for possibly required resizing. See <see cref="FixSize"/></param> /// <param name="hasMipmaps">Specifies whether mipmaps are generated or not.</param> /// <param name="samplingFactor">(Optional) Sampling factor 1-30. Lower values stand for higher quality to the disadvantage of speed. Does not apply to Raw3 encoding.</param> /// <param name="samplingFactor">(Optional) Specifies whether or not to use dithering. Does not apply to Raw3 encoding.</param> public void Save(string filename, RawFormat format, ResizeMethod resizeMethod, bool hasMipmaps, byte samplingFactor = 10, bool dither = false) { if (bmp != null) { FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Write); using (BinaryWriter bw = new BinaryWriter(fs)) { Blp2Header header = new Blp2Header(DataType.Uncompressed_DirectX, 0, AlphaDepth.Alpha8Bit, AlphaEncoding.RAW8BIT, (byte)(hasMipmaps ? 1 : 0), 0, 0); List <Bitmap> bmps = new List <Bitmap>(); // Add resized image to the output bmps.Add(FixSize(resizeMethod)); header.width = (uint)bmps[0].Width; header.height = (uint)bmps[0].Height; // Add mipmaps if (hasMipmaps) { bmps.AddRange(GenerateMipmaps(bmps[0])); } byte[] compImgData = { }; byte[] colorPalette = new byte[256 * 4]; if (format == RawFormat.Raw1) { // Uncompressed paletted image if (samplingFactor < 1 || samplingFactor > 30) { throw new ArgumentException("Sampling factor must be between 1 and 30."); } header.encoding = Encoding.RAW1; // Quantize colors to a 256 color palette NeuQuant quant = new NeuQuant(bmps[0]); quant.Quantize(); // Set color palette int j = 0; foreach (Color c in quant.Palette) { colorPalette[j] = c.R; colorPalette[j + 1] = c.G; colorPalette[j + 2] = c.B; colorPalette[j + 3] = 0; j += 4; } for (int i = 0; i < bmps.Count; i++) { // Copy raw image data to byte array BitmapData bmpData = bmps[i].LockBits(new Rectangle(0, 0, bmps[i].Width, bmps[i].Height), ImageLockMode.ReadOnly, bmps[i].PixelFormat); byte[] imgData = new byte[bmpData.Stride * bmps[i].Height]; Marshal.Copy(bmpData.Scan0, imgData, 0, bmpData.Stride * bmps[i].Height); bmps[i].UnlockBits(bmpData); byte[] alphaData = new byte[imgData.Length / 4]; // Index image colors int curOffset = compImgData.Length; Array.Resize(ref compImgData, compImgData.Length + imgData.Length / 4); for (j = 0; j < imgData.Length; j += 4) { Color c = Color.FromArgb(imgData[j], imgData[j + 1], imgData[j + 2]); compImgData[curOffset + j / 4] = (byte)quant.GetPaletteIndex(c); // Floyd-Steinberg dithering if (dither) { // Quantization error int diffErrR = c.R - quant.Palette[compImgData[curOffset + j / 4]].R; int diffErrG = c.G - quant.Palette[compImgData[curOffset + j / 4]].G; int diffErrB = c.B - quant.Palette[compImgData[curOffset + j / 4]].B; // Diffuse error, distribute to neighbour bytes as follows: // X P 7/16 // 3/16 5/16 1/16 if (bmpData.Stride - (j % bmpData.Stride) >= 8) { imgData[j + 4] = truncateByte(imgData[j + 4], (diffErrR * 7) >> 4); imgData[j + 5] = truncateByte(imgData[j + 5], (diffErrG * 7) >> 4); imgData[j + 6] = truncateByte(imgData[j + 6], (diffErrB * 7) >> 4); } else if (j + bmpData.Stride < imgData.Length) { imgData[j + bmpData.Stride] = truncateByte(imgData[j + bmpData.Stride], (diffErrR * 5) >> 4); imgData[j + bmpData.Stride + 1] = truncateByte(imgData[j + bmpData.Stride + 1], (diffErrG * 5) >> 4); imgData[j + bmpData.Stride + 2] = truncateByte(imgData[j + bmpData.Stride + 2], (diffErrB * 5) >> 4); if (bmpData.Stride - (j % bmpData.Stride) >= 8) { imgData[j + bmpData.Stride + 4] = truncateByte(imgData[j + bmpData.Stride + 4], (diffErrR * 1) >> 4); imgData[j + bmpData.Stride + 5] = truncateByte(imgData[j + bmpData.Stride + 5], (diffErrG * 1) >> 4); imgData[j + bmpData.Stride + 6] = truncateByte(imgData[j + bmpData.Stride + 6], (diffErrB * 1) >> 4); } else if (j % bmpData.Stride >= 4) { imgData[j + bmpData.Stride - 4] = truncateByte(imgData[j + bmpData.Stride - 4], (diffErrR * 3) >> 4); imgData[j + bmpData.Stride - 3] = truncateByte(imgData[j + bmpData.Stride - 3], (diffErrG * 3) >> 4); imgData[j + bmpData.Stride - 2] = truncateByte(imgData[j + bmpData.Stride - 2], (diffErrB * 3) >> 4); } } } // Fill alpha array (8 bit) alphaData[j / 4] = imgData[j + 3]; } // Append alpha channels Array.Resize(ref compImgData, compImgData.Length + alphaData.Length); alphaData.CopyTo(compImgData, compImgData.Length - alphaData.Length); // Image data locations header.offsets[i] = (uint)(curOffset + Marshal.SizeOf(typeof(Blp2Header)) + colorPalette.Length); header.lengths[i] = (uint)(compImgData.Length - ((i > 0) ? header.offsets[i - 1] : 0)); } } else if (format == RawFormat.Raw3) { // Uncompressed 32bpp image header.encoding = Encoding.RAW3; for (int i = 0; i < bmps.Count; i++) { // Copy raw image data to byte array BitmapData bmpData = bmps[i].LockBits(new Rectangle(0, 0, bmps[i].Width, bmps[i].Height), ImageLockMode.ReadOnly, bmps[i].PixelFormat); byte[] imgData = new byte[bmpData.Stride * bmps[i].Height]; Marshal.Copy(bmpData.Scan0, imgData, 0, bmpData.Stride * bmps[i].Height); bmps[i].UnlockBits(bmpData); Array.Resize(ref compImgData, compImgData.Length + imgData.Length); imgData.CopyTo(compImgData, compImgData.Length - imgData.Length); // Image data locations header.offsets[i] = (i > 0) ? header.offsets[i - 1] + header.lengths[i - 1] : (uint)(Marshal.SizeOf(typeof(Blp2Header)) + colorPalette.Length); header.lengths[i] = (uint)imgData.Length; } } bw.Write(header.GetBytes()); bw.Write(colorPalette); bw.Write(compImgData); } } else { throw new BlpConversionException("No image data to convert."); } }
/// <summary> /// Saves the currently loaded image to a uncompressed or paletted Blp file. /// </summary> /// <param name="filename">The path to the output file.</param> /// <param name="format">Raw format. See <see cref="RawFormat"/></param> /// <param name="resizeMethod">Method for possibly required resizing. See <see cref="FixSize"/></param> /// <param name="hasMipmaps">Specifies whether mipmaps are generated or not.</param> /// <param name="samplingFactor">(Optional) Sampling factor 1-30. Lower values stand for higher quality to the disadvantage of speed. Does not apply to Raw3 encoding.</param> /// <param name="samplingFactor">(Optional) Specifies whether or not to use dithering. Does not apply to Raw3 encoding.</param> public void Save(string filename, RawFormat format, ResizeMethod resizeMethod, bool hasMipmaps, byte samplingFactor = 10, bool dither = false) { if (bmp != null) { FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Write); using (BinaryWriter bw = new BinaryWriter(fs)) { Blp2Header header = new Blp2Header(DataType.Uncompressed_DirectX, 0, AlphaDepth.Alpha8Bit, AlphaEncoding.RAW8BIT, (byte)(hasMipmaps ? 1 : 0), 0, 0); List<Bitmap> bmps = new List<Bitmap>(); // Add resized image to the output bmps.Add(FixSize(resizeMethod)); header.width = (uint)bmps[0].Width; header.height = (uint)bmps[0].Height; // Add mipmaps if (hasMipmaps) bmps.AddRange(GenerateMipmaps(bmps[0])); byte[] compImgData = { }; byte[] colorPalette = new byte[256 * 4]; if (format == RawFormat.Raw1) { // Uncompressed paletted image if (samplingFactor < 1 || samplingFactor > 30) throw new ArgumentException("Sampling factor must be between 1 and 30."); header.encoding = Encoding.RAW1; // Quantize colors to a 256 color palette NeuQuant quant = new NeuQuant(bmps[0]); quant.Quantize(); // Set color palette int j = 0; foreach (Color c in quant.Palette) { colorPalette[j] = c.R; colorPalette[j + 1] = c.G; colorPalette[j + 2] = c.B; colorPalette[j + 3] = 0; j += 4; } for (int i = 0; i < bmps.Count; i++) { // Copy raw image data to byte array BitmapData bmpData = bmps[i].LockBits(new Rectangle(0, 0, bmps[i].Width, bmps[i].Height), ImageLockMode.ReadOnly, bmps[i].PixelFormat); byte[] imgData = new byte[bmpData.Stride * bmps[i].Height]; Marshal.Copy(bmpData.Scan0, imgData, 0, bmpData.Stride * bmps[i].Height); bmps[i].UnlockBits(bmpData); byte[] alphaData = new byte[imgData.Length / 4]; // Index image colors int curOffset = compImgData.Length; Array.Resize(ref compImgData, compImgData.Length + imgData.Length / 4); for (j = 0; j < imgData.Length; j += 4) { Color c = Color.FromArgb(imgData[j], imgData[j + 1], imgData[j + 2]); compImgData[curOffset + j / 4] = (byte)quant.GetPaletteIndex(c); // Floyd-Steinberg dithering if (dither) { // Quantization error int diffErrR = c.R - quant.Palette[compImgData[curOffset + j / 4]].R; int diffErrG = c.G - quant.Palette[compImgData[curOffset + j / 4]].G; int diffErrB = c.B - quant.Palette[compImgData[curOffset + j / 4]].B; // Diffuse error, distribute to neighbour bytes as follows: // X P 7/16 // 3/16 5/16 1/16 if (bmpData.Stride - (j % bmpData.Stride) >= 8) { imgData[j + 4] = truncateByte(imgData[j + 4], (diffErrR * 7) >> 4); imgData[j + 5] = truncateByte(imgData[j + 5], (diffErrG * 7) >> 4); imgData[j + 6] = truncateByte(imgData[j + 6], (diffErrB * 7) >> 4); } else if (j + bmpData.Stride < imgData.Length) { imgData[j + bmpData.Stride] = truncateByte(imgData[j + bmpData.Stride], (diffErrR * 5) >> 4); imgData[j + bmpData.Stride + 1] = truncateByte(imgData[j + bmpData.Stride + 1], (diffErrG * 5) >> 4); imgData[j + bmpData.Stride + 2] = truncateByte(imgData[j + bmpData.Stride + 2], (diffErrB * 5) >> 4); if (bmpData.Stride - (j % bmpData.Stride) >= 8) { imgData[j + bmpData.Stride + 4] = truncateByte(imgData[j + bmpData.Stride + 4], (diffErrR * 1) >> 4); imgData[j + bmpData.Stride + 5] = truncateByte(imgData[j + bmpData.Stride + 5], (diffErrG * 1) >> 4); imgData[j + bmpData.Stride + 6] = truncateByte(imgData[j + bmpData.Stride + 6], (diffErrB * 1) >> 4); } else if (j % bmpData.Stride >= 4) { imgData[j + bmpData.Stride - 4] = truncateByte(imgData[j + bmpData.Stride - 4], (diffErrR * 3) >> 4); imgData[j + bmpData.Stride - 3] = truncateByte(imgData[j + bmpData.Stride - 3], (diffErrG * 3) >> 4); imgData[j + bmpData.Stride - 2] = truncateByte(imgData[j + bmpData.Stride - 2], (diffErrB * 3) >> 4); } } } // Fill alpha array (8 bit) alphaData[j / 4] = imgData[j + 3]; } // Append alpha channels Array.Resize(ref compImgData, compImgData.Length + alphaData.Length); alphaData.CopyTo(compImgData, compImgData.Length - alphaData.Length); // Image data locations header.offsets[i] = (uint)(curOffset + Marshal.SizeOf(typeof(Blp2Header)) + colorPalette.Length); header.lengths[i] = (uint)(compImgData.Length - ((i > 0) ? header.offsets[i - 1] : 0)); } } else if (format == RawFormat.Raw3) { // Uncompressed 32bpp image header.encoding = Encoding.RAW3; for (int i = 0; i < bmps.Count; i++) { // Copy raw image data to byte array BitmapData bmpData = bmps[i].LockBits(new Rectangle(0, 0, bmps[i].Width, bmps[i].Height), ImageLockMode.ReadOnly, bmps[i].PixelFormat); byte[] imgData = new byte[bmpData.Stride * bmps[i].Height]; Marshal.Copy(bmpData.Scan0, imgData, 0, bmpData.Stride * bmps[i].Height); bmps[i].UnlockBits(bmpData); Array.Resize(ref compImgData, compImgData.Length + imgData.Length); imgData.CopyTo(compImgData, compImgData.Length - imgData.Length); // Image data locations header.offsets[i] = (i > 0) ? header.offsets[i - 1] + header.lengths[i - 1] : (uint)(Marshal.SizeOf(typeof(Blp2Header)) + colorPalette.Length); header.lengths[i] = (uint)imgData.Length; } } bw.Write(header.GetBytes()); bw.Write(colorPalette); bw.Write(compImgData); } } else { throw new BlpConversionException("No image data to convert."); } }