2012-01-11 449 views
10

Estoy tratando de encontrar una manera de imprimir imágenes a una cebra y de tener muchos problemas.Imprimir imágenes PNG en una impresora de red zebra

De acuerdo con los documentos:

La primera codificación, conocida como B64, codifica los datos utilizando el esquema MIME Base64. Base64 se utiliza para codificar anexos de correo electrónico ...
Base64 codifica seis bits en el byte, para una expansión del 33 por ciento sobre los datos no incluidos.
La segunda codificación, conocida como Z64, primero comprime los datos utilizando el algoritmo LZ77 para reducir su tamaño. (Este algoritmo es utilizado por PKZIP y es intergral al formato de gráficos PNG ).
Los datos comprimidos se codifican utilizando el esquema MIME Base64 como se describe anteriormente.
Se calculará un CRC entre los datos codificados en Base64.

Pero no tiene mucha información.

Básicamente yo estaba tratando de codificación con

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

Entonces intentar imprimir con algo como:

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

A partir de los sonidos de la misma, ya sea B64 o Z64 son aceptadas.

He intentado algunas variaciones, y un par de métodos para generar el CRC y calcular el 'tamaño'. Pero ninguno parece funcionar y la descarga de los gráficos a la impresora siempre se cancela.

¿Alguien ha logrado lograr algo como esto? ¿O sabe dónde me estoy equivocando?

+1

He tenido más éxito con la impresión de ventanas estándar. es decir, imprima una imagen como lo haría con una impresora estándar. Si la configuración/stock de la impresora está configurada correctamente, se imprimirá correctamente. –

+1

Esta es una ruta que he estudiado, pero no creo que pueda instalar los controladores en todas las computadoras (todas están en una granja de cítricos) y no puedo conectar localmente una impresora a cada máquina que pueda necesitar eso. –

+1

En ese caso, no estoy seguro, ya que esa es la única forma en que lo hice en el pasado. Lo único que se me ocurre es crear un programa en el servidor que busque archivos de imagen, luego hacer que las PC del cliente escriban en esa carpeta, o escribir un cliente/servidor adecuado. –

Respuesta

1

Después de consultar el manual de ZPL, debe calcular Comprobación de redundancia cíclica (CRC) para la imagen. Aquí hay un código C que calcula la 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; 

También puede hacer referencia a la página de Wikipedia sobre la CRC, ya que contiene otros ejemplos de código también.

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

Todo lo demás va a enviar hacia abajo se ve bien. Me gustaría estudiar el uso de uno de los SDK de Zebra. Sé que Android enviará una imagen a la impresora y la guardará para usted.

+4

Y esta es la razón por la que las respuestas del enlace son malas - ese enlace está muerto :( –

12

Todo el crédito para mí al llegar a esta respuesta fue de LabView Forum usuario Raydur. Publica una solución LabView que se puede abrir en LabView para enviar imágenes. Personalmente no lo ejecuté con mi impresora, solo lo usé para descubrir el código de imagen correcto para poder replicarlo en mi código. Lo más importante que me faltaba era rellenar mi código Hexadecimal. Por ejemplo: 1A está bien, pero si solo tiene A, necesita rellenar un 0 delante de él para enviar 0A. El tamaño del archivo en la ZPL que está enviando también es el tamaño original de la matriz de bytes, no la representación de cadena final de los datos.

He recorrido muchos, muchos, muchos foros y publicaciones de Stackoverflow tratando de resolver esto porque parece algo muy simple de hacer. He intentado todas y cada una de las soluciones publicadas en otro sitio, pero realmente quería imprimir un .PNG porque el manual para mi impresora (Mobile QLN320) tiene soporte para ello. Dice que lo envíe en Base64 o Hexadecimal, y lo intenté ambos en vano. Para cualquiera que quiera hacer Base64, encontré en un manual anterior que necesita calcular manualmente los códigos CRC para cada paquete que envía, así que elegí ir con la ruta hexadecimal más fácil. ¡Así que aquí está el código que tengo para trabajar!

 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 
     } 
+0

Hmmm, esto no funciona para mí ... sé si esto funcionaría para un GK420t? – Calvin

+0

Estoy usando un Transmisión TCP pero de forma predeterminada las impresoras lo tienen apagado. ¿Se ha asegurado de que TCP esté habilitado? ¿Y de que su impresora lo admita/inalámbrico? ¿Cuál es el error que está obteniendo? – Warren

+0

Intenté hacer esto con base64, añadiendo el calculado Valor de CRC según el manual, pero no fue capaz de hacerlo funcionar. Terminé yendo también con hex. –

6

El ZPL II Programming Guide documenta el comando ~DG y el formato GRF (página 124) para descargar imágenes. Volume Two agrega detalles sobre un formato de compresión opcional (página 52).

En primer lugar, debe convertir la imagen en una imagen de dos niveles de 1bpp, luego conviértala en una cadena codificada en hexadecimal. Puede comprimir aún más la imagen para reducir el tiempo de transmisión. A continuación, puede imprimir la imagen con el comando ^ID.

Si bien existe un soporte inherente para las imágenes PNG en el comando ~DY, está poco documentado y no parece funcionar en ciertos modelos de impresoras. El formato ZB64 básicamente no está documentado, y los intentos de obtener más información del soporte de Zebra han sido infructuosos. Si tiene el corazón puesto en ZB64, puede usar Java based Zebralink SDK (busque ImagePrintDemo.java y com.zebra.sdk.printer.internal.GraphicsConversionUtilZpl.sendImageToStream).

Una vez que tenga los datos del comando, puede enviarlos a través de TCP/IP si la impresora tiene un servidor de impresión, o puede enviarlos por escrito al formato RAW a la impresora.

El código siguiente imprime un 5 kB PNG como un 13 kB comprimido GRF (60 kB sin comprimir):

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 
} 
+0

¡Acabas de salvar mi vida! ¡Lo aprecio! –

1

Por alguna razón no puedo conseguir B64 para trabajar, pero por suerte pude Google mi camino para hacer que Z64 funcione (en 3 días de búsqueda de almas más o menos) usando JavaScript simple.

En algún otro lugar en la Guía de programación ZPL te tropiezas con el comando El CISDFCRC16 - vamos a ser críptico, por qué no - sección, que establece:

"El valor del campo se calcula el CRC 16 para el contenido de un archivo especificado utilizando el polinomio CRC16-CCITT que es x^16 + x^12 + x^5 + 1. Se calcula utilizando un CRC inicial de 0x0000. "

Japanglish a un lado, ahora se puede ver el catálogo de algoritmos CRC parametrizado con 16 bits (http://reveng.sourceforge.net/crc-catalogue/16.htm) y busque el algoritmo XMODEM, que pasa a ser

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

Aha. entonces empecé a buscar el resto del código que necesitaba y topé con lo siguiente: En base-LZ77-Algoritmo

así que leí el archivo como una matriz de bytes (Uint8Array), analizarlo como una cadena, comprimirlo con LZ77, a su vez que de nuevo en una matriz de bytes y codificar base 64, momento en el que se calcula el CRC y pegarlo todo en mi comando ZPL ~ DT para un ahorro de aproximadamente 40%. Hermosa.

Desafortunadamente estoy desarrollando una solución patentada por lo que no puedo publicar ningún código.

¡Buena suerte!

-Lo que un hombre hizo, otro puede hacer.

+0

Parece que me engañé a mí mismo para resolver esto. no tendría sentido que Z64 funcionara si B64 no lo hace. Además, parece que ZPL incumplió la versión sin comprimir de la fuente después de no cargar las comprimidas. Volver al plano de diseño. – thor2k

+0

Resulta que la funcionalidad de compresión no se implementó en las impresoras que estoy usando: el fabricante está trabajando en una versión de firmware que maneja objetos comprimidos (fuentes o imágenes). ¡Los mantendré informados! – thor2k

Cuestiones relacionadas