Print Image to Zebra Printer using EPL Language






4.75/5 (9 votes)
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 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
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
:
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.
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:
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:
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
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