I am trying to save an image as monochrome (black&white, 1 bit-depth) but I'm coming up lost how to do it.
I am starting with a png and converting to a bitmap for printing (it's a thermal printer and only supports black anyway - plus its slow as hell for large images if I try to send them as color/grayscale).
My code so far is dead simple to convert it to a bitmap, but it is retaining the original colour depth.
Image image = Image.FromFile("C:\\test.png");
byte[] bitmapFileData = null;
int bitsPerPixel = 1;
int bitmapDataLength;
using (MemoryStream str = new MemoryStream())
{
image.Save(str, ImageFormat.Bmp);
bitmapFileData = str.ToArray();
}
Here's some code I put together that takes a full colour (24 bits/pixel) image, and converts it to a 1 bit/pixel output bitmap, applying a standard RGB to greyscale conversion, and then using Floyd-Steinberg to convert greyscale to the 1 bit/pixel output.
Note that this should by no means be considered an "ideal" implementation, but it does work. There are a number of improvements that could be applied if you wanted. For example, it copies the entire input image into the data
array, whereas we really only need to keep two lines in memory (the "current" and "next" lines) for accumulating the error data. Despite this, performance seems acceptable.
public static Bitmap ConvertTo1Bit(Bitmap input)
{
var masks = new byte[] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
var output = new Bitmap(input.Width, input.Height, PixelFormat.Format1bppIndexed);
var data = new sbyte[input.Width, input.Height];
var inputData = input.LockBits(new Rectangle(0, 0, input.Width, input.Height), ImageLockMode.ReadOnly, PixelFormat.Format24bppRgb);
try
{
var scanLine = inputData.Scan0;
var line = new byte[inputData.Stride];
for (var y = 0; y < inputData.Height; y++, scanLine += inputData.Stride)
{
Marshal.Copy(scanLine, line, 0, line.Length);
for (var x = 0; x < input.Width; x++)
{
data[x, y] = (sbyte)(64 * (GetGreyLevel(line[x * 3 + 2], line[x * 3 + 1], line[x * 3 + 0]) - 0.5));
}
}
}
finally
{
input.UnlockBits(inputData);
}
var outputData = output.LockBits(new Rectangle(0, 0, output.Width, output.Height), ImageLockMode.WriteOnly, PixelFormat.Format1bppIndexed);
try
{
var scanLine = outputData.Scan0;
for (var y = 0; y < outputData.Height; y++, scanLine += outputData.Stride)
{
var line = new byte[outputData.Stride];
for (var x = 0; x < input.Width; x++)
{
var j = data[x, y] > 0;
if (j) line[x / 8] |= masks[x % 8];
var error = (sbyte)(data[x, y] - (j ? 32 : -32));
if (x < input.Width - 1) data[x + 1, y] += (sbyte)(7 * error / 16);
if (y < input.Height - 1)
{
if (x > 0) data[x - 1, y + 1] += (sbyte)(3 * error / 16);
data[x, y + 1] += (sbyte)(5 * error / 16);
if (x < input.Width - 1) data[x + 1, y + 1] += (sbyte)(1 * error / 16);
}
}
Marshal.Copy(line, 0, scanLine, outputData.Stride);
}
}
finally
{
output.UnlockBits(outputData);
}
return output;
}
public static double GetGreyLevel(byte r, byte g, byte b)
{
return (r * 0.299 + g * 0.587 + b * 0.114) / 255;
}