将位图转换为字节数组时出现不必要的黑条

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]=blackColor[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();
    }
}