Sending a bit image to an Epson TM-T88III receipt printer using C# and ESC/POS

At Skiviez/WFS, our “low-volume fulfillment requests” (which usually simply means “retail orders”) get a simple receipt printed out via an Epson TM-T88III receipt printer.

TM-T88III Receipt Printer

TM-T88III Receipt Printer

You’ve probably seen the TM-T88III before, though the current model is the TM-T88IV–they’re ubiquitous and especially common in restaurants. We chose it because the receipt prints quickly (which reduces the probability that a stack of order receipts gets missorted such that an employee puts the wrong receipt in the wrong parcel), is thermal so there is no ink to replace (which reduces the cost of consumables), uses standard thermal receipt paper (which is cheap, and we can always run out to Staples if we run out), and, perhaps most importantly, we found one in a box in the back of the warehouse.

At the top of the retail receipt, I print out the Skiviez logo. This is stored in a non-volatile area of memory in the printer (called NV RAM in EPSON parlance), and I dole out a command that essentially says “print out the image stored in slot #1,” having previously uploaded the bitmap via the printer driver’s flash utility. This worked great for as long as we only shipped Skiviez orders and only had one TM-T88III printer. But with the advent of WFS, however, Skiviez now ships orders (we call them “fulfillment requests”) for multiple e-commerce stores, each requiring a different logo to appear at the top of the receipt. So I now had two options:

  • I could manually upload the logos for all of the merchants that we ship for into all of the receipt printers that we use, making sure to upload each merchant image into the same NV memory slot for each printer.
  • I could store the merchant’s logo in our database, figure out how to use the “select bit image mode” receipt printer command, and just send the image data just-in-time whenever I generated a receipt.

The second option has obvious maintenance benefits, so that’s the path I started hacking my way down.

A brief primer on ESC/POS and data

Like the Zebra LP2844 and its brethren, the TM-T88III comes with a Windows printer driver that allows Windows and applications to see it as any other GDI-based printer. You can print a Word document to the TM-T88III and it will print, albeit slowly, and it’ll look like crap. That’s because, as with any other GDI printer, the Word document gets rendered as a bitmap and the entire big-ass bitmap gets sent to the poor little receipt printer which then prints it 24 dots at a time. A better method is to use the printer for its intended purpose and use the native printer language to generate the receipt. This allows us to select printer fonts that are specifically designed for the printer’s resolution and speed characteristics and use other advanced features of the printer such as the paper cutter.

That printer language is called ESC/POS, which is entirely unpronounceable and a stupid name. The “POS” stands for “Point of Sale” since this printer belongs to a class of devices commonly known as point of sale devices–cash drawers, VFDs, barcode scanners, and the like. The “ESC” stands for “escape” because the printer treats any data that is sent to it as text–passed straight through–unless it is escaped with a special character to indicate that a command instruction follows in the input stream. The most common escape character used is the ASCII ESC character, and so we have “ESC/POS” as our language name.

If you’ve just picked up one of these printers on eBay, you may find that getting documentation on this language is hard to come by because you need to get a copy of the “ESC/POS Application Programming Guide” from your “EPSON Authorized Dealer,” whoever the hell that is. (Again, we found ours in a box in the back of the warehouse, so my “authorized dealer” is long gone.) Still, some Googling will give you more or less complete and current copies of these guides, which I’ve mirrored here. Go ahead and download them:

(I particularly like how the programming guide has “proprietary” and “confidential” stamped all over it and then proceeds to describe how great EPSON is for “taking initiative” for “expandability” and “universal applicability” on page 7 of the same document. You can’t make this crap up–it’s full of oxymorons.)

Having just come from writing in the EPL2 printer language for our Zebra LP2844 and Zebra ZP 500 Plus thermal label printers, I found the ESC/POS language to be confusing. My confusion mostly stemmed partly from the way the documentation was written and partly because I was used to the way EPL2 worked. Let’s take a look at the documentation for the bit image command that I’m trying to use:

Documentation Snipped

If this were EPL2, I would actually send the document as a string to the printer, so if a command needed the integer 33 as a parameter, I would send the string “33″ (two bytes of data). In ESC/POS, each parameter is a single byte, so if I want to send 33 as a parameter for m in the bit image command above, then I need to send 33 in a single byte: that is, 0b100001, which is 25 + 20 = 32 + 1 = 33.

The reason that this is super confusing is that other parameters in the documentation are specified to be ASCII characters, such as the ‘*’ parameter in the bit image command above. This is because low-level programmers, such as those who designed the ESC/POS language, tend to blur the lines between data types: it’s all bytes at the end of the day. As a result, if you’re using C#, you might be tempted to use a StringBuilder to build up your document to send to the printer. Don’t do it! You’ll inevitably get confused by its overloads. Let’s take that m = 33 parameter as an example:

var sb = new StringBuilder();
 
// ASCII escape
sb.Append((char)27);
 
// ASCII '*' for the bit image command
sb.Append('*');
 
// oops! this appends the string "33" in two bytes, doesn't work
sb.Append(33);
 
// this is what I want, but semantically weird
sb.Append((char)33);

A much better and less confusing in the long run solution is to eschew any notion that we are dealing with text. Instead, let’s use the semantics that we are sending bytes of raw binary data directly to the printer:

using (var ms = new MemoryStream())
using (var bw = new BinaryWriter(bw))
{
     bw.Write(AsciiControlChars.Escape);
     bw.Write('*');         // bit-image mode
     bw.Write((byte)33);    // 24-dot double-density
}

But remember that cast to byte! Otherwise, you’ll get a C# int, which is four bytes, written to the stream, when we only wanted one. Yes, that means that any parameter that an ESC/POS command takes has a maximum value of 255.

You could get by on the StringBuilder method for a while and not have it burn you until you try the bit image command because all the characters that you’re appending happen to be less than 128–greater than that and you start getting into bytes that don’t map to a Unicode character for quirky historical reasons, such as the range 128 to 159, and you’ll be pulling your hair out as to why some of your data is getting “lost.” Just use the BinaryWriter method. You can thank me later.

Why is the bit image command designed this way?

The m parameter in the bit image command as defined in the previous section has 4 values, and each value changes the way we construct the image data bytes and the way the printer interprets them. The first three values are of dubious value, so let’s throw them out for now. We’ll focus on m = 33, which means “24-dot double density mode.” So keep this in mind and set this aside.

When we think of drawing a bitmap in high level software, we usually think of drawing it pixel by pixel, left to right, top to bottom. So the order that the dots get printed as indicated by the programming guide seems strange:

Relationship between image data and the print result

That is, the dots are rendered from top to bottom, then left to right.

Why in the blue hell do I need to rearrange my nice and neat array of bitmap data into this Connect Four scheme of dots? It all comes down to the size of the print head.

For our mental model, the thermal print head in the receipt printer is physically 1 dot wide by 24 dots high where there are 203 dots per inch (square pixels). It moves left to right across the receipt paper, burning up to 24 dots in the vertical (y) direction for each dot in the horizontal (x) direction.

Since we’re talking about a thermal printer here, there’s no concept of gray scale tones–we’ve either burned a dot into the paper (black) or we haven’t. So if we’re sending image data to the printer in bytes, it would make sense to say that a bit set to 1 means “burn a dot” and a bit set to “0″ means do nothing.

But a byte is only 8 bits wide, and we have 24 dots to burn in each “slice” of the image. 24 just happens to be divisible by 8, so we can send 3 bytes of data for each slice to represent our 24 dots. (If the bitmap I want to draw is taller than 24 dots/pixels, then I need to send multiple bit image commands, effectively doing multiple stripes of the image that are 24 dots high; more on this later.)

So, now, the diagram in the programming guide makes sense: we’re not burning 1 dot at time, we’re burning a vertical stripe that is 1 dot wide and 24 dots high at a time, and then moving to the right. So our printer reads our first 3 bytes of image data, burns the dots specified by those 24 bits, then reads the next 3 bytes, and so on.

Aside: Converting the bitmap to monochrome

Right. So a merchant has given me a *.bmp file, and I need to convert that into array of bits that I can send to the receipt printer. It’s a good bet that the bitmap that the merchant sent me for the logo is not monochrome (that is, every pixel is either 100% black or 100% white).

So what we can do is look at each pixel in the bitmap, determine its luma, and if that’s below a certain threshold, count that pixel as black. How did I write the code to do this? I looked the formula up via the intertubes and modified it to fit my needs:

private static BitmapData GetBitmapData(string bmpFileName)
{
    using (var bitmap = (Bitmap)Bitmap.FromFile(bmpFileName))
    {
        var threshold = 127;
        var index = 0;
        var dimensions = bitmap.Width * bitmap.Height;
        var dots = new BitArray(dimensions);
 
        for (var y = 0; y < bitmap.Height; y++)
        {
            for (var x = 0; x < bitmap.Width; x++)
            {
                var color = bitmap.GetPixel(x, y);
                var luminance = (int)(color.R * 0.3 + color.G * 0.59 + color.B * 0.11);
                dots[index] = (luminance < threshold);
                index++;
            }
        }
 
        return new BitmapData()
            {
                Dots = dots,
                Height = bitmap.Height,
                Width = bitmap.Width
            };
    }
}

BitmapData is just a little struct that contains the three properties that you see here. After all, once I have my BitArray with 1 indicating black dots and 0 indicating white dots, I don’t need the original Bitmap instance anymore.

Just to clarify, this means that we’re holding onto a BitArray of monochrome data in the Dots property that conceptually looks something like this:

Conceptual Overview

Marshalling the monochrome data

Now for the trickiest part of all. We need to take our BitArray of monochrome data, divide it up into 8-dot chunks, represent those 8-dot chunks as bytes, and send them to the printer in the order required by the bit image command, discussed above. That is, I’ll need to send the data as bytes in this order:

Capture

So the printer will draw them one vertical stripe of three bytes each at a time. (If you don’t get it, print out that image, cut up the bytes, and then arrange them in the order shown in the diagram from the EPSON documentation. It’ll instantly make sense once you see it.) The X’s in the diagram correspond to bits that aren’t in our original image that we have to send anyway as padding. Remember, since we selected the 24-dot double density mode, the printer is going to draw a 1 x 24 dot slice as it moves the print head. My example bitmap is only 4 pixels tall, so I have to send 20 zero bits as padding.

And thus we would end up with our pretty image.

Unfortunately, there is some math involved in translating the bits in our BitArray into these bytes that we need to send. Assuming bw is our reference to a BinaryWriter, here’s the code that does just that:

// So we have our bitmap data sitting in a bit array called "dots."
// This is one long array of 1s (black) and 0s (white) pixels arranged
// as if we had scanned the bitmap from top to bottom, left to right.
// The printer wants to see these arranged in bytes stacked three high.
// So, essentially, we need to read 24 bits for x = 0, generate those
// bytes, and send them to the printer, then keep increasing x. If our
// image is more than 24 dots high, we have to send a second bit image
// command to draw the next slice of 24 dots in the image.
 
// Set the line spacing to 24 dots, the height of each "stripe" of the
// image that we're drawing. If we don't do this, and we need to
// draw the bitmap in multiple passes, then we'll end up with some
// whitespace between slices of the image since the default line
// height--how much the printer moves on a newline--is 30 dots.
bw.Write(AsciiControlChars.Escape);
bw.Write('3'); // '3' just means 'change line height command'
bw.Write((byte)24);
 
// OK. So, starting from x = 0, read 24 bits down and send that data
// to the printer. The offset variable keeps track of our global 'y'
// position in the image. For example, if we were drawing a bitmap
// that is 48 pixels high, then this while loop will execute twice,
// once for each pass of 24 dots. On the first pass, the offset is
// 0, and on the second pass, the offset is 24. We keep making
// these 24-dot stripes until we've run past the height of the
// bitmap.
int offset = 0;
 
while (offset < data.Height)
{
    // The third and fourth parameters to the bit image command are
    // 'nL' and 'nH'. The 'L' and the 'H' refer to 'low' and 'high', respectively.
    // All 'n' really is is the width of the image that we're about to draw.
    // Since the width can be greater than 255 dots, the parameter has to
    // be split across two bytes, which is why the documentation says the
    // width is 'nL' + ('nH' * 256).
    bw.Write(AsciiControlChars.Escape);
    bw.Write('*');         // bit-image mode
    bw.Write((byte)33);    // 24-dot double-density
    bw.Write(width[0]);  // width low byte
    bw.Write(width[1]);  // width high byte
 
    for (int x = 0; x < data.Width; ++x)
    {
        // Remember, 24 dots = 24 bits = 3 bytes.
        // The 'k' variable keeps track of which of those
        // three bytes that we're currently scribbling into.
        for (int k = 0; k < 3; ++k)
        {
            byte slice = 0;
 
            // A byte is 8 bits. The 'b' variable keeps track
            // of which bit in the byte we're recording.
            for (int b = 0; b < 8; ++b)
            {
                // Calculate the y position that we're currently
                // trying to draw. We take our offset, divide it
                // by 8 so we're talking about the y offset in
                // terms of bytes, add our current 'k' byte
                // offset to that, multiple by 8 to get it in terms
                // of bits again, and add our bit offset to it.
                int y = (((offset / 8) + k) * 8) + b;
 
                // Calculate the location of the pixel we want in the bit array.
                // It'll be at (y * width) + x.
                int i = (y * data.Width) + x;
 
                // If the image (or this stripe of the image)
                // is shorter than 24 dots, pad with zero.
                bool v = false;
                if (i < dots.Length)
                {
                    v = dots[i];
                }
 
                // Finally, store our bit in the byte that we're currently
                // scribbling to. Our current 'b' is actually the exact
                // opposite of where we want it to be in the byte, so
                // subtract it from 7, shift our bit into place in a temp
                // byte, and OR it with the target byte to get it into there.
                slice |= (byte)((v ? 1 : 0) << (7 - b));
            }
 
            // Phew! Write the damn byte to the buffer
            bw.Write(slice);
        }
    }
 
    // We're done with this 24-dot high pass. Render a newline
    // to bump the print head down to the next line
    // and keep on trucking.
    offset += 24;
    bw.Write(AsciiControlChars.Newline);
}
 
// Restore the line spacing to the default of 30 dots.
bw.Write(AsciiControlChars.Escape);
bw.Write('3');
bw.Write((byte)30);

This code looks confusing, and is, but I’ve tried to document it with explanatory comments in line.

Sending the document to the printer

Finally, there is the task of sending this array of bytes directly to the printer. I’ve talked about this before and the code is uninteresting, requiring some P/Invokes into native APIs. Essentially, you pass in the name of the printer as it appears in the Control Panel, the array of bytes from our BinaryWriter, and you’re all set:

private static void Print(string printerName, byte[] document)
{
    NativeMethods.DOC_INFO_1 documentInfo;
    IntPtr printerHandle;
 
    documentInfo = new NativeMethods.DOC_INFO_1();
    documentInfo.pDataType = "RAW";
    documentInfo.pDocName = "Bit Image Test";
 
    printerHandle = new IntPtr(0);
 
    if (NativeMethods.OpenPrinter(printerName.Normalize(), out printerHandle, IntPtr.Zero))
    {
        if (NativeMethods.StartDocPrinter(printerHandle, 1, documentInfo))
        {
            int bytesWritten;
            byte[] managedData;
            IntPtr unmanagedData;
 
            managedData = document;
            unmanagedData = Marshal.AllocCoTaskMem(managedData.Length);
            Marshal.Copy(managedData, 0, unmanagedData, managedData.Length);
 
            if (NativeMethods.StartPagePrinter(printerHandle))
            {
                NativeMethods.WritePrinter(
                    printerHandle,
                    unmanagedData,
                    managedData.Length,
                    out bytesWritten);
                NativeMethods.EndPagePrinter(printerHandle);
            }
            else
            {
                throw new Win32Exception();
            }
 
            Marshal.FreeCoTaskMem(unmanagedData);
 
            NativeMethods.EndDocPrinter(printerHandle);
        }
        else
        {
            throw new Win32Exception();
        }
 
        NativeMethods.ClosePrinter(printerHandle);
    }
    else
    {
        throw new Win32Exception();
    }
}

The P/Invoke declarations can be found in the sample code at the end of the article, and the documentation is available on MSDN. But, again, there’s nothing sexy going on here.

Conclusions and delusions

And that’s a wrap. I always find it interesting when I encounter a problem at work–which is a small business by any definition of the phrase–and the Google indicates that no one has ever encountered or documented this problem before. And it’s these types of integration problems that I find fascinating; after years of playing in castles in the sky, dealing with databases and exceptions and HTML forms, it’s nice to know that I can dust off the corners of my brain and my college degree and deal with bits and bytes when I need to. Writing software that makes hardware do stuff is always fun, too.

And after hours of watching the receipt printer spew out lines and lines of gibberish, there’s nothing like the feeling of seeing the bitmap print out and knowing that, finally, things have simply started to work.

Good luck!

Download the code used in this article.

Update 3/30/2012: Chris M. has provided a Delphi translation.

71 thoughts on “Sending a bit image to an Epson TM-T88III receipt printer using C# and ESC/POS

  1. Hi, i’m really happy about this document. After study the specifikation , i suspect, that i have to find out and test this part by my own. So i can take this code and transform this to VB.NET. This way is definitly fault-safe as implementing this without this base. Thanks for that. Best regards Jane Dierks-Higeist

  2. Great!

    I can do it if the bmp is less than 576 pixel width

    if the bmp is 945 pixels width and the maximum dots of 24 bits mode double density is 576, how can i adjust to fit the paper?

    P.S. if i change the bmp to less than 576 pixel width before printing, the result is not good.

    Thanks.

    • @kay

      As you’ve guessed, you’ve only got so many dots in the horizontal direction to deal with. There’s not much you can do if you have a gigantic image: you can truncate it or you can resize it to fit.

      The only recourse really is to always send a bitmap that is fewer than 576 pixels wide; for example, I restrict my application so that it only accepts bitmaps that are 450 x 450 pixels or smaller.

      Instead of doing the resize operation within your application using GDI, I’d recommend that the source bitmap be resized in an image editing program like Paint.NET or Photoshop.

      Good luck!

  3. Great code :-)

    It works for me. but one small thing, how do I print chars such as ø æ å which are danish? they come out strange :-(

    How about things like, print different font, align, how do I set that??

    • @Joseph

      Download the ESC/POS Programming Guide linked to this article. It contains commands for both alignment and bolding and such.

      As for printing Danish characters, well, the printer only has so much memory available to it at any given time. So to print these characters, you have to tell the printer to load a different “code page,” or set of characters, into memory. The same Programming Guide discusses these code pages in the appendix at the end of the document. You select them using the ESC R command.

  4. First of all, this is great stuff, Nicholas!

    I’m transcribing your code to Delphi (5 – don’t ask) and I think I’ve gotten everything right up to the point of “slice”. I’m not a C# programmer, but can pretty much translate things easily… Except (I think):

    slice |= (byte)((v ? 1 : 0) << (7 – b));

    Could you or any of your learned readers translate the above into Delphi? Again, I think I've gotten everything transcribed correctly as I've tested my "dots" array and the For loops are straightforward.

    Incidentally, I'm familiar with Epson printers and I'm doing this (a signature captured into a monochrome bmp and passed to me via a filename) to print on a receipt.

    I'm just getting garbage and I'm pretty sure my translation of the above code is screwed up.

    Thanks for any help, Craig

    • @Craig

      Don’t know squat about Delphi, unfortunately. The best I can do is describe in English:

      We have our working copy of a byte that we’re munging with in a variable called ‘slice’. I have a Boolean named ‘v’ that tells me if I want to shove a 1 or a zero into this byte. So if v is true, I take the integer 1 and shift it (7 – b) units to the left. (I think in Delphi this is ‘Shl’?) If it’s a zero, well, I don’t have to do anything actually but there is not harm in “shifting zero” in a one-liner. Then I bitwise OR my shifted integer with the ‘slice’.

      So if ‘slice’ looked like ’01000000′ and I’m working on b = 3, then I take ’00000001′, shift it to the left (7 – 3) == 4 places to get ’00010000′, and then I OR that number (’00010000′) with the slice (’01000000′) to get ’01010000′. And then I keep on trucking. (If it was zero, I would have shifted zero and then OR it with the slice, effectively doing nothing.)

      Hope that helps!

  5. Wow, that was totally fast!

    OK, on closer inspection, I think I got it (the translation) correct. There are “parts” of the bitmap that are showing up within the gibberish. What I think is happening is that I’m testing by writing directly to the printer without proper IO checking; i.e. I’m not (yet) using our (house written) IO routines that check for a _OUT_Q_FULL and the like. I think I was a little to anxious having gotten the Dots array right off the bat.

    And yes, your response does help verify what I did to replicate the C# code correctly. I had to “dumb down” (for me) each separate process and put them on a separate statements. I did this to assure myself the Delphi compiler wasn’t doing something I hadn’t intended and for my own peace of mind:

    C#:

    slice |= (byte)((v ? 1 : 0) << (7 – b));

    Dumb (Craig) Delphi:

    smb := 7 – b; //smb is Seven Minus b, declared as byte holdtv := trueval(v); //trueval is a function that returns 1 if v is true, 0 otherwise. holdtv declared as byte hold := holdtv shl smb; //Yes, shl is Delphi shift left logical slice := slice or hold; //Final OR

    I declared everything as type byte so I wouldn't have to cast as well as doing mixed mode operations, and I could (can) check results at each step in debug.

    I can't thank you enough, Nicholas, as I was given this hi-priority project a couple of days ago and your excellent post was (is) a godsend!

    Craig

    • @Craig

      Glad to hear it. For your random gibberish problem, you might check to make sure that you’re not writing any of your data to some kind of string-based buffer before sending it to the printer. The reason is that some of the bytes that we may be constructing to represent our bitmap might not actually correspond to representable Unicode or ASCII characters, so they might get “baked” wrong if they were added to the .NET equivalent of a string or a StringBuilder. (Most likely, they will just vanish.) The solution for me was to keep it bytes the whole way through:

      You could get by on the StringBuilder method for a while and not have it burn you until you try the bit image command because all the characters that you’re appending happen to be less than 128–greater than that and you start getting into bytes that don’t map to a Unicode character for quirky historical reasons, such as the range 128 to 159, and you’ll be pulling your hair out as to why some of your data is getting “lost.” Just use the BinaryWriter method. You can thank me later.

      I also saw some random gibberish when using .NET’s BinaryWriter because one of its Write() overloads for the string type would write the length of the string to buffer, resulting in a “weird character” at the end of the each string.

      Hope that helps.

  6. FYI - I didn’t fully understand (“grok”, in geek terms) the nL and nH along with the C# width[0] and width[1]. Now I do.

    At last, the Eureka Moment!

    It Simply Works!

    Thanks, Craig

  7. Hi, Would highly appreciate if I could have a C# example of printing something on TMT88III printer that includes: 1.Send a string to the printer 2.Print and cut

    thanks and regards

  8. Hi,

    Craig, I read that you trancribed the c# code to delphi, could you send this code to make some test, i need to do the same, i would be thankful for your support

  9. Hi Nicholas, Stumbled upon this. excellent code. Can you help me out with some printing issues I am having, I am in a similar small business model as you are, and I am sure you have encountered my problems before. Thank you.

  10. Hi Nick,

    Really Wonderful blog, Even we are facing many problem in printing to TM L90 Epson Printer. We wanted to print the barcode, Getting printer status and Storing and printing images in NVRam using ESC / POS commands, Image printing is working gr8 .

    Thank You,

  11. Hi, This blog is very useful for printing image to printer.Thanks a lot,can you please help me in printing barcode to printer. I am using the following commands for printing barcode but not working,instead it prints empty space.

    Code:

            bw.Write((char)0x1D); //GS
            //font selection
            bw.Write((char)0x66); //font
            bw.Write(0); //font A

        //set position of barcode
        bw.Write(GS);
        bw.Write((char)0x48); //H
        bw.Write(2);
    
        //set width
        bw.Write(GS);
        bw.Write((char)0x77); //w
        bw.Write(6);
    
        //set height
        bw.Write(GS);
        bw.Write((char)0x68); //h
        bw.Write(100);
    

    bw.Write(GS); bw.Write((char)0x6B); //K bw.Write(2); //m =2 :Ean13 barcode bw.Write(13); //K = 12,13

    //barcode data bw.Write(48); bw.Write(49); bw.Write(50); bw.Write(51); bw.Write(52); bw.Write(53); bw.Write(54); bw.Write(55); bw.Write(56); bw.Write(57); bw.Write(50); bw.Write(51); bw.Write(50);

            bw.Write((char)0x00); //null
    

  12. Hi, Can you please help me if i am missing anything in the following code for printing barcode to Epson TM-L90P printer in C# .

    Code:

    bw.Write((char)0×1D); //GS //font selection bw.Write((char)0×66); //font bw.Write(0); //font A

    //set position of barcode bw.Write(GS); bw.Write((char)0×48); //H bw.Write(2);

    //set width bw.Write(GS); bw.Write((char)0×77); //w bw.Write(6);

    //set height bw.Write(GS); bw.Write((char)0×68); //h bw.Write(100); bw.Write(GS); bw.Write((char)0×6B); //K bw.Write(2); //m =2 :Ean13 barcode bw.Write(13); //K = 12,13

    //barcode data bw.Write(48); bw.Write(49); bw.Write(50); bw.Write(51); bw.Write(52); bw.Write(53); bw.Write(54); bw.Write(55); bw.Write(56); bw.Write(57); bw.Write(50); bw.Write(51); bw.Write(50);

    bw.Write((char)0×00); //null

    Mail Id : santosh.rachakonda@in.tesco.com

  13. HI Nicholas,

    I am trying to print korean text using ESC /POS command but it prints some ascii characters

            bw.Write((byte)28);
            bw.Write((byte)43);
            bw.Write((byte)0); // FS C 0
            bw.Write((byte)28);
            bw.Write('&amp;');     // FS &amp;
            bw.Write((byte)28);
            bw.Write('!');
            bw.Write((byte)4); // FS ! 0
            bw.Write("양용준");  // Korean Text
    

    Please let me know where is the problem in the above code. Mail ID : prashanthmys@gmail.com

  14. Hi, I am trying to rotate barcode ,and gone through various blogs and documents but did not find any command for rotating the barcode.I found the following command but it is for only to rotate text and anynhow i tried and found not working for Barcode .

    Code:

            //rotate barcode
            bw.Write(AsciiControlChars.Escape);
            bw.Write('V');
            bw.Write((byte)2);

        //set position of text on barcode:    above,below
        bw.Write(GS);
        bw.Write((char)0x48); //H
        bw.Write((byte)1);
    
        //set height of bar code
        bw.Write(GS);
        bw.Write('h');
        bw.Write((byte)100);
    
        //set width of bar code
        bw.Write(GS);
        bw.Write('w');
        bw.Write((byte)2);
    
    
    
        //print bar code
        bw.Write(GS);
        bw.Write('k');
        bw.Write((byte)65);
        bw.Write((byte)12);
        bw.Write(Encoding.ASCII.GetBytes(barcode));
    

  15. Thanks for the code. I translated it to Delphi and its works well. Except for one thing your rendering is more clear executng your c# code than my delphi code. Curiously I analyse all the output and everything is identic. Is there something else I should know. Thanks

  16. Hello Very useful information i made a few modification and i am able to print to an Custom (KPM300H) thermal printer, do you kno how to print to the buffer or to upload a logo to the printer memory the documentation is very difficult and i can’t understand it very well one of the command that i am talking is GS * (1D 2A x y d1 … d(X x Y x 8) )

    Thanks

    • Hi Robert!

      I’m struggling also with Custom TK300 printer. I can’t get it to work (prints rubbish). Could You show me the modification that You maded to get this working? Thanks

  17. Hi, I have a TM-T88II printer and I want to cut paper from an application in Delphi. Could you send me some samples of how to do this?

    Thank you

  18. Thank you for the example of print bitmap images. I want to clarify about the fact that wrote Nicholas Piasecki: ” I also saw some random gibberish when using. NET’s BinaryWriter because one of its Write () overloads for the string type would write the length of the string to buffer, resulting in a “Weird character” at the end of the each string. ” In the example, Nicholas: bw.Write (“CASE 1″) is entered into buffer to first byte 6 – number of characters in the string, and after a byte characters themselves – 67 (‘C’), 65 (‘A’), 83 (‘S ‘), 69 (‘ E ‘), 32 (‘ ‘) and 49 (’1′). And if the string of characters long, then the “Weird character” is a character code – that corresponds to the number of characters per line. bw.Write (Encoding.GetEncoding (0). GetBytes (“CASE 1″)) saves us from “Weird character”, because that is in the buffer are recorded only byte characters themselves.

  19. hi i AM USING THE THERMAL PRINTER OF 2″ PRINTER AND I WANT A CONVERTER TOOL FOR THE BIT IMAGE TO CONVERT TO C CODE IN TURBO C IF U HAVE ANY TOOL PLEASE FORWARD IT TO MY.THANKS

  20. Excellent article. Sometimes I cant believe that a common task like this can just be explained in a single blog on the entire internet :)

    I have a question related to this:

    I noticed that when printing a logo it takes like 2 or 3 seconds and the printhead is heard to move more.

    Am I doing something wrong or printing directly has its limitations? Can a logo be flashed via code?

    Thanks in advance

    • @Emilio

      I don’t know much — AFAIK, the speed of printing the image is dependent on the darkness setting and the printer model. (For example, a TM-88IV will run circles around the TM-88III, even though they look the same and speak the same language.) It’s possible to flash an image via code, but I haven’t done it. It’s easier just to just the flash utility supplied with the Epson printer driver.

  21. After hours of fruitless searching – I hit gold!

    Brilliant piece of work – I had been resigned to loading our company logo on over a 1000 printers, then calling the NV data prior to receipt printing. This method ‘centralises’ the whole process for me.

    Thanks again

    Peter

  22. hi,

    i have no experiance with bitmap, but now im doing a project with the vkp80ii escp/pos printer in c# microframework, the problem is i dont know where to find the values to put in ‘nL’ and ‘nH’ is it the size of my image? should i save the image in a specific format? can youi help me please?

  23. I found Nicholas’s post and I programmed a “C” program to mimic the logic. It failed in 2 ways. I had to read my bitmap files bottom up or reverse of what was in the example. Also, the logo has minor shifts or fuzzy borders due do some slight error in dot placements. Spent about 12 hours on this and feel like I am in the twilight zone. Any reasons for the reverse order and clues why I am getting unclean borders.

  24. fantastic!, I can now print graphics to my printer. The only problem is it is slow and not really viable in a shop environment. I am trying to upload bitmaps to the NV area as these render much quicker. I have used the logo upload tools, but I have hundreds of them to do and am looking to a database solution that will tie the NV areas to the graphics and keep everything in place across the organisation. I have been trying to use the FS q n Command but am having a fustrating time! Has anybody done this in a similar vein to nicholas?

  25. I have successfully worked out how to upload a bitmap into the NV ram for rapid printing with the FS p n command by reworking Nicholas’s code.

    the problems were that the data is stored a bit differently and the dimensions (xl xh & yl yh) are required in bytes and not pixels (as per direct print method)

    The code is available and if Nicholas wants I will send it to him for download off this site.

    • Lance, can you plese send me the sample code to upload bitmap to NVRam and printing it using FS p n command, I’m using VS2010. This is exactly what I needed and had been searching for it everywhere

  26. Ditto that. I’m looking to programtically save the image to the NV ram. Would love to see the code that Lance has doen for this. Thanks.

  27. I’ve found the answer. Refer to my thread on stackoverflow.

    BTW, page more printing is a lot smoother and far less noisier IMHO.

  28. I know this is an older thread but It saved me from going insane. I tried everyway in the world to understand this feature for these printers and the books are terrible. Thanks so much for posting this. I converted your function to Delphi and added a few enhancements to the function capabilities.

    Here is the delphi function with usage example below it. I hope this helps someone out there…Enjoy!!

    FUNCTION

    function BitmapToAPAGraphics(const bmp:TBitmap; const RowHeightEscapeStr:String; const SliceEscapeStr:String; const BitsPerSlice:byte = 8):String; const threshhold = 127; CR = #13; LF = #10; TYPE TBitArray = array of boolean;

    TRGBTripleArray = ARRAY[Word] of TRGBTriple;
    pRGBTripleArray = ^TRGBTripleArray; // Use a PByteArray for pf8bit color.
    

    var iCol,iRow,index, sliceIndex, bytePos, bitPos, offset,luminance: integer; line: pRGBTripleArray; Pixel: TRGBTriple; tmpFile: TextFile;

    dots: TBitArray;
    slice,bit,tmpBit,bytesPerSlice, hold: byte;
    bVal: Boolean;

    p:pRGBTripleArray;

    begin result := ”;

    if not Assigned(bmp) then exit; if SliceEscapeStr = ” then exit;

    try bmp.PixelFormat := pf24bit;

    SetLength(dots, (bmp.Height * bmp.Width));

    index := 0;

    //1) Loop the bitmap scanlines and build a bitArray from each pixel for iRow := 0 to bmp.Height-1 do begin line := bmp.Scanline[iRow];

    for iCol := 0 to bmp.Width-1 do
      begin
        Pixel := line[iCol];
        luminance := Trunc((Pixel.rgbtRed * 0.3) + (Pixel.rgbtGreen * 0.59) + (Pixel.rgbtBlue * 0.11));
        dots[index] := (luminance &lt; threshhold);
        inc(index);
      end;
    

    end;

    offset := 0;

    //Convert BitsPerSlice to byte count in each slice bytesPerSlice := (BitsPerSlice div 8);

    //if there is a remainder, we need another byte added (9 bits is 2 bytes, etc...) if BitsPerSlice mod 8 > 0 then inc(bytesPerSlice);

    result := result + RowHeightEscapeStr;

    //Loop height steping down by offset while offset < bmp.Height do begin

    result := result + SliceEscapeStr;
    
    //Loop width of bitmap
    for iCol := 0 to bmp.Width-1 do
      begin
    
        //Loop each byte from current slice
        for sliceIndex := 0 to bytesPerSlice -1 do
          begin
            slice := 0;
    
            //Loop each bit from current byte from current slice
            for bit := 0 to 7 do
              begin
                bytePos := (((offset div 8) + sliceIndex) * 8) + bit;
                bitPos := (bytePos * bmp.Width) + iCol;
    
                bVal := FALSE;
                if bitPos &lt; Length(dots) then
                  bVal := dots[bitPos];
    
                //Delphi has no ternary operator so we have to compensate
                if (bVal) then
                  tmpBit := 1
                else
                  tmpBit := 0;
    
                slice := slice or (tmpBit shl (7 - bit));
    
              end;
            result := result + chr(slice);    
          end;
      end;
    
    //Step down to next slice
    inc(offset,BitsPerSlice);
    result := result + CR+LF;
    

    end;

    finally dots := nil; end;

    end;

    USEAGE:

    function PrintBitmap: Boolean; var prnBuffer,RowHeightEscapeStr,SliceEscapeStr :String; myBmp:TBitmap;

    begin if FileExists(‘signature.bmp’) then begin

              myBMP := TBitmap.create;
              try
                  myBMP.LoadFromFile('signature.bmp');

              RowHeightEscapeStr := #27+ #49;
    
              nH := Hi(myBMP.Width);
              nL := Lo(myBMP.Width);
    
              SliceEscapeStr := #27+#42+#50 + chr(nL)+ chr(nH);
    
              prnBuffer := prnBuffer + #13+  G.BitmapToAPAGraphics(myBMP,RowHeightEscapeStr,SliceEscapeStr,8);
    
         finally
              myBMP.Free;
         end;
    
     end;
    

    end;

  29. Great stuff Nicholas! I was making traction on my own until I hit some obstacles and decided to search. Note to self: search first! Your code is amazing and your explanation is even better. Thank you!

  30. Thanks to squashbrain…

    your code very helpfull.

    but i still have a problem, when i print the image, the image can’t print correctly the print result has a blank space/blank line, between the image slice. FYI i use epson TM-U220B.

    can u help me?

    thanks a lot

  31. I am doing something similar function with C# but I was stuck in the image convertion. I am using Epson TM88IV and function ESC/P is “GS ( L – Define the NV graphics data (raster format).”

    Epson document here: https://lh4.googleusercontent.com/-ceLtiOLVtME/UMBL1yf4RpI/AAAAAAAAAD0/itid5KibW_I/s1015/Doc1.png

    I have asked for help to Stackoverflow: http://stackoverflow.com/questions/13715950/writing-a-bitmap-to-epson-tm88iv-through-esc-p-commands-write-to-nvram

    Can anyone helped me please? thanks.

  32. I have ported it to java, here is the code:

    public class BitmapData { private BitSet dots; private int height; private int width;

    public BitSet getDots() {
        return dots;
    }

    public void setDots(BitSet dots) { this.dots = dots; }

    public int getHeight() { return height; }

    public void setHeight(int height) { this.height = height; }

    public int getWidth() { return width; }

    public void setWidth(int width) { this.width = width; }

    } private BitmapData getBitmapData(String pngFileName) { Bitmap bitmap = BitmapFactory.decodeFile(pngFileName); int threshold = 127; int index = 0; int dimensions = bitmap.getWidth() * bitmap.getHeight(); BitSet dots = new BitSet(dimensions);

        for (int y = 0; y < bitmap.getHeight(); y++) {
            for (int x = 0; x < bitmap.getWidth(); x++) {
                int color = bitmap.getPixel(x, y);
                int luminance = (int) (Color.red(color) * 0.3 + Color.green(color) * 0.59 + Color.blue(color) * 0.11);

            if (luminance &lt; threshold) {
                dots.set(index);
            }
            index++;
        }
    }
    BitmapData bitmapData = new BitmapData();
    bitmapData.setWidth(bitmap.getWidth());
    bitmapData.setHeight(bitmap.getHeight());
    bitmapData.setDots(dots);
    
    return bitmapData;
    

    }

    private void printPicture(BitmapData data) throws IOException { BitSet dots = data.getDots(); outputStream.write(INIT);

    // // // // So we have our bitmap data sitting in a bit array called “dots.” //// This is one long array of 1s (black) and 0s (white) pixels arranged //// as if we had scanned the bitmap from top to bottom, left to right. //// The printer wants to see these arranged in bytes stacked three high. //// So, essentially, we need to read 24 bits for x = 0, generate those //// bytes, and send them to the printer, then keep increasing x. If our //// image is more than 24 dots high, we have to send a second bit image //// command to draw the next slice of 24 dots in the image. // //// Set the line spacing to 24 dots, the height of each “stripe” of the //// image that we’re drawing. If we don’t do this, and we need to //// draw the bitmap in multiple passes, then we’ll end up with some //// whitespace between slices of the image since the default line //// height–how much the printer moves on a newline–is 30 dots. outputStream.write(SET_LINE_SPACING_24); // //// OK. So, starting from x = 0, read 24 bits down and send that data //// to the printer. The offset variable keeps track of our global ‘y’ //// position in the image. For example, if we were drawing a bitmap //// that is 48 pixels high, then this while loop will execute twice, //// once for each pass of 24 dots. On the first pass, the offset is //// 0, and on the second pass, the offset is 24. We keep making //// these 24-dot stripes until we’ve run past the height of the //// bitmap. int offset = 0;

        while (offset < data.getHeight()) {
            // The third and fourth parameters to the bit image command are
            // 'nL' and 'nH'. The 'L' and the 'H' refer to 'low' and 'high', respectively.
            // All 'n' really is is the width of the image that we're about to draw.
            // Since the width can be greater than 255 dots, the parameter has to
            // be split across two bytes, which is why the documentation says the
            // width is 'nL' + ('nH' * 256).
            outputStream.write(SELECT_BIT_IMAGE_MODE);

        for (int x = 0; x &lt; data.getWidth(); ++x) {
            // Remember, 24 dots = 24 bits = 3 bytes.
            // The 'k' variable keeps track of which of those
            // three bytes that we're currently scribbling into.
            for (int k = 0; k &lt; 3; ++k) {
                byte slice = 0;
    
                // A byte is 8 bits. The 'b' variable keeps track
                // of which bit in the byte we're recording.
                for (int b = 0; b &lt; 8; ++b) {
                    // Calculate the y position that we're currently
                    // trying to draw. We take our offset, divide it
                    // by 8 so we're talking about the y offset in
                    // terms of bytes, add our current 'k' byte
                    // offset to that, multiple by 8 to get it in terms
                    // of bits again, and add our bit offset to it.
                    int y = (((offset / 8) + k) * 8) + b;
    
                    // Calculate the location of the pixel we want in the bit array.
                    // It'll be at (y * width) + x.
                    int i = (y * data.getWidth()) + x;
    
                    // If the image (or this stripe of the image)
                    // is shorter than 24 dots, pad with zero.
                    boolean v = false;
                    if (i &lt; dots.length()) {
                        v = dots.get(i);
                    }
    
                    // Finally, store our bit in the byte that we're currently
                    // scribbling to. Our current 'b' is actually the exact
                    // opposite of where we want it to be in the byte, so
                    // subtract it from 7, shift our bit into place in a temp
                    // byte, and OR it with the target byte to get it into there.
                    slice |= (byte) ((v ? 1 : 0) &lt;&lt; (7 - b));
                }
    
                // Phew! Write the damn byte to the buffer
                outputStream.write(slice);
            }
        }
    
        // We're done with this 24-dot high pass. Render a newline
        // to bump the print head down to the next line
        // and keep on trucking.
        offset += 24;
        outputStream.write(FEED_LINE);
    }
    

    // Restore the line spacing to the default of 30 dots. outputStream.write(SET_LINE_SPACING_30); for (byte i = 0; i<5; i++){ for (byte m = 1; m<4;m++){ byte[] array = new byte[14]; array[0] = -1; array[1] = -1; array[2] = -1; array[3] = -1; array[4] = -1; array[5] = -1; array[6] = -1; array[7] = -1; array[8] = -1; array[9] = -1; array[10] = -1; array[11] = -1; array[12] = -1; array[13] = -1;

                outputStream.write(array);
            }
        }
        outputStream.write(FEED_LINE);
        outputStream.write(FEED_LINE);
        outputStream.write(FEED_LINE);
        outputStream.write(FEED_LINE);
        outputStream.write(FEED_LINE);
        outputStream.write(FEED_LINE);
        outputStream.write(FEED_LINE);
    }
    

    • Hey quick question can this solution be implement by directly sending the data to the printer via serial port? I do not want to install the drivers for my epson tm t88iii. Any help would be wonder full. Thank you.

      • Yes this is how I use this code.

        You send (in Delphi terms) a string of bytes to the printer through the serial port, and it does what you want.

        So, for example, to print a slice in the above example, you would send [27][42][33] followed by the rest of the data.

        In ASCII, this would be ESC * ! – this is where the ESC/POS commands come it.

  33. Does anyone know if there is any way I can connect to the serial port directly, instead of using the printer name?

    I have not installed the receipt printer drivers, and windows does not see the printer through printer manager.

  34. Pingback: Using the EPL2 GW command to send an image to a Zebra thermal printer | Simply Does Not Work

  35. Excellent post. I used to be checking constantly this blog and I am impressed! Extremely helpful info particularly the ultimate section :) I handle such information a lot. I used to be seeking this particular information for a long time. Thank you and best of luck.

  36. how get name of my printer? I try run the program and I have error about the name of my printer and I was change to Generic \Text Only

  37. Can any one help me with printing a barcode with a TM T88IV?

    I’m using the example code in the document and puting this code in the GetDocument() funtion :

    bw.Write(AsciiControlChars.Escape); bw.Write(‘@’);

                bw.Write(AsciiControlChars.Newline);
                bw.Write(AsciiControlChars.Escape);
                bw.Write('W');
                bw.Write((byte)20);
                bw.Write((byte)1);
                bw.Write((byte)226);
                bw.Write((byte)0);
                bw.Write((byte)70);
                bw.Write((byte)0);
                bw.Write((byte)56);
                bw.Write((byte)1);

            bw.Write(AsciiControlChars.Escape);
            bw.Write('T');
            bw.Write((byte)1);
    
            bw.Write((char)0x1D); //GS
            //font selection
            bw.Write((char)0x66); //font
            bw.Write(0); //font A
    
            bw.Write(AsciiControlChars.GroupSeparator);
            bw.Write('H');
            bw.Write((byte)2);
    
            bw.Write(AsciiControlChars.GroupSeparator);
            bw.Write('f');
            bw.Write((byte)1);
    
            bw.Write(AsciiControlChars.GroupSeparator);
            bw.Write('h');
            bw.Write((byte)40);
    
            bw.Write(AsciiControlChars.GroupSeparator);
            bw.Write('w');
            bw.Write((byte)2);
    
            bw.Write(AsciiControlChars.GroupSeparator);
            bw.Write('$');
            bw.Write((byte)40);
            bw.Write((byte)0);
    
            bw.Write(AsciiControlChars.GroupSeparator);
            bw.Write('k');
            bw.Write((byte)4);
            bw.Write("*10% OFF*");
            bw.Write(AsciiControlChars.Nul);
            bw.Flush();
            return ms.ToArray();
    

    • Here is an example that prints out a barcode:

              private static void AppendBarcode(BinaryWriter bw, string barcode)
              {
                  bw.Write(AsciiControlChars.NewLine);
                  bw.Write(AsciiControlChars.GroupSeparator);
                  bw.Write('h');
                  bw.Write((byte)50);
                  bw.Write(AsciiControlChars.GroupSeparator);
                  bw.Write('w');
                  bw.Write((byte)2);
                  bw.Write(AsciiControlChars.GroupSeparator);
                  bw.Write('k');
                  bw.Write((byte)4);
                  bw.Write('');
                  bw.Write(Encoding.ASCII.GetBytes(barcode));
                  bw.Write('');
                  bw.Write(AsciiControlChars.Null);
              }
      

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>