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