将 PNG 图像打印到 Zebra 网络打印机

发布于 2024-12-26 04:15:08 字数 1102 浏览 2 评论 0原文

我正在尝试找到一种将图像打印到斑马的方法,但遇到了很多麻烦。

根据文档:

第一种编码称为 B64,使用 MIME 对数据进行编码 Base64 方案。 Base64 用于对电子邮件附件进行编码...
Base64 将 6 位编码为字节,扩展了 33% 覆盖未封闭的数据。
第二种编码,称为 Z64, 首先使用LZ77算法压缩数据以减小其大小。 (该算法由 PKZIP 使用,并且是 PNG 的组成部分 图形格式。)
然后使用以下格式对压缩数据进行编码 MIME Base64 方案如上所述。
将计算 CRC 遍历 Base64 编码的数据。

但它没有更多信息。

基本上我尝试使用编码

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 和计算“大小”的方法。 但似乎都不起作用,并且将图形下载到打印机总是被中止。

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

I am trying to find a way of printing images to a zebra and having a lot of trouble.

According to the docs:

The first encoding, known as B64, encodes the data using the MIME
Base64 scheme. Base64 is used to encode e-mail atachedments ...
Base64 encodes six bits to the byte, for an expantion of 33 percent
over the un-enclosed data.
The second encoding, known as Z64,
first compresses the data using the LZ77 algorithm to reduce its size.
(This algorithm is used by the PKZIP and is intergral to the PNG
graphics format.)
The compressed data is then encoded using the
MIME Base64 scheme as described above.
A CRC will be calculated
accross the Base64-encoded data.

But it doesn't have a great deal more info.

Basically I was trying encoding with

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

Then trying to print with something like:

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));

From the sounds of it, either B64 or Z64 are both accepted.

I've tried a few variations, and a couple of methods for generating the CRC and calculating the 'size'.
But none seem to work and the download of the graphics to the printer is always getting aborted.

Has anyone managed to accomplish something like this? Or knows where I am going wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

放肆 2025-01-02 04:15:08

我得出这个答案的所有功劳都来自LabView 论坛 用户 Raydur。他发布了一个 LabView 解决方案,可以在 LabView 中打开该解决方案来发送图像。我个人没有用我的打印机运行它,我只是用它来找出正确的图像代码,这样我就可以在我的代码中复制它。我错过的最重要的事情是填充我的十六进制代码。例如,1A 就可以,但如果只有 A,则需要在其前面填充 0 才能发送 0A。您发送的 ZPL 中文件的大小也是字节数组的原始大小,而不是数据的最终字符串表示形式。

我浏览了很多很多论坛和 Stackoverflow 帖子试图解决这个问题,因为这看起来是一件很简单的事情。我已经尝试了其他地方发布的每个解决方案,但我真的只想打印 a.PNG,因为我的打印机(Mobile QLN320)的手册内置了对其的支持。它说要么以 Base64 要么以十六进制发送,我尝试了两者均无济于事。对于任何想要执行 Base64 的人,我在一本较旧的手册中发现,您需要手动计算发送的每个数据包的 CRC 代码,因此我选择使用更简单的十六进制路线。所以这是我要工作的代码!

        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
        }

All credit for me coming to this answer was from LabView Forum user Raydur. He posts a LabView solution that can be opened up in LabView to send images down. I personally didn't run it with my printer, I just used it to figure out the correct image code so I could replicate it in my code. The big thing that I was missing was padding my Hexadecimal code. For example, 1A is fine, but if you have just A, you need to pad a 0 in front of it to send 0A. The size of the file in the ZPL you are sending is also the original size of the byte array, not the final string representation of the data.

I've scoured many, many, many forums and Stackoverflow posts trying to figure this out because it seems like such a simple thing to do. I've tried every single solution posted elsewhere but I really wanted to just print a.PNG because the manual for my printer(Mobile QLN320) has support for it built-in. It says to either send it in Base64 or Hexadecimal, and I tried both to no avail. For anyone wanting to do Base64, I found in an older manual that you need to manually calculate CRC codes for each packet you send so I chose to go with the easier Hexadecimal route. So here is the code I got to work!

        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
        }
故事未完 2025-01-02 04:15:08

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

首先,您必须将图像转换为 1bpp 双层图像,然后将其转换为十六进制编码的字符串。您可以进一步压缩图像以减少传输时间。然后您可以使用 ^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
}

/// <summary>
/// Represents a print job in a spooler queue
/// </summary>
public class GdiPrintJob
{
    IntPtr PrinterHandle;
    IntPtr DocHandle;

    /// <summary>
    /// The ID assigned by the print spooler to identify the job
    /// </summary>
    public UInt32 PrintJobID { get; private set; }

    /// <summary>
    /// Create a print job with a enumerated datatype
    /// </summary>
    /// <param name="PrinterName"></param>
    /// <param name="dataType"></param>
    /// <param name="jobName"></param>
    /// <param name="outputFileName"></param>
    public GdiPrintJob(string PrinterName, GdiPrintJobDataType dataType, string jobName, string outputFileName)
        : this(PrinterName, translateType(dataType), jobName, outputFileName)
    {
    }

    /// <summary>
    /// Create a print job with a string datatype
    /// </summary>
    /// <param name="PrinterName"></param>
    /// <param name="dataType"></param>
    /// <param name="jobName"></param>
    /// <param name="outputFileName"></param>
    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);
        }
    }

    /// <summary>
    /// Write the data of a single page or a precomposed PCL document
    /// </summary>
    /// <param name="data"></param>
    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();
    }

    /// <summary>
    /// Complete the current job
    /// </summary>
    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
}

The ZPL II Programming Guide documents the ~DG command and GRF format (page 124) to download images. Volume Two adds details on an optional compression format (page 52).

First, you have to convert the image to a 1bpp bi-level image, then convert it to a hex-encoded string. You can further compress the image to reduce transmission time. You can then print the image with the ^ID command.

While there is inherent support for PNG images in the ~DY command, it is poorly documented and does not seem to work on certain models of printers. The ZB64 format is basically not documented, and attempts to get more information from Zebra support have been fruitless. If you have your heart set on ZB64, you can use the Java based Zebralink SDK (look to ImagePrintDemo.java and com.zebra.sdk.printer.internal.GraphicsConversionUtilZpl.sendImageToStream).

Once you have the command data, it can be sent via TCP/IP if the printer has a print-server, or it can be sent by writing in RAW format to the printer.

The code below prints a 5 kB PNG as a 13 kB compressed GRF (60 kB uncompressed):

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
}

/// <summary>
/// Represents a print job in a spooler queue
/// </summary>
public class GdiPrintJob
{
    IntPtr PrinterHandle;
    IntPtr DocHandle;

    /// <summary>
    /// The ID assigned by the print spooler to identify the job
    /// </summary>
    public UInt32 PrintJobID { get; private set; }

    /// <summary>
    /// Create a print job with a enumerated datatype
    /// </summary>
    /// <param name="PrinterName"></param>
    /// <param name="dataType"></param>
    /// <param name="jobName"></param>
    /// <param name="outputFileName"></param>
    public GdiPrintJob(string PrinterName, GdiPrintJobDataType dataType, string jobName, string outputFileName)
        : this(PrinterName, translateType(dataType), jobName, outputFileName)
    {
    }

    /// <summary>
    /// Create a print job with a string datatype
    /// </summary>
    /// <param name="PrinterName"></param>
    /// <param name="dataType"></param>
    /// <param name="jobName"></param>
    /// <param name="outputFileName"></param>
    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);
        }
    }

    /// <summary>
    /// Write the data of a single page or a precomposed PCL document
    /// </summary>
    /// <param name="data"></param>
    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();
    }

    /// <summary>
    /// Complete the current job
    /// </summary>
    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
}
戏舞 2025-01-02 04:15:08

由于某种原因,我无法让 B64 工作,但幸运的是,我能够通过 Google 搜索到使用普通的旧 JavaScript 使 Z64 工作(在 3 天左右的时间里)。

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

“该字段的值是通过 CRC-16 计算得出的
使用 CRC16-CCITT 多项式的指定文件的内容
x^16 + x^12 + x^5 + 1。它是使用初始 CRC 计算的
0x0000。”

撇开日本英语不谈,您现在可以查看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%。美丽的。

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

祝你好运!

-一个人能做到的事,另一个人也能做到。

For some reason I cannot get B64 to work, but luckily I was able to Google my way into making Z64 work (in 3 soul-searching days or so) using plain old JavaScript.

Somewhere else on the ZPL programming Guide you stumble upon the The CISDFCRC16 command--let's be cryptic, why not--section, which states:

"The value of the field is calculated the CRC-16 for the
contents of a specified file using the CRC16-CCITT polynomial which is
x^16 + x^12 + x^5 + 1. It is calculated using an initial CRC of
0x0000."

Japanglish aside, you can now check out the Catalogue of parametrised CRC algorithms with 16 bits (http://reveng.sourceforge.net/crc-catalogue/16.htm) and look for the XMODEM algorithm, which happens to be

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

Aha. I then started looking for the rest of the code I needed and stumbled upon the following:

So I read the file as a byte array (Uint8Array), parse it as a string, compress it with LZ77, turn that back into a byte array and encode it using base64, at which point I calculate the CRC and paste it all into my ZPL ~DT command for savings of about 40%. Beautiful.

Unfortunately I'm developing a proprietary solution so I cannot post any code.

Good luck!

-What one man did another can do.

末蓝 2025-01-02 04:15:08

查看 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;

您还可以参考维基百科关于 CRC 的页面,因为它还包含其他代码示例。

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

您发送的其他所有内容看起来好的。我会考虑使用 Zebra SDK 之一。我知道 Android 设备会将图像发送到打印机并为您保存。

After looking at the ZPL manual you need to calculate the Cyclic Redundancy Check (CRC) for the image. Here is some C Code that calculates the CRC (source):

// 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;

You can also refer to Wikipedia's page on CRC, as it contains other code examples as well.

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

Everything else you are sending down looks good. I would look into using one of the Zebra SDKs. I know the Android one will send an image to the printer and save it for you.

旧伤还要旧人安 2025-01-02 04:15:08

在此 GitHub 项目中,您将找到所需的一切。 https://github.com/BinaryKits/BinaryKits.Zpl

还有一个PNG转GRF具有附加数据压缩功能的图像转换器。

var elements = new List<ZplElementBase>();
elements.Add(new ZplDownloadGraphics('R', "SAMPLE", System.IO.File.ReadAllBytes("sample.png")));
elements.Add(new ZplRecallGraphic(100, 100, 'R', "SAMPLE"));

var renderEngine = new ZplEngine(elements);
var zpl = renderEngine.ToZplString(new ZplRenderOptions());

In this GitHub project you will find everything you need. https://github.com/BinaryKits/BinaryKits.Zpl

There is also a PNG to GRF image converter with additional data compression available.

var elements = new List<ZplElementBase>();
elements.Add(new ZplDownloadGraphics('R', "SAMPLE", System.IO.File.ReadAllBytes("sample.png")));
elements.Add(new ZplRecallGraphic(100, 100, 'R', "SAMPLE"));

var renderEngine = new ZplEngine(elements);
var zpl = renderEngine.ToZplString(new ZplRenderOptions());
病毒体 2025-01-02 04:15:08

虽然这个问题有 C# 标签,但其他几个答案并不是严格意义上的 C#,所以这里是一个使用 Node 8.5+ (javascript)、使用 java 和 Zebra SDK。对于任何也可以使用 SDK 并执行 POST 请求的 .NET 语言,相同的步骤都非常相似。

const { promisify } = require('util');
const java = require('java');
java.asyncOptions = {
  asyncSuffix: "",
  syncSuffix: "Sync",
  promiseSuffix: "Promise", // Generate methods returning promises, using the suffix Promise.
  promisify
};
// Include all .jar's under C:\Program Files\Zebra Technologies\link_os_sdk\PC\v2.14.5198\lib
// in your lib folder
java.classpath.push(__dirname + "/lib/ZSDK_API.jar"); 

var ByteArrayOutputStream = java.import('java.io.ByteArrayOutputStream');
var ZebraImageFactory = java.import('com.zebra.sdk.graphics.ZebraImageFactory');
var PrinterUtil = java.import('com.zebra.sdk.printer.PrinterUtil');

const main = async function () {
  let path = `C:\\images\\yourimage.png`;
  let os = new ByteArrayOutputStream();
  let image = await ZebraImageFactory.getImagePromise(path);
  PrinterUtil.convertGraphicPromise("E:IMAGE.PNG", image, os);
  console.log(os.toStringSync()); // junk:Z64:~:CRC
  console.log('done');
};
main();

然后你可以通过 ZPL 打印图像,例如:

^XA
~DYE:IMAGE,P,P,1,,:B64:<YOURB64>:<YOURCRC>
^FO0,0^IME:IMAGE.PNG
^XZ

使用类似的东西

await axios.post(`${printer.ip}/pstprnt`, zpl);

Although this question has the C# tag, several other answers are not strictly C#, so here is an answer using Node 8.5+ (javascript), using java and the Zebra SDK. The same steps are very similar for any .NET language that can also use the SDK and perform a POST request.

const { promisify } = require('util');
const java = require('java');
java.asyncOptions = {
  asyncSuffix: "",
  syncSuffix: "Sync",
  promiseSuffix: "Promise", // Generate methods returning promises, using the suffix Promise.
  promisify
};
// Include all .jar's under C:\Program Files\Zebra Technologies\link_os_sdk\PC\v2.14.5198\lib
// in your lib folder
java.classpath.push(__dirname + "/lib/ZSDK_API.jar"); 

var ByteArrayOutputStream = java.import('java.io.ByteArrayOutputStream');
var ZebraImageFactory = java.import('com.zebra.sdk.graphics.ZebraImageFactory');
var PrinterUtil = java.import('com.zebra.sdk.printer.PrinterUtil');

const main = async function () {
  let path = `C:\\images\\yourimage.png`;
  let os = new ByteArrayOutputStream();
  let image = await ZebraImageFactory.getImagePromise(path);
  PrinterUtil.convertGraphicPromise("E:IMAGE.PNG", image, os);
  console.log(os.toStringSync()); // junk:Z64:~:CRC
  console.log('done');
};
main();

Then you can print the image via ZPL like:

^XA
~DYE:IMAGE,P,P,1,,:B64:<YOURB64>:<YOURCRC>
^FO0,0^IME:IMAGE.PNG
^XZ

Using something like

await axios.post(`${printer.ip}/pstprnt`, zpl);
怀中猫帐中妖 2025-01-02 04:15:08

我建议使用一些额外的库,以使您的长期实施更容易。
我通过 BinaryKits.Zpl NuGet 包使用以下代码:

    // PreCondition: bmp and destionation printer must have the same resolution
        public static string CreateZplFromImage(Bitmap bmp, int bmpAndLabelerDotsPerInchResolution)
    {
        ImageSharpImageConverter converter = new ImageSharpImageConverter();
        byte[] data = BitmapUtil.ToPngBytes(bmp);
        ImageResult imageResult = converter.ConvertImage(data);
        List<ZplElementBase> elements = new List<ZplElementBase>
        {
            new ZplGraphicField(0, 0, imageResult.BinaryByteCount, imageResult.BinaryByteCount, imageResult.BytesPerRow, imageResult.RawData.ToHexFromBytes())
        };
        ZplEngine renderEngine = new ZplEngine(elements);
        string output = renderEngine.ToZplString(new ZplRenderOptions
        {
            AddEmptyLineBeforeElementStart = true,
            TargetPrintDpi = bmpAndLabelerDotsPerInchResolution,
            SourcePrintDpi = bmpAndLabelerDotsPerInchResolution, 
        });
        return output;
    }

func ToPngBytes 具有以下代码:

    public static byte[] ToPngBytes(Bitmap src)
    {
        using MemoryStream stream = new MemoryStream();
        src.Save(stream, ImageFormat.Png);
        return stream.ToArray();
    }

该系统可能看起来较慢,因为它从图像创建所有十六进制/二进制数据,但在另一部分,它不使用存储/检索图形,如果您像大多数示例那样立即使用相同的 Zpl 代码提供图像,我会觉得很困惑。

I recommend using some extra library to make your implementation easier on the lon term.
I use the following code through the BinaryKits.Zpl NuGet package:

    // PreCondition: bmp and destionation printer must have the same resolution
        public static string CreateZplFromImage(Bitmap bmp, int bmpAndLabelerDotsPerInchResolution)
    {
        ImageSharpImageConverter converter = new ImageSharpImageConverter();
        byte[] data = BitmapUtil.ToPngBytes(bmp);
        ImageResult imageResult = converter.ConvertImage(data);
        List<ZplElementBase> elements = new List<ZplElementBase>
        {
            new ZplGraphicField(0, 0, imageResult.BinaryByteCount, imageResult.BinaryByteCount, imageResult.BytesPerRow, imageResult.RawData.ToHexFromBytes())
        };
        ZplEngine renderEngine = new ZplEngine(elements);
        string output = renderEngine.ToZplString(new ZplRenderOptions
        {
            AddEmptyLineBeforeElementStart = true,
            TargetPrintDpi = bmpAndLabelerDotsPerInchResolution,
            SourcePrintDpi = bmpAndLabelerDotsPerInchResolution, 
        });
        return output;
    }

The func ToPngBytes has the following code:

    public static byte[] ToPngBytes(Bitmap src)
    {
        using MemoryStream stream = new MemoryStream();
        src.Save(stream, ImageFormat.Png);
        return stream.ToArray();
    }

This system may seem slower as it creates all the Hex/Binary data from your image but on the other part, it doesn't use the store/retrieve graphics which I find confusing if you are providing the image right away with the same Zpl code as most of examples do.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文