Exemplo n.º 1
0
        internal static Blp2Header FromBinaryReader(BinaryReader br)
        {
            byte[]     buff   = br.ReadBytes(Marshal.SizeOf(typeof(Blp2Header)));
            GCHandle   handle = GCHandle.Alloc(buff, GCHandleType.Pinned);
            Blp2Header ret    = (Blp2Header)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(Blp2Header));

            handle.Free();
            return(ret);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Creates a Blp2 object from the specified data stream.
        /// </summary>
        /// <param name="stream">A stream that contains the data of a Blp2 or System.Drawing.Image picture.</param>
        /// <returns>Blp2 object containing the image of the stream.</returns>
        /// <exception cref=""></exception>
        public static Blp2 FromStream(Stream stream)
        {
            using (BinaryReader br = new BinaryReader(stream)) {
                Blp2Header header = Blp2Header.FromBinaryReader(br);

                Blp2 ret = new Blp2();

                if (new string(header.fourCC) == "BLP2")
                {
                    ret.bmp = new Bitmap((int)header.width, (int)header.height, PixelFormat.Format32bppArgb);

                    BitmapData bmpData = ret.bmp.LockBits(new Rectangle(0, 0, ret.bmp.Width, ret.bmp.Height), ImageLockMode.ReadWrite, ret.bmp.PixelFormat);

                    byte[] imgData = new byte[header.lengths[0]];
                    br.BaseStream.Seek(header.offsets[0], SeekOrigin.Begin);
                    imgData = br.ReadBytes(imgData.Length);

                    // BLP2 conversion
                    if (header.type == DataType.Uncompressed_DirectX && header.encoding == Encoding.RAW1 &&
                        (header.alphaDepth == AlphaDepth.NoAlpha || header.alphaDepth == AlphaDepth.Alpha1Bit || header.alphaDepth == AlphaDepth.Alpha8Bit))
                    {
                        // Uncompressed paletted image
                        ret.format = FileFormat.Raw;

                        // Load palette (4-byte BGRA color values)
                        br.BaseStream.Seek(Marshal.SizeOf(typeof(Blp2Header)), SeekOrigin.Begin);
                        byte[]  colorData = br.ReadBytes(256 * 4);
                        Color[] c         = new Color[256];
                        for (int i = 0; i < 1024; i += 4)
                        {
                            c[i / 4] = Color.FromArgb(255, colorData[i], colorData[i + 1], colorData[i + 2]);
                        }

                        // Read indexed bitmap
                        byte[] decompImgData = new byte[header.width * header.height * 4];
                        for (int i = 0; i < (header.width * header.height); i++)
                        {
                            decompImgData[i * 4]     = c[imgData[i]].R;
                            decompImgData[i * 4 + 1] = c[imgData[i]].G;
                            decompImgData[i * 4 + 2] = c[imgData[i]].B;
                            decompImgData[i * 4 + 3] = 255;
                        }

                        // Apply alpha channels
                        if (header.alphaDepth == AlphaDepth.Alpha8Bit)
                        {
                            // 8-bits alpha
                            br.BaseStream.Seek(header.offsets[0] + header.width * header.height, SeekOrigin.Begin);
                            byte[] alphaData = br.ReadBytes((int)(header.width * header.height));
                            for (int i = 0; i < (header.width * header.height); i++)
                            {
                                decompImgData[i * 4 + 3] = alphaData[i];
                            }
                        }
                        else if (header.alphaDepth == AlphaDepth.Alpha1Bit)
                        {
                            // 1-bits alpha
                            br.BaseStream.Seek(header.offsets[0] + (header.width * header.height), SeekOrigin.Begin);
                            byte[] alphaData = br.ReadBytes((int)(header.width * header.height / 8));
                            for (int i = 0; i < (header.width * header.height / 8); i++)
                            {
                                for (int j = 0; j < 8; j++)
                                {
                                    decompImgData[i * 32 + ((j + 1) * 4) - 1] = (byte)((alphaData[i] & 1 << j) == 1 << j ? 255 : 0);
                                }
                            }
                        }

                        Marshal.Copy(decompImgData, 0, bmpData.Scan0, decompImgData.Length);
                    }
                    else if (header.type == DataType.Uncompressed_DirectX && header.encoding == Encoding.RAW3 &&

                             (header.alphaDepth == AlphaDepth.NoAlpha || header.alphaDepth == AlphaDepth.Alpha1Bit || header.alphaDepth == AlphaDepth.Alpha8Bit))
                    {
                        // Uncompressed BGRA image
                        ret.format = FileFormat.Raw;
                        Marshal.Copy(imgData, 0, bmpData.Scan0, imgData.Length);
                    }
                    else if (header.type == DataType.Uncompressed_DirectX && header.encoding == Encoding.DXT &&
                             (header.alphaEncoding == AlphaEncoding.DXT1 || header.alphaEncoding == AlphaEncoding.DXT3 || header.alphaEncoding == AlphaEncoding.DXT5))
                    {
                        // DXT1/3/5
                        ret.format = FileFormat.Dxt;

                        byte[] decompImgData = new byte[header.width * header.height * 4];

                        LibSquish.DxtFormat flags = 0;
                        switch (header.alphaEncoding)
                        {
                        case AlphaEncoding.DXT1:
                            flags = LibSquish.DxtFormat.Dxt1;
                            break;

                        case AlphaEncoding.DXT3:
                            flags = LibSquish.DxtFormat.Dxt3;
                            break;

                        case AlphaEncoding.DXT5:
                            flags = LibSquish.DxtFormat.Dxt5;
                            break;
                        }

                        LibSquish.DecompressImage(decompImgData, header.width, header.height, imgData, (int)flags);
                        RemapRGBA(ref decompImgData);
                        Marshal.Copy(decompImgData, 0, bmpData.Scan0, decompImgData.Length);
                    }
                    else if (header.type == DataType.JPEG)
                    {
                        ret.format = FileFormat.Jpeg;
                        ret.bmp.UnlockBits(bmpData);
                        throw new NotImplementedException();
                    }
                    else
                    {
                        ret.bmp.UnlockBits(bmpData);
                        throw new BlpFormatException("The given BLP2 format is not supported.");
                    }

                    ret.bmp.UnlockBits(bmpData);
                }
                else
                {
                    try {
                        // Let System.Drawing.Image handle loading
                        ret.bmp    = (Bitmap)Image.FromStream(stream);
                        ret.format = FileFormat.Image;
                    } catch (ArgumentException e) {
                        throw new BlpFormatException("The stream did not contain valid image data.", e.InnerException);
                    }
                }

                ret.header = header;
                return(ret);
            }
        }
Exemplo n.º 3
0
        /// <summary>
        /// Saves the currently loaded image to a Dxt encoded Blp file.
        /// </summary>
        /// <param name="filename">The path to the output file.</param>
        /// <param name="format">Dxt format. See <see cref="LibSquish.DxtFormat"/></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="compression">Compression method used by LibSquish. Affects quality / speed. See <see cref="LibSquish.ColorCompression"/></param>
        public void Save(string filename, LibSquish.DxtFormat format, ResizeMethod resizeMethod, bool hasMipmaps, LibSquish.ColorCompression compression = LibSquish.ColorCompression.ClusterFit)
        {
            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, Encoding.DXT, 0, 0, (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];

                    switch (format)
                    {
                    case LibSquish.DxtFormat.Dxt1:
                        header.alphaEncoding = AlphaEncoding.DXT1;
                        header.alphaDepth    = AlphaDepth.Alpha1Bit;
                        break;

                    case LibSquish.DxtFormat.Dxt3:
                        header.alphaEncoding = AlphaEncoding.DXT3;
                        header.alphaDepth    = AlphaDepth.Alpha8Bit;
                        break;

                    case LibSquish.DxtFormat.Dxt5:
                        header.alphaEncoding = AlphaEncoding.DXT5;
                        header.alphaDepth    = AlphaDepth.Alpha8Bit;
                        break;
                    }

                    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);

                        // Set up conversion flags
                        int flags = (int)format | (int)compression | (int)LibSquish.ColorErrorMetric.Perceptual;

                        // Compress to DXT format
                        RemapRGBA(ref imgData);
                        byte[] buff = new byte[LibSquish.GetStorageRequirements((uint)bmps[i].Width, (uint)bmps[i].Height, flags)];
                        LibSquish.CompressImage(imgData, (uint)bmps[i].Width, (uint)bmps[i].Height, buff, flags);

                        Array.Resize(ref compImgData, compImgData.Length + buff.Length);
                        buff.CopyTo(compImgData, compImgData.Length - buff.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)buff.Length;
                    }

                    bw.Write(header.GetBytes());
                    bw.Write(colorPalette);
                    bw.Write(compImgData);
                }
            }
            else
            {
                throw new BlpConversionException("No image data to convert.");
            }
        }
Exemplo n.º 4
0
        /// <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.");
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Saves the currently loaded image to a Dxt encoded Blp file.
        /// </summary>
        /// <param name="filename">The path to the output file.</param>
        /// <param name="format">Dxt format. See <see cref="LibSquish.DxtFormat"/></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="compression">Compression method used by LibSquish. Affects quality / speed. See <see cref="LibSquish.ColorCompression"/></param>
        public void Save(string filename, LibSquish.DxtFormat format, ResizeMethod resizeMethod, bool hasMipmaps, LibSquish.ColorCompression compression = LibSquish.ColorCompression.ClusterFit)
        {
            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, Encoding.DXT, 0, 0, (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];

                    switch (format) {
                        case LibSquish.DxtFormat.Dxt1:
                            header.alphaEncoding = AlphaEncoding.DXT1;
                            header.alphaDepth = AlphaDepth.Alpha1Bit;
                            break;
                        case LibSquish.DxtFormat.Dxt3:
                            header.alphaEncoding = AlphaEncoding.DXT3;
                            header.alphaDepth = AlphaDepth.Alpha8Bit;
                            break;
                        case LibSquish.DxtFormat.Dxt5:
                            header.alphaEncoding = AlphaEncoding.DXT5;
                            header.alphaDepth = AlphaDepth.Alpha8Bit;
                            break;
                    }

                    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);

                        // Set up conversion flags
                        int flags = (int)format | (int)compression | (int)LibSquish.ColorErrorMetric.Perceptual;

                        // Compress to DXT format
                        RemapRGBA(ref imgData);
                        byte[] buff = new byte[LibSquish.GetStorageRequirements((uint)bmps[i].Width, (uint)bmps[i].Height, flags)];
                        LibSquish.CompressImage(imgData, (uint)bmps[i].Width, (uint)bmps[i].Height, buff, flags);

                        Array.Resize(ref compImgData, compImgData.Length + buff.Length);
                        buff.CopyTo(compImgData, compImgData.Length - buff.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)buff.Length;
                    }

                    bw.Write(header.GetBytes());
                    bw.Write(colorPalette);
                    bw.Write(compImgData);

                }

            } else {
                throw new BlpConversionException("No image data to convert.");
            }
        }
Exemplo n.º 6
0
        /// <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.");
            }
        }