Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Print Image to Zebra Printer using EPL Language

4.75/5 (10 votes)
14 Oct 2013CPOL3 min read 74.8K   87  
Print image to Zebra printer

Introduction

In this tip, we shall see how C# can print image to Zebra printer using RawPrinterHelper class from Microsoft.

Background

My job involves printing texts and images on Zebra labels. In the past, I used the Neodynamic.SDK.ThermalLabel.dll to help to do so.

When a new project comes, I decide to create my own way to print texts and images to Zebra printer. I start downloading epl2_programming.pdf and read through it.

First, I found out the code of EPL is not hard to learn, so I start coding with the text, barcode, box, line, etc.
But, hmm.., with image is not simple as I imagine. I Google for a week but unluckily, some people show good pieces of code but not what I want.
So I decide to do it my way and I post here. Maybe somebody will be interested.

Understand How Zebra GW Command Works

Direct Graphic White (GW) has syntax:

GWp1,p2,p3,p4,DATA

  • p1: Horizontal start position
  • p2: Vertical start position
  • p3: Width of Graphic in bytes (8 dots = 1 byte of data)
  • p4: Length of graphic in dots (or printlines)
  • DATA: Raw binary data without graphic file formatting

There are 2 parameters we need to analyse here:

  • p3: EPL expects this parameter is a multi of 8 bits, that means we have to round up the width of image to multi of 8 (a pixel presenting a bit)
  • DATA: EPL expects this parameter is a 2 dimension binary matrix with the width is width of image which is rounded up and the height is the height of image in pixels

In order to show the DATA I mention above, let's say we have a image with 35 x 5 pixels.
Because Zebra printer prints image on black & white colours only, black (burned) presenting by bit 0 and white (not burned) presenting by bit 1.
So we have to convert an image into 2 dimension binary matrix as below and add extra bits to make up the width of matrix which is multi of 8 bits

40 pixels round up to multi of 8
35 pixels
11111111111111110001111111111111111
11111111111111100000111111111111111
11111111111111000000011111111111111
11111111111111100000111111111111111
11111111111111110001111111111111111
5 pixels extra
11111
11111
11111
11111
11111

Using the Code

The attached code is built using C# 2008. It has:

  • C# syntax

Let's break down

We will use the SendStringToPrinter of RawPrinterHelper.cs to send data to Zebra printer. First, we create a function wrapping GW command and return a string:

C#
private static string SendImageToPrinter( int top, int left, System.Drawing.Bitmap bitmap)
{
   using (MemoryStream ms = new MemoryStream())
   using (BinaryWriter bw = new BinaryWriter(ms, Encoding.ASCII))
   {
       //we set p3 parameter, remember it is Width of Graphic in bytes,
       //so we divive the width of image and round up of it
       int P3 = (int)Math.Ceiling((double)bitmap.Width / 8);
       bw.Write(Encoding.ASCII.GetBytes(string.Format
       ("GW{0},{1},{2},{3},", top, left, P3, bitmap.Height)));
       //the width of matrix is rounded up multi of 8
       int canvasWidth = P3 * 8;
       //Now we convert image into 2 dimension binary matrix by 2 for loops below,
       //in the range of image, we get colour of pixel of image,
       //calculate the luminance in order to set value of 1 or 0
       //otherwise we set value to 1
       //Because P3 is set to byte (8 bits), so we gather 8 dots of this matrix,
       //convert into a byte then write it to memory by using shift left operator <<
       //e,g 1 << 7  ---> 10000000
       //    1 << 6  ---> 01000000
       //    1 << 3  ---> 00001000
       for (int y = 0; y < bitmap.Height; ++y)     //loop from top to bottom
       {
          for (int x = 0; x < canvasWidth; )       //from left to right
          {
              byte abyte = 0;
              for (int b = 0; b < 8; ++b, ++x)     //get 8 bits together and write to memory
              {
                  int dot = 1;                     //set 1 for white,0 for black
                  //pixel still in width of bitmap,
                  //check luminance for white or black, out of bitmap set to white
                  if (x < bitmap.Width)
                  {
                      System.Drawing.Color color = bitmap.GetPixel(x, y);
                      int luminance = (int)((color.R * 0.3) + (color.G * 0.59) + (color.B * 0.11));
                      dot = luminance > 127 ? 1 : 0;
                   }
                   abyte |= (byte)(dot << (7 - b)); //shift left,
                                   //then OR together to get 8 bits into a byte
               }
               bw.Write(abyte);
          }
       }
       bw.Write("\n");
       bw.Flush();
       //reset memory
       ms.Position = 0;
       //get encoding, I have no idea why encode page of 1252 works and fails for others
       return Encoding.GetEncoding(1252).GetString(ms.ToArray());
    }
}

Rotate image

I found this function on the internet, I just edited to suit my purpose. I post here together so if someone is interested, there is no need to Google.

C#
private static System.Drawing.Bitmap RotateImg
(System.Drawing.Bitmap bmp, float angle)
{
   angle = angle % 360;
   if (angle > 180) angle -= 360;
   float sin = (float)Math.Abs(Math.Sin(angle *
   Math.PI / 180.0)); // this function takes radians
   float cos = (float)Math.Abs(Math.Cos(angle * Math.PI / 180.0)); // this one too
   float newImgWidth = sin * bmp.Height + cos * bmp.Width;
   float newImgHeight = sin * bmp.Width + cos * bmp.Height;
   float originX = 0f;
   float originY = 0f;
   if (angle > 0)
   {
      if (angle <= 90)
         originX = sin * bmp.Height;
      else
      {
         originX = newImgWidth;
         originY = newImgHeight - sin * bmp.Width;
      }
   }
   else
   {
      if (angle >= -90)
         originY = sin * bmp.Width;
      else
      {
         originX = newImgWidth - sin * bmp.Height;
         originY = newImgHeight;
      }
   }
   System.Drawing.Bitmap newImg =
   new System.Drawing.Bitmap((int)newImgWidth, (int)newImgHeight);
   System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(newImg);
   g.Clear(System.Drawing.Color.White);
   g.TranslateTransform(originX, originY); // offset the origin to our calculated values
   g.RotateTransform(angle); // set up rotate
   g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
   g.DrawImageUnscaled(bmp, 0, 0); // draw the image at 0, 0
   g.Dispose();
   return newImg;
}

Now we create 2 public functions:

C#
public static string SendImageToPrinter(int top, int left, string source, float angle)
{
   System.Drawing.Bitmap bitmap = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile(source);
   System.Drawing.Bitmap newbitmap = RotateImg(bitmap, angle);
   return SendImageToPrinter(top, left, newbitmap);
}
public static string SendImageToPrinter(int top, int left, string source)
{
   System.Drawing.Bitmap bitmap = (System.Drawing.Bitmap)System.Drawing.Bitmap.FromFile(source);
   return SendImageToPrinter( top, left, bitmap);
}

Last part we create demon project:

C#
string str = "\nN\nq812Q1218,20\n";
str += Utils.SendImageToPrinter( 50, 50, System.IO.Path.GetDirectoryName
(System.Reflection.Assembly.GetEntryAssembly().Location) + @"\yourimage.jpg");
str += "B200,150,0,K,3,7,150,N,\"A123456789A\"\n";
str += "A340,310,0,4,1,1,N,\"123456789\"\n";
str += "X100,400,1,730,1100\n";
str += "A110,560,0,3,1,1,N,\"Contact\"\n";
str += "A260,560,0,3,1,1,N,\"YOUR NAME\"\n";
str += "A110,590,0,3,1,1,N,\"Deparment\"\n";
str += "A260,590,0,3,1,1,N,\"YOUR DEPARTMENT\"\n";
str += "A110,620,0,3,1,1,N,\"Company\"\n";
str += "A260,620,0,3,1,1,N,\"YOUR COMPANY\"\n";
str += "A110,650,0,3,1,1,N,\"Address\"\n";
str += "A260,650,0,3,1,1,N,\"YOUR ADDRESS1\"\n";
str += "A260,680,0,3,1,1,N,\"YOUR ADDRESS2\"\n";
str += "A260,710,0,3,1,1,N,\"YOUR ADDRESS3\"\n";
str += "A110,740,0,3,1,1,N,\"City\"\n";
str += "A260,740,0,3,1,1,N,\"YOUR CITY\"\n";
str += "A110,770,0,3,1,1,N,\"State\"\n";
str += "A260,770,0,3,1,1,N,\"YOUR STATE\"\n";
str += "A110,800,0,3,1,1,N,\"Country\"\n";
str += "A260,800,0,3,1,1,N,\"YOUR COUNTRY\"\n";
str += "A110,830,0,3,1,1,N,\"Post code\"\n";
str += "A260,830,0,3,1,1,N,\"YOUR POSTCODE\"\n";
str += "A110,860,0,3,1,1,N,\"Phone No\"\n";
str += "A260,860,0,3,1,1,N,\"YOUR PHONE\"\n";
str += "A110,890,0,3,1,1,N,\"Email\"\n";
str += "A260,890,0,3,1,1,N,\"YOUR EMAIL\"\n";
str += "P1\n";
RawPrinterHelper.SendStringToPrinter(ZebraPrinter, str);

RawPrinterHelper.cs class

C#
public class RawPrinterHelper
    {
        // Structure and API declarions:
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public class DOCINFOA
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDocName;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pOutputFile;
            [MarshalAs(UnmanagedType.LPStr)]
            public string pDataType;
        }
        [DllImport("winspool.Drv", EntryPoint = "OpenPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool OpenPrinter([MarshalAs(UnmanagedType.LPStr)] string szPrinter, out IntPtr hPrinter, IntPtr pd);

        [DllImport("winspool.Drv", EntryPoint = "ClosePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool ClosePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartDocPrinterA", SetLastError = true, CharSet = CharSet.Ansi, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);

        [DllImport("winspool.Drv", EntryPoint = "EndDocPrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool EndDocPrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "StartPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool StartPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "EndPagePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool EndPagePrinter(IntPtr hPrinter);

        [DllImport("winspool.Drv", EntryPoint = "WritePrinter", SetLastError = true, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
        public static extern bool WritePrinter(IntPtr hPrinter, IntPtr pBytes, Int32 dwCount, out Int32 dwWritten);

        // SendBytesToPrinter()
        // When the function is given a printer name and an unmanaged array
        // of bytes, the function sends those bytes to the print queue.
        // Returns true on success, false on failure.
        public static bool SendBytesToPrinter(string szPrinterName, IntPtr pBytes, Int32 dwCount)
        {
            Int32 dwError = 0, dwWritten = 0;
            IntPtr hPrinter = new IntPtr(0);
            DOCINFOA di = new DOCINFOA();
            bool bSuccess = false; // Assume failure unless you specifically succeed.

            di.pDocName = "My C#.NET RAW Document";
            di.pDataType = "RAW";

            // Open the printer.
            if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
            {
                // Start a document.
                if (StartDocPrinter(hPrinter, 1, di))
                {
                    // Start a page.
                    if (StartPagePrinter(hPrinter))
                    {
                        // Write your bytes.
                        bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
                        EndPagePrinter(hPrinter);
                    }
                    EndDocPrinter(hPrinter);
                }
                ClosePrinter(hPrinter);
            }
            // If you did not succeed, GetLastError may give more information
            // about why not.
            if (bSuccess == false)
            {
                dwError = Marshal.GetLastWin32Error();
            }
            return bSuccess;
        }
        public static bool SendMemoryToPrinter(string szPrinterName, MemoryStream ms)
        {            
            BinaryReader br = new BinaryReader(ms);
            Byte[] bytes = new Byte[ms.Length];
            bool bSuccess = false;
            IntPtr pUnmanagedBytes = new IntPtr(0);
            int nLength;

            nLength = Convert.ToInt32(ms.Length);
            bytes = br.ReadBytes(nLength);
            pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
            Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
            bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
            Marshal.FreeCoTaskMem(pUnmanagedBytes);
            return bSuccess;
        }
        public static bool SendFileToPrinter(string szPrinterName, string szFileName)
        {
            // Open the file.
            FileStream fs = new FileStream(szFileName, FileMode.Open);
            // Create a BinaryReader on the file.
            BinaryReader br = new BinaryReader(fs);
            // Dim an array of bytes big enough to hold the file's contents.
            Byte[] bytes = new Byte[fs.Length];
            bool bSuccess = false;
            // Your unmanaged pointer.
            IntPtr pUnmanagedBytes = new IntPtr(0);
            int nLength;

            nLength = Convert.ToInt32(fs.Length);
            // Read the contents of the file into the array.
            bytes = br.ReadBytes(nLength);
            // Allocate some unmanaged memory for those bytes.
            pUnmanagedBytes = Marshal.AllocCoTaskMem(nLength);
            // Copy the managed byte array into the unmanaged array.
            Marshal.Copy(bytes, 0, pUnmanagedBytes, nLength);
            // Send the unmanaged bytes to the printer.
            bSuccess = SendBytesToPrinter(szPrinterName, pUnmanagedBytes, nLength);
            // Free the unmanaged memory that you allocated earlier.
            Marshal.FreeCoTaskMem(pUnmanagedBytes);
            return bSuccess;
        }
        public static bool SendStringToPrinter(string szPrinterName, string szString)
        {
            IntPtr pBytes;
            Int32 dwCount;
            // How many characters are in the string?
            dwCount = szString.Length;
            // Assume that the printer is expecting ANSI text, and then convert
            // the string to ANSI text.
            pBytes = Marshal.StringToCoTaskMemAnsi(szString);
            // Send the converted ANSI string to the printer.
            SendBytesToPrinter(szPrinterName, pBytes, dwCount);
            Marshal.FreeCoTaskMem(pBytes);
            return true;
        }
    }

Points of Interest

What we want to know in this tip is to understand how Zebra printer expects image data in kind of binary array.

So we can develop and maintain the code in different languages as Java for example.

History

  • 10 October 2013: First version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)