using System; using System.Drawing; using System.Drawing.Imaging; using System.IO; using Marshal = System.Runtime.InteropServices.Marshal; namespace Kawa.Tools.Bitmap { /// /// Abstracts away the retrieval of an image's raw pixel and palette data, and allows reading ZSoft Paintbrush PCX files into Bitmap objects. /// public static class BitmapDataTools { /// /// Extracts the raw pixel data and optionally palette from a 256-color 320 by 200 pixel Bitmap. /// /// The name of a bitmap file in any of the formats supported by Bitmap, or ZSoft Paintbrush PCX. /// An array of 768 bytes to recieve the color palette of the Bitmap, or null. /// An array of 64000 bytes to recieve the pixel data of the Bitmap. public static void GetPixels(string from, ref byte[] palette, ref byte[] data) { if (from.EndsWith(".pcx", StringComparison.InvariantCultureIgnoreCase)) GetPixels(ReadPCX(from), ref palette, ref data); else GetPixels(new System.Drawing.Bitmap(from), ref palette, ref data); } /// /// Extracts the raw pixel data and optionally palette from a 256-color 320 by 200 pixel Bitmap. /// /// A Bitmap object. /// An array of 768 bytes to recieve the color palette of the Bitmap, or null. /// An array of 64000 bytes to recieve the pixel data of the Bitmap. public static void GetPixels(System.Drawing.Bitmap from, ref byte[] palette, ref byte[] data) { if (data == null) throw new Exception("Need a target array."); if (data.Length != 320 * 200) throw new Exception("Target array isn't big enough."); if (palette != null && palette.Length != 256 * 3) throw new Exception("Palette array isn't big enough."); var victim = from; if (victim.PixelFormat != PixelFormat.Format8bppIndexed) throw new Exception("Input images can only be in 256 color format."); if (victim.Width != 320 || victim.Height != 200) throw new Exception("Input images can only be 320 by 200 pixels in size."); if (palette != null) { for (var i = 0; i < victim.Palette.Entries.Length; i++) { var color = victim.Palette.Entries[i]; palette[i * 3 + 0] = color.R; palette[i * 3 + 1] = color.G; palette[i * 3 + 2] = color.B; } } var bitmapData = victim.LockBits(new Rectangle(0, 0, victim.Width, victim.Height), ImageLockMode.ReadOnly, victim.PixelFormat); Marshal.Copy(bitmapData.Scan0, data, 0, 320 * 200); victim.UnlockBits(bitmapData); } /// /// Reads a ZSoft Paintbrush PCX file into a Bitmap. Only supports 256-color version 5 files with a single plane. /// /// The name of a PCX file. /// A Bitmap object representation of the PCX file. public static System.Drawing.Bitmap ReadPCX(string from) { using (var stream = new BinaryReader(File.Open(from, FileMode.Open))) { if (stream.ReadByte() != 10) throw new Exception("Filename says PCX, but first byte doesn't."); if (stream.ReadByte() != 5) throw new Exception("Wrong PCX version."); if (stream.ReadByte() != 1) throw new Exception("Wrong PCX encoding."); if (stream.ReadByte() != 8) throw new Exception("Input images can only be in 256 color format."); var winLeft = stream.ReadInt16(); var winTop = stream.ReadInt16(); var winRight = stream.ReadInt16(); var winBottom = stream.ReadInt16(); var width = winRight + 1; var height = winBottom + 1; var size = width * height; if (width % 8 != 0) throw new Exception("Can't work with widths that aren't divisible by 8."); stream.ReadInt16(); //HRes stream.ReadInt16(); //VRes stream.ReadBytes(48); //ColorMap stream.ReadByte(); //Reserved if (stream.ReadByte() != 1) throw new Exception("Input images can only have the one plane."); var stride = stream.ReadInt16(); if (stream.ReadByte() != 1) throw new Exception("Unexpected palette interpretation."); var bitmap = new System.Drawing.Bitmap(width, height, PixelFormat.Format8bppIndexed); var palette = bitmap.Palette; stream.BaseStream.Seek(-769, SeekOrigin.End); if (stream.ReadByte() != 12) throw new Exception("Unexpected byte as palette marker."); for (var i = 0; i < 256; i++) palette.Entries[i] = Color.FromArgb(255, stream.ReadByte(), stream.ReadByte(), stream.ReadByte()); bitmap.Palette = palette; stream.BaseStream.Seek(128, SeekOrigin.Begin); var data = new byte[size]; for (var i = 0; i < size; i++) { var b = stream.ReadByte(); if ((b & 0xC0) == 0xC0) { var length = b & 0x3F; b = stream.ReadByte(); for (var j = 0; j < length; j++) data[i + j] = b; i += length - 1; } else data[i] = b; } var bitmapData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed); Marshal.Copy(data, 0, bitmapData.Scan0, size); bitmap.UnlockBits(bitmapData); return bitmap; } } } }