将位图转换为字节数组时出现不必要的黑条
Unnecessary black bar when convert a Bitmap to Byte Array
我写了一个简单的程序来将单色位图数据转换为字节数组,然后将其反转 (0->1, 1->0) 以便将其作为 ZPL 发送到标签打印机。
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());
但是画了一个原本没有的不必要的黑条!
(由 Line 图片提供)
我一开始以为可能是因为我分配给 BitmapData 的矩形太大所以包含了一些“未使用”的区域,但显然我错了,07FFFFs 仍然在那里!
我什至试图用空字符串替换它来破解它!
但这是错误的,因为它弄乱了 ZPL 输出!
当然我可以只存储替换的字符串并重新计算 ^GFA
命令的长度,但是如果图片“恰好”本身有一些 07FFFF
怎么办!?我这样做基本上把图片本身搞砸了!
07FFFF
被我的代码“反转”了,所以它在原始字节数组中是 F80000
,所以我认为它可能类似于位图中的“\n”数据告诉图片“从新的一行继续绘制”!
而且我在网上找不到任何解释为什么位图文件中有 F80000
的内容。
拍照都挺好的,怎么能“去掉”呢!?
我“只”想要“图片本身”的数据。
有没有好心人帮帮我!?
非常感谢!
原因
Stride
属性 以字节为单位给出图像每条扫描线的长度,向上舍入下一个 4 字节边界。如果图像中的一行没有填满整个扫描线,则会用 0 填充它。您的代码处理了此填充数据并将其包含在 ^GFA
命令中,从而在右侧产生了黑条。如果不反转图像,您将不会注意到问题。
您需要反转图像,因为每个像素的值不仅
黑与白。它是双色 table 的索引。颜色 table 中的每个条目定义了 RGB 中像素的实际颜色。在你的情况下 Color[0]=black
和 Color[1]=white
.
使用 Format1bppIndexed
的像素格式,如果图像宽度不是 8 的倍数,您将获得额外的复杂性。然后在图像中每行的最后使用的字节中有填充位。
我们以你的图片为例。
您的图像宽度为 173。每行至少需要 22 (=ceil(173 / 8)) 字节来存储一行的数据。在最后一个字节中,只有前 5 位 (=173 % 8) 有真正的像素数据。
22 以上的 4 的下一个倍数是 24。所以每一行占用 24 个字节的内存。额外的 2 个字节是用零填充的填充字节。
ZPL 命令^GFA
格式:^GFa,b,c,d,data
其中 a、b、c、d 和数据是命令的参数。
- a:
A
用于 ASCII-HEX (Base16),B
二进制
- b:二进制字节数;如果使用 ASCII,b 必须等于 c
- c:图形字段数;包含图像的字节数(宽度[以字节为单位]*高度)
- d:每行字节数
- data:指定格式的单色图像数据
据我了解该命令的文档,它只支持8的倍数的图像宽度,因为您只能指定row/line的字节数。如果源图像没有正确的宽度,您可以扩展图像,其中所有填充位设置为 0/white/inactive.
解决方案
我已经实现了两种方法来解决这个问题。第一个使用适当的 ^GFA
命令读取单色位图和 returns 一个 StringBuilder
对象。
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
将字节数组转换为 ^GFA
命令的图像数据。
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();
}
}
我写了一个简单的程序来将单色位图数据转换为字节数组,然后将其反转 (0->1, 1->0) 以便将其作为 ZPL 发送到标签打印机。
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());
但是画了一个原本没有的不必要的黑条!
(由 Line 图片提供)
我一开始以为可能是因为我分配给 BitmapData 的矩形太大所以包含了一些“未使用”的区域,但显然我错了,07FFFFs 仍然在那里!
我什至试图用空字符串替换它来破解它!
但这是错误的,因为它弄乱了 ZPL 输出!
当然我可以只存储替换的字符串并重新计算 ^GFA
命令的长度,但是如果图片“恰好”本身有一些 07FFFF
怎么办!?我这样做基本上把图片本身搞砸了!
07FFFF
被我的代码“反转”了,所以它在原始字节数组中是 F80000
,所以我认为它可能类似于位图中的“\n”数据告诉图片“从新的一行继续绘制”!
而且我在网上找不到任何解释为什么位图文件中有 F80000
的内容。
拍照都挺好的,怎么能“去掉”呢!?
我“只”想要“图片本身”的数据。
有没有好心人帮帮我!?
非常感谢!
原因
Stride
属性 以字节为单位给出图像每条扫描线的长度,向上舍入下一个 4 字节边界。如果图像中的一行没有填满整个扫描线,则会用 0 填充它。您的代码处理了此填充数据并将其包含在 ^GFA
命令中,从而在右侧产生了黑条。如果不反转图像,您将不会注意到问题。
您需要反转图像,因为每个像素的值不仅
黑与白。它是双色 table 的索引。颜色 table 中的每个条目定义了 RGB 中像素的实际颜色。在你的情况下 Color[0]=black
和 Color[1]=white
.
使用 Format1bppIndexed
的像素格式,如果图像宽度不是 8 的倍数,您将获得额外的复杂性。然后在图像中每行的最后使用的字节中有填充位。
我们以你的图片为例。 您的图像宽度为 173。每行至少需要 22 (=ceil(173 / 8)) 字节来存储一行的数据。在最后一个字节中,只有前 5 位 (=173 % 8) 有真正的像素数据。 22 以上的 4 的下一个倍数是 24。所以每一行占用 24 个字节的内存。额外的 2 个字节是用零填充的填充字节。
ZPL 命令^GFA
格式:^GFa,b,c,d,data
其中 a、b、c、d 和数据是命令的参数。
- a:
A
用于 ASCII-HEX (Base16),B
二进制 - b:二进制字节数;如果使用 ASCII,b 必须等于 c
- c:图形字段数;包含图像的字节数(宽度[以字节为单位]*高度)
- d:每行字节数
- data:指定格式的单色图像数据
据我了解该命令的文档,它只支持8的倍数的图像宽度,因为您只能指定row/line的字节数。如果源图像没有正确的宽度,您可以扩展图像,其中所有填充位设置为 0/white/inactive.
解决方案
我已经实现了两种方法来解决这个问题。第一个使用适当的 ^GFA
命令读取单色位图和 returns 一个 StringBuilder
对象。
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
将字节数组转换为 ^GFA
命令的图像数据。
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();
}
}