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 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
5 pixels extra | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
|
Using the Code
The attached code is built using C# 2008. It has:
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))
{
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)));
int canvasWidth = P3 * 8;
for (int y = 0; y < bitmap.Height; ++y)
{
for (int x = 0; x < canvasWidth; )
{
byte abyte = 0;
for (int b = 0; b < 8; ++b, ++x)
{
int dot = 1;
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));
}
bw.Write(abyte);
}
}
bw.Write("\n");
bw.Flush();
ms.Position = 0;
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));
float cos = (float)Math.Abs(Math.Cos(angle * Math.PI / 180.0));
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);
g.RotateTransform(angle);
g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
g.DrawImageUnscaled(bmp, 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
{
[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);
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;
di.pDocName = "My C#.NET RAW Document";
di.pDataType = "RAW";
if (OpenPrinter(szPrinterName.Normalize(), out hPrinter, IntPtr.Zero))
{
if (StartDocPrinter(hPrinter, 1, di))
{
if (StartPagePrinter(hPrinter))
{
bSuccess = WritePrinter(hPrinter, pBytes, dwCount, out dwWritten);
EndPagePrinter(hPrinter);
}
EndDocPrinter(hPrinter);
}
ClosePrinter(hPrinter);
}
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)
{
FileStream fs = new FileStream(szFileName, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
Byte[] bytes = new Byte[fs.Length];
bool bSuccess = false;
IntPtr pUnmanagedBytes = new IntPtr(0);
int nLength;
nLength = Convert.ToInt32(fs.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 SendStringToPrinter(string szPrinterName, string szString)
{
IntPtr pBytes;
Int32 dwCount;
dwCount = szString.Length;
pBytes = Marshal.StringToCoTaskMemAnsi(szString);
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