将PNG图像打印到斑马网络打印机

我试图找到一种将图像打印到斑马并且遇到很多麻烦的方法。

根据文件:

第一种编码称为B64,使用MIME Base64方案对数据进行编码。 Base64用于编码电子邮件地址…
Base64对该字节进行6位编码,对未封闭数据的扩展为33%。
第二种编码称为Z64,首先使用LZ77算法压缩数据以减小其大小。 (该算法由PKZIP使用,与PNG图形格式是一体的。)
然后使用如上所述的MIME Base64方案对压缩数据进行编码。
将根据Base64编码的数据计算CRC。

但它没有更多的信息。

基本上我正在尝试编码

private byte[] GetItemFromPath(string filepath) { using (MemoryStream ms = new MemoryStream()) { using (Image img = Image.FromFile(filepath)) { img.Save(ms, ImageFormat.Png); return ms.ToArray(); } } } 

然后尝试使用以下内容进行打印:

 var initialArray = GetItemFromPath("C:\\RED.png"); string converted = Convert.ToBase64String(b); PrintThis(string.Format(@"~DYRED.PNG,P,P,{1},0,:B64: {0} ^XA ^F0200,200^XGRED.PNG,1,1^FS ^XZ", converted .ToString(), initialArray.Length)); 

根据它的声音,B64或Z64都被接受。

我尝试了一些变体,以及几种生成CRC和计算’大小’的方法。 但似乎没有工作,并且图形下载到打印机总是被中止。

有没有人设法完成这样的事情? 或者知道我哪里出错了?

我得到这个答案的所有功劳都来自LabView论坛用户Raydur。 他发布了一个可以在LabView中打开的LabView解决方案,用于发送图像。 我个人没有用我的打印机运行它,我只是用它来找出正确的图像代码,所以我可以在我的代码中复制它。 我遗漏的重要事情是填充我的hex代码。 例如:1A很好,但是如果你只有A,你需要在它前面填一个0来发送0A。 您发送的ZPL中文件的大小也是字节数组的原始大小,而不是数据的最终字符串表示forms。

我已经搜索过很多很多很多论坛和Stackoverflow的post,试图解决这个问题,因为它似乎很简单。 我已尝试在其他地方发布的每一个解决方案,但我真的只想打印.PNG,因为我的打印机手册(Mobile QLN320)支持它内置。它说要么用Base64或Hexadecimal发送,我试过两者都无济于事。 对于任何想要做Base64的人,我在一本较旧的手册中发现,你需要为你发送的每个数据包手动计算CRC码,所以我选择了更简单的hex路由。 所以这是我开始工作的代码!

  string ipAddress = "192.168.1.30"; int port = 6101; string zplImageData = string.Empty; //Make sure no transparency exists. I had some trouble with this. This PNG has a white background string filePath = @"C:\Users\Path\To\Logo.png"; byte[] binaryData = System.IO.File.ReadAllBytes(filePath); foreach (Byte b in binaryData) { string hexRep = String.Format("{0:X}", b); if (hexRep.Length == 1) hexRep = "0" + hexRep; zplImageData += hexRep; } string zplToSend = "^XA" + "^MNN" + "^LL500" + "~DYE:LOGO,P,P," + binaryData.Length + ",," + zplImageData+"^XZ"; string printImage = "^XA^FO115,50^IME:LOGO.PNG^FS^XZ"; try { // Open connection System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(); client.Connect(ipAddress, port); // Write ZPL String to connection System.IO.StreamWriter writer = new System.IO.StreamWriter(client.GetStream(),Encoding.UTF8); writer.Write(zplToSend); writer.Flush(); writer.Write(printImage); writer.Flush(); // Close Connection writer.Close(); client.Close(); } catch (Exception ex) { // Catch Exception } 

ZPL II编程指南记录了~DG命令和GRF格式(第124页)以下载图像。 第二卷添加了有关可选压缩格式的详细信息(第52页)。

首先,您必须将图像转换为1bpp双层图像,然后将其转换为hex编码的字符串。 您可以进一步压缩图像以减少传输时间。 然后,您可以使用^ID命令打印图像。

虽然在~DY命令中对PNG图像有固有的支持,但它的记录很少,似乎不适用于某些型号的打印机。 ZB64格式基本上没有记录,尝试从Zebra支持获取更多信息也没有结果。 如果你的心脏设置在ZB64上,你可以使用基于Java的Zebralink SDK (查看ImagePrintDemo.javacom.zebra.sdk.printer.internal.GraphicsConversionUtilZpl.sendImageToStream )。

获得命令数据后,如果打印机有打印服务器,可以通过TCP / IP发送,也可以通过RAW格式写入打印机发送。

下面的代码打印出5 kB PNG作为13 kB压缩GRF(60 kB未压缩):

 class Program { static unsafe void Main(string[] args) { var baseStream = new MemoryStream(); var tw = new StreamWriter(baseStream, Encoding.UTF8); using (var bmpSrc = new Bitmap(Image.FromFile(@"label.png"))) { tw.WriteLine(ZplImage.GetGrfStoreCommand("R:LBLRA2.GRF", bmpSrc)); } tw.WriteLine(ZplImage.GetGrfPrintCommand("R:LBLRA2.GRF")); tw.WriteLine(ZplImage.GetGrfDeleteCommand("R:LBLRA2.GRF")); tw.Flush(); baseStream.Position = 0; var gdipj = new GdiPrintJob("ZEBRA S4M-200dpi ZPL", GdiPrintJobDataType.Raw, "Raw print", null); gdipj.WritePage(baseStream); gdipj.CompleteJob(); } } class ZplImage { public static string GetGrfStoreCommand(string filename, Bitmap bmpSource) { if (bmpSource == null) { throw new ArgumentNullException("bmpSource"); } validateFilename(filename); var dim = new Rectangle(Point.Empty, bmpSource.Size); var stride = ((dim.Width + 7) / 8); var bytes = stride * dim.Height; using (var bmpCompressed = bmpSource.Clone(dim, PixelFormat.Format1bppIndexed)) { var result = new StringBuilder(); result.AppendFormat("^XA~DG{2},{0},{1},", stride * dim.Height, stride, filename); byte[][] imageData = GetImageData(dim, stride, bmpCompressed); byte[] previousRow = null; foreach (var row in imageData) { appendLine(row, previousRow, result); previousRow = row; } result.Append(@"^FS^XZ"); return result.ToString(); } } public static string GetGrfDeleteCommand(string filename) { validateFilename(filename); return string.Format("^XA^ID{0}^FS^XZ", filename); } public static string GetGrfPrintCommand(string filename) { validateFilename(filename); return string.Format("^XA^FO0,0^XG{0},1,1^FS^XZ", filename); } static Regex regexFilename = new Regex("^[REBA]:[A-Z0-9]{1,8}\\.GRF$"); private static void validateFilename(string filename) { if (!regexFilename.IsMatch(filename)) { throw new ArgumentException("Filename must be in the format " + "R:XXXXXXXX.GRF. Drives are R, E, B, A. Filename can " + "be alphanumeric between 1 and 8 characters.", "filename"); } } unsafe private static byte[][] GetImageData(Rectangle dim, int stride, Bitmap bmpCompressed) { byte[][] imageData; var data = bmpCompressed.LockBits(dim, ImageLockMode.ReadOnly, PixelFormat.Format1bppIndexed); try { byte* pixelData = (byte*)data.Scan0.ToPointer(); byte rightMask = (byte)(0xff << (data.Stride * 8 - dim.Width)); imageData = new byte[dim.Height][]; for (int row = 0; row < dim.Height; row++) { byte* rowStart = pixelData + row * data.Stride; imageData[row] = new byte[stride]; for (int col = 0; col < stride; col++) { byte f = (byte)(0xff ^ rowStart[col]); f = (col == stride - 1) ? (byte)(f & rightMask) : f; imageData[row][col] = f; } } } finally { bmpCompressed.UnlockBits(data); } return imageData; } private static void appendLine(byte[] row, byte[] previousRow, StringBuilder baseStream) { if (row.All(r => r == 0)) { baseStream.Append(","); return; } if (row.All(r => r == 0xff)) { baseStream.Append("!"); return; } if (previousRow != null && MatchByteArray(row, previousRow)) { baseStream.Append(":"); return; } byte[] nibbles = new byte[row.Length * 2]; for (int i = 0; i < row.Length; i++) { nibbles[i * 2] = (byte)(row[i] >> 4); nibbles[i * 2 + 1] = (byte)(row[i] & 0x0f); } for (int i = 0; i < nibbles.Length; i++) { byte cPixel = nibbles[i]; int repeatCount = 0; for (int j = i; j < nibbles.Length && repeatCount <= 400; j++) { if (cPixel == nibbles[j]) { repeatCount++; } else { break; } } if (repeatCount > 2) { if (repeatCount == nibbles.Length - i && (cPixel == 0 || cPixel == 0xf)) { if (cPixel == 0) { if (i % 2 == 1) { baseStream.Append("0"); } baseStream.Append(","); return; } else if (cPixel == 0xf) { if (i % 2 == 1) { baseStream.Append("F"); } baseStream.Append("!"); return; } } else { baseStream.Append(getRepeatCode(repeatCount)); i += repeatCount - 1; } } baseStream.Append(cPixel.ToString("X")); } } private static string getRepeatCode(int repeatCount) { if (repeatCount > 419) throw new ArgumentOutOfRangeException(); int high = repeatCount / 20; int low = repeatCount % 20; const string lowString = " GHIJKLMNOPQRSTUVWXY"; const string highString = " ghijklmnopqrstuvwxyz"; string repeatStr = ""; if (high > 0) { repeatStr += highString[high]; } if (low > 0) { repeatStr += lowString[low]; } return repeatStr; } private static bool MatchByteArray(byte[] row, byte[] previousRow) { for (int i = 0; i < row.Length; i++) { if (row[i] != previousRow[i]) { return false; } } return true; } } internal static class NativeMethods { #region winspool.drv #region P/Invokes [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool OpenPrinter(string szPrinter, out IntPtr hPrinter, IntPtr pd); [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool ClosePrinter(IntPtr hPrinter); [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern UInt32 StartDocPrinter(IntPtr hPrinter, Int32 level, IntPtr di); [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool EndDocPrinter(IntPtr hPrinter); [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool StartPagePrinter(IntPtr hPrinter); [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool EndPagePrinter(IntPtr hPrinter); [DllImport("winspool.Drv", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool WritePrinter( // 0 IntPtr hPrinter, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pBytes, // 2 UInt32 dwCount, out UInt32 dwWritten); #endregion #region Structs [StructLayout(LayoutKind.Sequential)] internal struct DOC_INFO_1 { [MarshalAs(UnmanagedType.LPWStr)] public string DocName; [MarshalAs(UnmanagedType.LPWStr)] public string OutputFile; [MarshalAs(UnmanagedType.LPWStr)] public string Datatype; } #endregion #endregion } ///  /// Represents a print job in a spooler queue ///  public class GdiPrintJob { IntPtr PrinterHandle; IntPtr DocHandle; ///  /// The ID assigned by the print spooler to identify the job ///  public UInt32 PrintJobID { get; private set; } ///  /// Create a print job with a enumerated datatype ///  ///  ///  ///  ///  public GdiPrintJob(string PrinterName, GdiPrintJobDataType dataType, string jobName, string outputFileName) : this(PrinterName, translateType(dataType), jobName, outputFileName) { } ///  /// Create a print job with a string datatype ///  ///  ///  ///  ///  public GdiPrintJob(string PrinterName, string dataType, string jobName, string outputFileName) { if (string.IsNullOrWhiteSpace(PrinterName)) throw new ArgumentNullException("PrinterName"); if (string.IsNullOrWhiteSpace(dataType)) throw new ArgumentNullException("PrinterName"); IntPtr hPrinter; if (!NativeMethods.OpenPrinter(PrinterName, out hPrinter, IntPtr.Zero)) throw new Win32Exception(); this.PrinterHandle = hPrinter; NativeMethods.DOC_INFO_1 docInfo = new NativeMethods.DOC_INFO_1() { DocName = jobName, Datatype = dataType, OutputFile = outputFileName }; IntPtr pDocInfo = Marshal.AllocHGlobal(Marshal.SizeOf(docInfo)); RuntimeHelpers.PrepareConstrainedRegions(); try { Marshal.StructureToPtr(docInfo, pDocInfo, false); UInt32 docid = NativeMethods.StartDocPrinter(hPrinter, 1, pDocInfo); if (docid == 0) throw new Win32Exception(); this.PrintJobID = docid; } finally { Marshal.FreeHGlobal(pDocInfo); } } ///  /// Write the data of a single page or a precomposed PCL document ///  ///  public void WritePage(Stream data) { if (data == null) throw new ArgumentNullException("data"); if (!data.CanRead && !data.CanWrite) throw new ObjectDisposedException("data"); if (!data.CanRead) throw new NotSupportedException("stream is not readable"); if (!NativeMethods.StartPagePrinter(this.PrinterHandle)) throw new Win32Exception(); byte[] buffer = new byte[0x14000]; /* 80k is Stream.CopyTo default */ uint read = 1; while ((read = (uint)data.Read(buffer, 0, buffer.Length)) != 0) { UInt32 written; if (!NativeMethods.WritePrinter(this.PrinterHandle, buffer, read, out written)) throw new Win32Exception(); if (written != read) throw new InvalidOperationException("Error while writing to stream"); } if (!NativeMethods.EndPagePrinter(this.PrinterHandle)) throw new Win32Exception(); } ///  /// Complete the current job ///  public void CompleteJob() { if (!NativeMethods.EndDocPrinter(this.PrinterHandle)) throw new Win32Exception(); } #region datatypes private readonly static string[] dataTypes = new string[] { // 0 null, "RAW", // 2 "RAW [FF appended]", "RAW [FF auto]", // 4 "NT EMF 1.003", "NT EMF 1.006", // 6 "NT EMF 1.007", "NT EMF 1.008", // 8 "TEXT", "XPS_PASS", // 10 "XPS2GDI" }; private static string translateType(GdiPrintJobDataType type) { return dataTypes[(int)type]; } #endregion } public enum GdiPrintJobDataType { Unknown = 0, Raw = 1, RawAppendFF = 2, RawAuto = 3, NtEmf1003 = 4, NtEmf1006 = 5, NtEmf1007 = 6, NtEmf1008 = 7, Text = 8, XpsPass = 9, Xps2Gdi = 10 } 

出于某种原因,我无法让B64工作,但幸运的是,我能够使用普通的旧版JavaScript来制作Z64(在3个灵魂搜索时间内)。

在ZPL编程指南的其他地方你偶然发现了CISDFCRC16命令 – 让我们神秘,为什么不呢 – 部分,其中指出:

“使用CRC16-CCITT多项式计算指定文件内容的CRC-16,其值为x ^ 16 + x ^ 12 + x ^ 5 + 1.使用初始CRC为0x0000计算“。

除了Japanglish之外,您现在可以查看16位参数化CRC算法目录http://reveng.sourceforge.net/crc-catalogue/16.htm )并查找XMODEM算法,该算法恰好是

 width=16 poly=0x1021 init=0x0000 refin=false refout=false xorout=0x0000 check=0x31c3 name="XMODEM" 

啊哈。 然后我开始寻找我需要的其余代码并偶然发现以下内容:

所以我把文件读作一个字节数组(Uint8Array),把它解析成一个字符串,用LZ77压缩它,把它转回一个字节数组并用base64编码,此时我计算CRC并将它全部粘贴到我的ZPL~DT命令可节省约40%。 美丽。

不幸的是我正在开发一个专有的解决方案,所以我不能发布任何代码。

祝好运!

– 一个人做了另一个人可以做到的。

查看ZPL手册后,您需要计算图像的循环冗余校验 (CRC)。 这是一些计算CRC( 源 )的C代码:

 // Update the CRC for transmitted and received data using // the CCITT 16bit algorithm (X^16 + X^12 + X^5 + 1). unsigned char ser_data; static unsigned int crc; crc = (unsigned char)(crc >> 8) | (crc << 8); crc ^= ser_data; crc ^= (unsigned char)(crc & 0xff) >> 4; crc ^= (crc << 8) << 4; crc ^= ((crc & 0xff) << 4) << 1; 

您也可以参考Wikipedia的CRC页面,因为它还包含其他代码示例。

https://en.wikipedia.org/wiki/Cyclic_redundancy_check

你发送的其他一切看起来都不错。 我会考虑使用其中一个Zebra SDK。 我知道Android会将图像发送到打印机并为您保存。