Reputation: 440
I wrote a easy program to convert a monochrome bitmap data to a byte array, and then reverse it (0->1, 1->0) in order to send it as a ZPL to a label printer.
Bitmap bmp = (Bitmap)pictureBox1.Image;
Rectangle rectangle = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bd = bmp.LockBits(rectangle, ImageLockMode.ReadOnly, bmp.PixelFormat);
IntPtr ptr = bd.Scan0;
byte[] b = new byte[Math.Abs(bd.Stride) * bmp.Height];
Marshal.Copy(ptr, b, 0, b.Length);
bmp.UnlockBits(bd);
StringBuilder sb = new StringBuilder();
foreach (byte i in b)
{
sb.Append((255 - i).ToString("X2"));
}
textBox1.Text = string.Format(@"^XA^FO100,0^GFA,{0},{0},{1},{2}^FS^XZ", b.Length, Math.Abs(bd.Stride), sb.ToString());
But it draws an unnecessary black bar that was originally not there!
I first thought it might be because the rectangle I assigned to the BitmapData was too big so there included some "unused" areas, but apparently I was wrong and the 07FFFFs was still there!
I even tried to just replace it with empty string for the hack of it!
But it was wrong because it mess up the ZPL output!
Of course I could just store the replaced string and recalculate the length for the ^GFA
command, but what if the picture "just so happen" to have some 07FFFF
s in itself!? I essentially mess up the picture itself by doing so!
The 07FFFF
was "reversed" by my code, so it was F80000
in the original byte array, so I figure it might be some thing similar to "\n" in the bitmap data to tell the picture to "keep drawing from the new line"!
And I can't find anything online explaining why there are F80000
s in the bitmap files.
It's all good for the picture, but how could I "get rid of it"!?
I "only" want the data that was "the picture itself".
Could somebody please be so kind and help me out!?
Much appreciated!
Upvotes: 0
Views: 797
Reputation: 821
The Stride
property gives you the length of each scanline of the image in bytes, rounded up the next 4-byte boundary. And if a line in the image does not fill the whole scanline, it is padded with 0. Your code processed and included this padding data in the ^GFA
command producing the black bars on the right side. If you don't invert the image, you won't notice the problem.
You need to invert the image because the value of each pixel is not only
black and white. It is an index to a two-color table. And each entry in the color table defines the actual color of the pixel in RGB. In your case Color[0]=black
and Color[1]=white
.
With the pixel format of Format1bppIndexed
you get additional complexity if the image width is not a multiple of 8. Then there are padding bits in the last used byte of each line in the image.
Let's take your image as an example. You have an image width of 173. Each line need at least 22 (=ceil(173 / 8)) bytes to store the data of one line. In the last byte, only the first 5 bits (=173 % 8) have real pixel data. The next multiple of 4 above 22 is 24. So each line takes 24 bytes of memory. The additional 2 bytes are padding bytes filled with zeros.
^GFA
The Format: ^GFa,b,c,d,data
where a, b, c, d and data are parameters to the command.
A
for ASCII-HEX (Base16), B
binaryAs far as I understand the documentation of the command, it only supports image widths that are a multiple of 8, because you can only specify the number of bytes for a row/line. If the source image does not have the correct width, you can expand the image where all the padding bits set to 0/white/inactive.
I have implemented two methods to solve the problem. The first one reads a monochrome bitmap and returns a StringBuilder
object with the appropriate ^GFA
command.
private static StringBuilder ZPLCommandFromMonochromeFile(string pFileName)
{
var sb = new StringBuilder();
using (var bmp = new Bitmap(pFileName))
{
if (bmp.PixelFormat != PixelFormat.Format1bppIndexed)
throw new InvalidOperationException($"We only suppoert monochrome images, file {pFileName} is invalid.");
var rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
byte[] data = null;
int stride = 0;
var bd = bmp.LockBits(rect, ImageLockMode.ReadOnly, bmp.PixelFormat);
try
{
stride = Math.Abs(bd.Stride);
data = new byte[stride * bmp.Height];
System.Runtime.InteropServices.Marshal.Copy(bd.Scan0, data, 0, data.Length);
}
finally
{
bmp.UnlockBits(bd);
}
ZPLCommandFromArray(sb, data, stride, bmp.Width, bmp.Height);
}
return sb;
}
ZPLCommandFromArray
converts the byte array to the image data for the ^GFA
command.
private static void ZPLCommandFromArray(StringBuilder dest, byte[] data, int stride, int width, int height)
{
// calc the length of the destination line in bytes
// this is the nr of bytes in a line where all 8 bits are used for the picture
var len8 = width / 8;
// this is the number of trailing pixels for a line if the width is not a multiple of 8
var bits = width % 8;
// the minimum nr of bytes needed for a line
var len = len8 + (bits > 0 ? 1 : 0);
// the number of bits we have to mask out if the width is not a multiple
// of 8. e.g if the width is 19, we only may change 3 bits of the last byte
var mask = (byte)(~(0xFF >> bits));
// in the output graphics, the width will always be a multiple of 8, because
// in the ZPL command ^GFA you can only specify the width of the imagen in
// number bytes.
dest.AppendFormat("^GFA,{0},{1},{2},", len * height, len * height, len);
dest.AppendLine();
for (int y = 0; y < height; y++)
{
for (int x = 0; x < len8; x++)
dest.AppendFormat("{0:X2}", (byte)~data[y * stride + x]);
if (bits > 0)
dest.AppendFormat("{0:X2}", (byte)((~data[y * stride + len8]) & mask));
dest.AppendLine();
}
}
Upvotes: 3