Печать изображений PNG на сетевом принтере Zebra - PullRequest
11 голосов
/ 11 января 2012

Я пытаюсь найти способ печати изображений на зебре и у меня много проблем.

Согласно документации:

Первая кодировка, известная как B64, кодирует данные, используя схему MIME Base64.Base64 используется для кодирования почтовых привязок ...Base64 кодирует шесть битов в байт для расширения 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 и вычисления «размера».Но ни одна из них не работает, и загрузка графики на принтер всегда прерывается.

Кому-нибудь удалось сделать что-то подобное?Или знает куда я иду не так?

Ответы [ 5 ]

13 голосов
/ 25 марта 2014

Вся моя благодарность за то, что я пришел к этому ответу, была от LabView Forum пользователя Raydur.Он публикует решение LabView, которое можно открыть в LabView для отправки изображений вниз.Лично я не запускал его на своем принтере, я просто использовал его, чтобы выяснить правильный код изображения, чтобы я мог воспроизвести его в своем коде.Главное, чего мне не хватало - это дополнить мой шестнадцатеричный код.Например: 1A - это хорошо, но если у вас есть только A, вам нужно добавить 0 перед ним, чтобы отправить 0A.Размер файла в ZPL, который вы отправляете, также является исходным размером байтового массива, а не окончательным строковым представлением данных.

Я просмотрел много, много, много форумов и постов Stackoverflow, пытаясьчтобы понять это, потому что это кажется таким простым делом.Я пробовал каждое решение, опубликованное в другом месте, но я действительно хотел просто напечатать .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
        }
6 голосов
/ 06 января 2015

Руководство по программированию ZPL II описывает команду ~DG и формат GRF (стр. 124) для загрузки изображений. Том второй добавляет сведения о необязательном формате сжатия (стр. 52).

Сначала необходимо преобразовать изображение в двухуровневое изображение с разрешением 1 бит на дюйм, а затем преобразовать его в шестнадцатеричный кодстрока.Вы можете дополнительно сжать изображение, чтобы уменьшить время передачи.Затем вы можете распечатать изображение с помощью команды ^ID.

Хотя в команде ~DY имеется встроенная поддержка изображений PNG, она плохо документирована и, похоже, не работает на некоторых моделях принтеров.Формат ZB64 в основном не документирован, и попытки получить больше информации от поддержки Zebra были бесплодными.Если ваше сердце настроено на ZB64, вы можете использовать Zebralink SDK на основе Java (см. ImagePrintDemo.java и com.zebra.sdk.printer.internal.GraphicsConversionUtilZpl.sendImageToStream).

Как только у вас есть данные команды, она можетотправлять через TCP / IP, если на принтере установлен сервер печати, или его можно отправить, написав на принтере в формате RAW.

Приведенный ниже код печатает PNG размером 5 кБ в сжатом виде 13 КБ.GRF (60 кБ без сжатия):

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
}
2 голосов
/ 29 августа 2015

По какой-то причине я не могу заставить работать B64, но, к счастью, я смог найти способ заставить Z64 работать (за 3 дня самообследования), используя старый добрый JavaScript.

Где-то еще в Руководстве по программированию ZPL вы наткнулись на команду CISDFCRC16 - давайте будем загадочным, почему нет - раздел, в котором говорится:

"Значение поля рассчитывается CRC-16 для содержимое указанного файла с использованием полинома CRC16-CCITT, который х ^ 16 + х ^ 12 + х ^ 5 + 1. Он рассчитывается с использованием начального CRC 0x0000 ".

С другой стороны, теперь вы можете просмотреть каталог параметризованных алгоритмов CRC с 16 битами (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"

Aha. Затем я начал искать остальную часть кода, который мне был нужен, и наткнулся на следующее:

Итак, я читаю файл как байтовый массив (Uint8Array), анализирую его как строку, сжимаю его с помощью LZ77, превращаю его обратно в байтовый массив и кодирую его с помощью base64, после чего я вычисляю CRC и вставляю его все в моей команде ZPL ~ DT для экономии около 40%. Красивые.

К сожалению, я разрабатываю проприетарное решение, поэтому не могу публиковать код.

Удачи!

-Что может сделать один человек, другой может сделать.

1 голос
/ 25 февраля 2012

После просмотра руководства по ZPL необходимо рассчитать Циклическая проверка избыточности (CRC) для изображения.Вот некоторый C-код, который вычисляет 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;

Вы также можете обратиться к странице Википедии по CRC, поскольку она также содержит другие примеры кода.

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

Все остальное, что вы отправляете вниз, выглядит хорошо.Я хотел бы изучить использование одного из Zebra SDK.Я знаю, что Android отправит изображение на принтер и сохранит его для вас.

0 голосов
/ 08 января 2019

Хотя этот вопрос имеет тег C #, некоторые другие ответы не являются строго C #, поэтому вот ответ с использованием Node 8.5+ (javascript), с использованием java и Zebra SDK .Те же шаги очень похожи для любого языка .NET, который также может использовать SDK и выполнить запрос POST.

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