I have this class that worked for 8 bpp bitmaps to pcx, but I am having trouble making it work for 1 bpp.
The image starts off like this:
But the resulting PCX is black after line 27, as seen in IrfanView:
Can anyone help me spot the glaring error?
Usage by the way is Pcx.SavePCX("AOut.pcx", new Bitmap("A.bmp"));
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
namespace Imaging
{
public sealed class Pcx
{
private static void WriteWord(Stream stream, int data)
{
stream.WriteByte((byte) (data & 0xFF));
stream.WriteByte((byte) ((data >> 8) & 0xFF));
}
public static void SavePCX(Stream pcxStream, Bitmap bmp)
{
if (bmp.PixelFormat != PixelFormat.Format1bppIndexed)
{
throw new Exception("Can only PCX bitmaps that are 1bpp indexed");
}
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
{
//header
pcxStream.WriteByte(10); // char manufacturer;
pcxStream.WriteByte(5); //char version;
pcxStream.WriteByte(1); //char encoding;
pcxStream.WriteByte(1); // char bpp;
pcxStream.WriteByte(0);
pcxStream.WriteByte(0); //char xmin[2];
pcxStream.WriteByte(0);
pcxStream.WriteByte(0); //char ymin[2];
WriteWord(pcxStream, bmp.Width - 1); // char xmax[2];
WriteWord(pcxStream, bmp.Height - 1); //char ymax[2];
WriteWord(pcxStream, 72); //word(pcx->hdpi, 72);
WriteWord(pcxStream, 72); // word(pcx->vdpi, 72);
for (var i = 0; i < 16*3; i++) //4bpp palette
{
pcxStream.WriteByte(0);
}
pcxStream.WriteByte(0); // pcx->res = 0;
pcxStream.WriteByte(1); // pcx->nplanes = 1;
WriteWord(pcxStream, bmp.Width); // word(pcx->bytesperline, width / 2);
WriteWord(pcxStream, 0); //word(pcx->palletteinfo, 0);
WriteWord(pcxStream, 0); //word(pcx->hscrn, 0);
WriteWord(pcxStream, 0); //word(pcx->vscrn, 0);
for (var i = 0; i < 54; i++) //memset(pcx->filler, 0, 54);
{
pcxStream.WriteByte(0);
}
} //end of header
{
//read all bytes to an array
var baseLine = data.Scan0;
// Declare an array to hold the bytes of the bitmap.
var byteLength = bmp.Width*bmp.Height;
var bytes = new byte[byteLength];
// Copy the RGB values into the array.
for (var y = 0; y < data.Height; y++)
{
var lineOffset = y*data.Stride;
Debug.WriteLine("Y={0}, Offset={1}", y, lineOffset);
for (var x = 0; x < data.Width; x++)
{
bytes[y*bmp.Width + x] = Marshal.ReadByte(baseLine, lineOffset + x);
}
}
var baseIdx = 0;
var end = byteLength;
var run = 0;
var ldata = -1;
byte ld;
while (baseIdx < end)
{
//if it matches, increase the run by 1 up to max of 63
if ((bytes[baseIdx] == ldata) && (run < 63)) run++;
else
{
//write data
if (run != 0) //not first run
{
ld = (byte) ldata;
if ((run > 1) || (ld >= 0xC0)) pcxStream.WriteByte((byte) (0xC0 | run));
pcxStream.WriteByte(ld);
}
run = 1;
}
ldata = bytes[baseIdx];
baseIdx++;
}
ld = (byte) ((ldata >> 4) | (ldata << 4));
if ((run > 1) || (ld >= 0xC0)) pcxStream.WriteByte((byte) (0xC0 | run));
pcxStream.WriteByte(ld);
}
}
finally
{
bmp.UnlockBits(data);
}
}
public static void SavePCX(string fileName, Bitmap bbp1Bmp)
{
using (var fstest = new FileStream(fileName, FileMode.Create, FileAccess.Write))
{
SavePCX(fstest, bbp1Bmp);
}
}
}
}
Solved it by using data.Stride
for the bytes per line, and reading in the bytes from the 1bpp using stride as the width, not width.
public static void SavePCX(Stream pcxStream, Bitmap bmp)
{
if (bmp.PixelFormat != PixelFormat.Format1bppIndexed)
{
throw new Exception("Can only PCX bitmaps that are 1bpp indexed");
}
var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
{
//header
pcxStream.WriteByte(10); // char manufacturer;
pcxStream.WriteByte(5); //char version;
pcxStream.WriteByte(1); //char encoding;
pcxStream.WriteByte(1); // char bpp;
pcxStream.WriteByte(0);
pcxStream.WriteByte(0); //char xmin[2];
pcxStream.WriteByte(0);
pcxStream.WriteByte(0); //char ymin[2];
WriteWord(pcxStream, bmp.Width - 1); // char xmax[2];
WriteWord(pcxStream, bmp.Height - 1); //char ymax[2];
WriteWord(pcxStream, 72); //word(pcx->hdpi, 72);
WriteWord(pcxStream, 72); // word(pcx->vdpi, 72);
for (var i = 0; i < 16*3; i++) //4bpp palette
{
pcxStream.WriteByte(0);
}
pcxStream.WriteByte(0); // pcx->res = 0;
pcxStream.WriteByte(1); // pcx->nplanes = 1;
WriteWord(pcxStream, data.Stride); // word(pcx->bytesperline, width / 2);
WriteWord(pcxStream, 0); //word(pcx->palletteinfo, 0);
WriteWord(pcxStream, 0); //word(pcx->hscrn, 0);
WriteWord(pcxStream, 0); //word(pcx->vscrn, 0);
for (var i = 0; i < 54; i++) //memset(pcx->filler, 0, 54);
{
pcxStream.WriteByte(0);
}
} //end of header
{
//read all bytes to an array
var baseLine = data.Scan0;
// Declare an array to hold the bytes of the bitmap.
var byteLength = data.Stride*data.Height;
var bytes = new byte[byteLength];
// Copy the RGB values into the array.
for (var y = 0; y < data.Height; y++)
{
var lineOffset = y*data.Stride;
Debug.WriteLine("Y={0}, Offset={1}", y, lineOffset);
for (var x = 0; x < data.Stride; x++)
{
bytes[y*data.Stride + x] = Marshal.ReadByte(baseLine, lineOffset + x);
}
}
var baseIdx = 0;
var end = byteLength;
var run = 0;
var ldata = -1;
byte ld;
while (baseIdx < end)
{
//if it matches, increase the run by 1 up to max of 63
if ((bytes[baseIdx] == ldata) && (run < 63)) run++;
else
{
//write data
if (run != 0) //not first run
{
ld = (byte) ldata;
if ((run > 1) || (ld >= 0xC0)) pcxStream.WriteByte((byte) (0xC0 | run));
pcxStream.WriteByte(ld);
}
run = 1;
}
ldata = bytes[baseIdx];
baseIdx++;
}
ld = (byte) ((ldata >> 4) | (ldata << 4));
if ((run > 1) || (ld >= 0xC0)) pcxStream.WriteByte((byte) (0xC0 | run));
pcxStream.WriteByte(ld);
}
}
finally
{
bmp.UnlockBits(data);
}
}