Printing PNG images to a zebra network printer

爱⌒轻易说出口 提交于 2019-11-26 16:09:35

问题


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?


回答1:


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 a 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
        }



回答2:


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
}



回答3:


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:

  • LZ77-Algorithm-Based JavaScript Compressor (http://lab.polygonpla.net/js/tinylz77.html)
  • base-64.js (https://github.com/beatgammit/base64-js/blob/master/lib/b64.js)
  • Lammert Bies' 2008 CRC Library (http://www.lammertbies.nl/comm/info/crc-calculation.html) ported from ANSI C--with the precaution to bitwise-AND with 0xffff the update function return value since JavaScript treats every number as a 32-bit signed integer.

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.




回答4:


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.




回答5:


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


来源:https://stackoverflow.com/questions/8818688/printing-png-images-to-a-zebra-network-printer

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!