Printing for the TLP2844 Zebra
[You can dowload some demo code at the end of the post]
Last week I had to find a way to print to TLP 2844 Zebra printer from a C# Windows Forms app. For those that don't know, I'm speaking about a thermal transfer printer which is normally used for printing bar codes in labels. I had to perform the following tasks:
- write text in the label;
- write bar codes to the label;
- print images in the label.
This specific printer understands EPL2 and expects to receive one or more commands in text. If you go through the manual, you'll see that there's several things you can do, like build forms (which are useful when you're using variables), print barcodes and even print images (which is really the hard part). For instance, if you want to print Testing, you'll have to send something like this to the printer:
If you have zebra and want to run a simple test, then save the following to a txt file:
The previous file has a blank empty line followed by the N command (which clears the buffer of the printer)and then prints one copy (P1) with the text Testing at 10,10 (x,y). Sending the previous file to the printer is as simple as copying it to LPT1 (assuming you've installed your printer in that port):
copy demo.txt lpt1:
After this short introduction, it's easy to see that we need to:
- abstract the printer commands
- write some code that communicates with the printer
Abstracting the printer commands, can be easily done. In my case, I've just created an interface that looks like this:
public interface ICommand
and then it was just a matter of creating several classes that implement this interface. For instance, the PrintTextCommand looks like this:
public class PrintTextCommand:BasePositioning, ICommand
private string _txt;
private int _fontSelection;
private PrintingRotation _rotation;
private PrintingMultiplier _horizontalMultiplier;
private PrintingMultiplier _verticalMultiplier;
private PrintingReverse _printingReverse;
public PrintTextCommand( int x, int y, string txt, PrintingRotation rotation, int fontSelection,
PrintingMultiplier horizontalMultiplier, PrintingMultiplier verticalMultiplier, PrintingReverse printingReverse)
_txt = txt;
_fontSelection = fontSelection;
_rotation = rotation;
_horizontalMultiplier = horizontalMultiplier;
_verticalMultiplier = verticalMultiplier;
_printingReverse = printingReverse;
public PrintTextCommand( int x, int y, string txt, int size)
:this(x, y, txt, PrintingRotation.NoRotation, size, PrintingMultiplier.One,
public PrintTextCommand( int x, int y, string txt)
:this(x, y, txt, 2)
#region ICommand Members
Since all of the commands need to specify their location, I've created a base class (BasePositioning) which is only used to hold the topleft point used by all the commands (yes, I'll probably burn in hell for not defining a protected property and letting the derived classes access the internal field directly :)). Now, the most difficult of the commands was the one responsible for printing the image. When you think about zebra and images, you have 2 options: you can load the image in the printer's memory or can just send it each time you need to print it. In my case, I decided to go with the 2nd option since I really dind't want to go to all the sites where the code was going to be used in order to add the image to the memory of the printer (nor was I in the mood to write an installer that did just that).
Unfortunately, the EPL2 manual falls short and really won't help you much when you decide to use the GW command. Don't believe me, here's what it has to say about sending graphics to the printer:
GW Command - Direct Graphic Write
Description: Use this command to load binary graphic data directly into the Image Buffer memory for immediate printing. The printer does not store graphic data sent directly to the image buffer. The graphic data is lost when the image has finished printing, power is removed or the printer is reset. Commands that size (Q and q) or clear (N and M) the image buffer will also remove graphic image data.
btw, note that it's missing the , char after p4. as you'd expect, p1 and p2 is the top-left coordinate. p3 is supposed to be the width of the image in bytes and p4 its height. Well, they don't even give you a simple example of how to use this command. After loosing some time trying to find a sample, i thought it was time to learn something about images. Bob Powell's site was great because it gave me what i needed: several samples and FAQs that explained several aspects related with GDI+ and graphics - it even a sample on how to convert an image to a 1bpp image (which is really the only kind of image i was interested in).
Going back to the command help, it said that p3 was the width of an image in bytes. After going through Bob's FAQ, it was clear that after creating a Bitmap you could call the LockBits method to lock the bitmap in memory. When you do that, you end up receiving and instance of BitmapData which has several members that are important for getting the info I needed to send to the printer. For instance, the class exposes a property called Stride which returns the correct length in bytes of each line of the bitmap image (right what was needed for the p3 parameter!). The stride is important because without it you don't know where each line of the bimap starts and ends (don't forget that, in memory, the bitmap is just a plain array- Bob explains it well, so go there to get more info on what I mean). Before you start thinking that getting the stride is a waste of time because you already know the width of the image in pixels, don't forget that you also need to know the pixel format you're using in order to get that width in bytes(for instance, in my case I got lucky because I only needed to print 1bpp images). Another thing you need to keep in mind is that the stride will always be equal or bigger than the width of the image in bytes. That's because strides are always multiples of 4 bytes (at least, in 32 bits machines). If you're really lucky, you'll get images whose width (in bytes) is also a multiple of 4. If that doesn't happen, then you do need to write more code to get the correct result printed in the Zebra. But I'll speak about this in a minute.
So, since I had all the info I needed, it was time to write the Print1bppImageCommand. I'm only showing the GetCommandString method:
BitmapData bits = _bmp.LockBits(new Rectangle(0, 0, _bmp.Width, _bmp.Height),
byte imageBytes = new byte[bits.Height*bits.Stride];
Marshal.Copy(bits.Scan0, imageBytes, 0, bits.Stride*bits.Height);
int imgWidth = bits.Stride;
int imgHeight = bits.Height;
As you can see, it's really simple: After locking the bitmap, I create a new byte array and use the Marshal.Copy method to copy all the image bits into that array. Yes, I could have optimized the code and worked with direct pointers. I'll leave that to you though ;)
what's important to note is that I use the stride and the height of the bitmap to get the correct size of the buffer used to hold the bytes of the image. Another important thing: I'm using the 1252 encoding in order to copy the byte array to the string that is going to be sent to the printer (if you try to use ASCII, it simply won't work - I guess that I could try to use the ANSII encoding - value 0, if i'm not mistaken - but since it worked well with this, I just went along and used it).
The 1st image I've used printed without any problems...since "I was the king of the world", I decided to run a small demo for fellow workers. Guess what: I decided to choose another image and after printing it, i noticed a weird black bar on the right. what could it be? Remember me saying that the stride is always bigger or equal to the image width (in bytes)? Yep, that was the problem. You see, the 1st image was a "good one" because the stride was equal to its width. That didn't happen in the 2nd case. In my case, solving this problem is easy. Since I'm printing 1bpp images and i was sure that their size was a multiple of 8 (since I'm using 1 bit per pixel image, that means that 8 pixels will be stored in a byte), I knew that i only needed to "clear" those extra bytes. In this case, clearing means setting the color to white (which really means setting the byte in the array to 255). That's why I inserted the following code before the string.Format:
int realWidth = bits.Width/8; //only works for fixed size 1bpp images where width % 8 == 0
if( realWidth != imgWidth )
int bytesToClear = imgWidth - realWidth;
for( int i = 0; i < imgHeight; i++ )
int pos = realWidth + imgWidth * i;
for( int counter = 0; counter < bytesToClear; counter++, pos++ )
imageBytes[pos] = 255;
And that's it! My 1bpp image is getting correctly printed. If you have a colored image, then you need to go to Bob's site and see how you can convert it to a 1bpp pixel. Btw, note that if you do need to do that, you might also need to clear individual "bits" (instead of bytes).
After finishing these commands, I only needed to create the Label base class that is responsible for letting you define several ICommands which can be sent to the printer.
After having this code working, it was only a matter of building the correct code for sending it to the printer. This time, I got lucky and found this article in codeproject. I just needed to change it slightly so that it used the same enconding I was using to save the image bytes in the string that is going to be sent to the printer.
You can download my wrappers and demo code from here.