Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#
Tip/Trick

Print Image to Zebra Printer using EPL Language

Rate me:
Please Sign up or sign in to vote.
4.75/5 (10 votes)
14 Oct 2013CPOL3 min read 72.1K   81   16   18
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)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to mak it work in VB.net?? Pin
hector fragoso29-Mar-22 7:42
hector fragoso29-Mar-22 7:42 
QuestionVery nice solution Pin
Misael C. Homem5-Nov-19 7:25
Misael C. Homem5-Nov-19 7:25 
Questionuse GW to Render Text Pin
baranils21-Oct-19 1:05
baranils21-Oct-19 1:05 
Praiseworks ! Pin
nickchan190125-Jan-19 1:54
nickchan190125-Jan-19 1:54 
QuestionException ( File not found ) . Pin
Member 1238019813-Apr-16 3:18
Member 1238019813-Apr-16 3:18 
QuestionThanks!, work with jpg, png and bmp in diferent sizes. Pin
ricardormc22-Jan-16 13:14
ricardormc22-Jan-16 13:14 
SuggestionMy vote of 1 Pin
morteza5712-Sep-14 3:39
morteza5712-Sep-14 3:39 
GeneralRe: My vote of 1 Pin
Martin Ton15-Sep-14 3:49
professionalMartin Ton15-Sep-14 3:49 
SuggestionRe: My vote of 4 Pin
morteza5715-Sep-14 21:33
morteza5715-Sep-14 21:33 
QuestionI Cannot able to print the image pls help Pin
L.Karthik keyan14-Jul-14 0:14
L.Karthik keyan14-Jul-14 0:14 
QuestionIt does not works on the IIS Pin
SDE EDP4-Jul-14 22:09
SDE EDP4-Jul-14 22:09 
QuestionNetwork printer Pin
DaveLarsen16-Mar-14 23:33
DaveLarsen16-Mar-14 23:33 
AnswerRe: Network printer Pin
irfankoprucu6-Jan-19 21:32
irfankoprucu6-Jan-19 21:32 
QuestionStatus responses Pin
wakthar15-Jan-14 5:00
wakthar15-Jan-14 5:00 
Hi Martin, Just wondering if you know if its possible to get a response back from the printer as to whether its ok to send the next print job? We connect to our label printers via tcp/ip and what happens is that after sending a fair few print jobs, the printer will print a batch, miss out on a batch and then print another batch.
I wrote a test app which send the command ~HS every 100ms using a timer and checked to see if the buffer full flag was set to true or not. If the flag was false then I kept sending print jobs but if the status return true, I waited until the status returned false. This works well for, I can send print jobs and I get the buffer full flag, wait, and then resume sending more print jobs. But then suddenly the printer stop communication, the red led appears and the printing stops.

Am I using the correct command?

I have tried contacting Zebra but no response from them at all. Very frustrating!

Thanks.
Questionprinting from mobile phone Pin
Yogeshwari Jadhav17-Dec-13 0:07
Yogeshwari Jadhav17-Dec-13 0:07 
QuestionUtils gives an error Pin
Kalujanaka25-Oct-13 0:37
Kalujanaka25-Oct-13 0:37 
AnswerRe: Utils gives an error Pin
Martin Ton25-Oct-13 1:28
professionalMartin Ton25-Oct-13 1:28 
GeneralRe: Utils gives an error Pin
Kalujanaka27-Oct-13 17:29
Kalujanaka27-Oct-13 17:29 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.