Printing a bitmap with the POS 104 / PRP-085IIIT receipt printer

Fiddled around some more with the new receipt printer I got. I needed some code to print a logo.

I got some good information on ESC/POS here and also here.

Turns out, that ESC/POS has a number of commands to print bitmaps. I ended up using the GS * command, which defines a bitmap, and then, the GS / command, which can print it.

Here is some example Qt/C++ code, that loads an image, and creates a QByteArray containing the bytes needed to do a GS * command. Even if you do not use Qt, the code should be so straightforward, that you should be able to get it working with other libs/languages quite easily.

// C++
#include <iostream>
#include <iomanip>
#include <vector>

// Qt
#include <QCoreApplication>
#include <QImage>

using namespace std;

class ESCPOSImage {
private:
    int m_width;
    int m_height;
    int m_gs_x;
    int m_gs_y;
    int m_gs_k;
    // Actual bytes for image - could have used QByteArray for this, I guess.
    vector <unsigned char> m_bytes;

    // This turns on a pixel a position x, y as you would expect. 
    // The bytes in the bitmap is stored in a weird "y first", "x second" order.
    // The bit fiddling here takes care of it.
    void setPixel( int x, int y ) {
        size_t byte_index = x * m_gs_y + y / 8;
        int bit_no = y % 8;
        // Swap msb/lsb order. This probably only works on machines with "normal" endianess.....
        unsigned char bit = 1 << ( 7 - bit_no );
        m_bytes.at( byte_index ) = m_bytes.at( byte_index ) | bit;
    }

public:
    ESCPOSImage( const QImage & image ) {
        // Set up x and y as pr. epson manual
        m_width = image.width();
        m_height = image.height();

        m_gs_x = m_width / 8 + (m_width % 8 != 0?1:0);
        m_gs_y = m_height / 8 + (m_height % 8 != 0?1:0);
        
         if ( m_gs_x > 255 || m_gs_y > 255 ) {
            // You may want to write an error message here
            throw "Too large on dimension"; 
        }

        m_gs_k = m_gs_x * m_gs_y * 8;
        // Bit unsure about this limit. It depends on the actual printer....
        if ( m_gs_k > (3072*8) ) {
            // You may want to write an error message here
            throw "Too large on area"; 
        }

        vector<unsigned char> bytes( m_gs_k, 0 ); // Blank all bytes.
        m_bytes = bytes;
        
        // Iterate over the image, turn on any pixels that are set in the monochromo image.
        for ( int i_y = 0; i_y < m_height; ++i_y ) {
            for ( int i_x = 0; i_x < m_width; ++i_x ) {
                if ( image.pixelIndex( i_x, i_y ) == Qt::color1 ) {
                    setPixel( i_x, i_y );
                }
            }
        }
    }

    // Access internal representation. Should be const something, I guess.
    vector<unsigned char> & getBytes() {
        return m_bytes;
    }

    // Bytes suitable to send to the printer to define the bitmap.
    QByteArray getGSStar() {
        QByteArray res( m_bytes.size() + 4, 0 );
        res[0] = 29;
        res[1] = '*';
        res[2] = (unsigned char) m_gs_x;
        res[3] = (unsigned char) m_gs_y;
        for ( size_t i = 0; i < m_bytes.size(); ++i ) {
            res[ 4 + i ] = m_bytes.at( i );
        }
        return res;
    };

};

int main( int argc, char ** argv ) {
    // Note, that exception code is not handled here!
    // If you get weird errors, try putting in an exception handler around the body of main.

    // Fire up Qt.
    QCoreApplication app( argc, argv );
 
    // Assume the user was friendly enough to pass the name of an image, load it.
    QImage orgImage( argv[1] );
    if ( orgImage.isNull() ) {
        cerr << "Unable to load image" << endl;
        return 1;
    }

    // Convert to monochrome. The conversion done by Qt is a bit bad. So probably a good idea to be monochrome already...
    QImage monoImage = orgImage.convertToFormat( QImage::Format_Mono, Qt::MonoOnly );
    if ( monoImage.isNull() ) {
        cerr << "Unable to convert image to monochrome" << endl;
        return 1;
    }

    // Create the ESCPOS image representation, get the bytes that represents the GS Star command, and dump the raw bytes to stdout.
    ESCPOSImage posImage( monoImage );
    QByteArray ar = posImage.getGSStar();
    for ( int i = 0; i < ar.size(); ++i ) {
        cout << (unsigned char) ar[i];
    }
    cout << "\n"; // This may not be needed, actually.

    // Do a GS / to actually output the bitmap.
    cout << (unsigned char) 29 << "/" << (unsigned char) 0 << "\n";
    
    return 0;
}

If you are running linux, you want to do something like:


g++ -L/usr/lib -lQtSql -lQtGui -lQtCore -lpthread -g -Wall -W -D_REENTRANT -DQT_SQL_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/linux-g++ -I. -I/usr/include/qt4/QtCore -I/usr/include/qt4/QtGui -I/usr/include/qt4/QtSql -I/usr/include/qt4 -I. -I. -o test-bitmap test-bitmap.cc

and then, something like


./test-bitmap some-image.png > /dev/usblp0

and you should be set.

Advertisement

About madsdyd
Software developer, linux guy, hacker, all that.

6 Responses to Printing a bitmap with the POS 104 / PRP-085IIIT receipt printer

  1. su_pyrow says:

    first off, id like to thank you for this code; you see, im thinking about buying a thermal printer, and had no idea what i was getting into untill i started looking for drivers and ways to make it work with linux. the code looks GREAT! i compiled it after a few trys, im using g++ 4.4.4 and it hated vector m_bytes; i changed it to vector m_bytes and it compiled perfectly. however im getting a “terminate called after throwing an instance of ‘char const*’ Aborted. ” as you imagine ive googled and found results, and for the likes of me its now 6 am maybe i have tunnel vision, but i dont get it. its something simple i know it! i dont like asking for help, however i could use a handout on this one. basically, my question is is what i changed correct? cause ive been doubting it and not doubting it. like i said i dont have the printer yet, but its on its way and id like to have something that is potentially working. also when you said some-image.pn do you mean some-image.pnm ? or another type of raw file that i havent heard of.

    anyway, id like to thank you again for the code, i must have yelled “hot shit!” when i saw it. awesome! thanks again!

    • madsdyd says:

      Hi there

      Great if the code can be of some use.

      First off: There are different types of thermal printers. The printer I am using is a recipt printer. There are also “label” printers, which I believe differs at least partially from receipt printers (then again, maybe not that much). The printer I am using, supports the ESC/POS command set, which is widely supported.

      If you want to drive such a printer under Linux, and you have a choice of languages, perhaps you might want to go with Java instead of C++. There are great drivers for Java. Check out the OpenBravo project.

      Now, regarding my C++ code. I don’t understand your description of the problem with vector…. I agree the html tags sucks. Perhaps you can wrap it in sourcecode blocks?

      Thr runtime error you are getting. ” “terminate called after throwing an instance of ‘char const*’ Aborted. ”” is the C++ runtimes way of saying:

      “Hi there. Seems an exception was thrown somewhere in the program. It has the type ‘char const *’, and after I propagated it up all the call stacks, I hit main, and nobody handled the exception. I don’t like stuff like that, so I am going to terminate now. Sorry about it, but you really should be catching your exceptions. Bye, bye.”

      You are probably passing too big an image (the pos printers only support quite small images), and hit one of the two exceptions that are thrown in the image class.

      You need to wrap the body of main in an exception handler. Its like this now:


      int main() {
      .. lots of stuff
      }

      You need to do


      int main() {
      try {
      ... lots of stuff
      } catch ( const char * ex ) {
      std::cout << "Got an exception of type const char *: " << ex << std::endl;
      }
      }

      I did not do it, becuase it is an example program, you may not want to throw where the errors are detected, you may want to throw something else, etc. Exceptions are really not used a lot in Qt. Next time I do an example, I will just cerr << something.

      Hope this clears it up a bit.

      Regards

      Mads

    • madsdyd says:

      Ah, now I get it about the vector, you are right, there is an error in the code, and the template parameter for the first vector declaration is missing!

      Probably some stupid mistake by my part, when pasting the code into wordpress. Sorry about that, I will try to correct the post.

      Regards

      Mads

    • madsdyd says:

      And, finally, for the image. It should have said “some-image.png”, but really, any image that Qt supports should be fine. The important point is that there is a limit to the size (the thermal printer does not have a very high resolution), and also that conversion to mono-chrome should probably be done in a proper image program (like gimp) instead of relying on Qts color-to-monochrome algorithm, which appears to be bad.

      Regards

      Mads

  2. su_pyrow says:

    html messed up what i typed vector m_bytes sorry to double post

  3. su_pyrow says:

    std::vector(unsigned char ) m_bytes damn html tags

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.