neděle 29. listopadu 2015

Print images on POS printer

For some unimportant reason I have mini thermal bluetooth printer IMP006A (ordered from alibaba.com). It is reliable piece of HW which I spent some time playing with.

Here you can see the result. Except receipts the printer can be used to print images. Here I share the piece of java code that does the magic.

The image to be printed must be in PBM format max. 373 px width. During the printing process the image is rotated 90 degrees clockwise.

You can use ImageMagick convert utility to prepare the image for printing.

convert  saturn.png \
        -rotate -90 \
        -scale x373  \
        -dither FloydSteinberg \
        -colors 2 \
         image.pbm

Where saturn.png is the input image of any kind ImageMagick can read. 
image.pbm is the resulting pbm image ready to be printed. 

The image is 
  • saturn.png - read from a file
  • -rotate -90 - rotated 90 degrees CCW
  • -scale x373 - scaled to full width of paper
  • - dither ... -colors 2 - color are reduced to 2 (BW) using dithering to render shades of gray on BW device
  • image.pbm - converson result is written to a file
Then if you have the code compiled just run:

java  printqr.PrintQR  image.pbm

And you will get image.prn containg a set of commands for the printer to print the image. Use linux command line to send the data to the printer.

Use USB to connect the printer to Linux box. When connected to linux box with recent distro (tested on Fedora 20), the printer will be accessible as /dev/usb/lp0. By default the device is accessible for root (and lp group members) only. Send the data to the printer usinf gollowing command:

sudo sh -c 'cat image.prn > /dev/usb.lp0'

And that's all. 

For illustration (click to enlarge):



Code:


package printqr;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Random;

/** Maximum image printed width (input height - the image is rotated) should be 373 px
  *
  * To prepare image for printing:
  * convert saturn.png  -rotate -90 -scale x373 -dither FloydSteinberg -colors 2 image.pbm
  *  or
  * convert saturn.png   -rotate -90 -scale x373 -dither Riemersma -colors 2 image.pbm
  *
  *
 * */

public class PrintQR {
    final static int[] wsChars=new int[] {' ','\n','\t'};
   
    public static void main(String[] args){
        FileInputStream fis=null;
        FileOutputStream fos=null;
        try {
           
            //prepare image name
            String inputImage="image.pbm";
            if (args.length>0)
                inputImage=args[0];
           
            if (! inputImage.endsWith(".pbm"))
                throw new IllegalArgumentException("Input image must be NetBPM PBM format with .pbm extension.");
           
            //open and read image
            fis=new FileInputStream(inputImage);
            //read header
            expectChar(fis,'P');           
            expectChar(fis,'4');
            expectWhitespace(fis);
            int width=readDecimal(fis);
            int height=readDecimal(fis);
           
            //read data
            int rowWidthBytes= width%8 == 0 ? width / 8 : width / 8 +1 ;
            int dataLength=rowWidthBytes*height;
            byte[] data=new byte[dataLength];
            int off=0;
            int remaining=dataLength;
            while (remaining>0){
                int cnt=fis.read(data, off, remaining);
                remaining-=cnt;
                off+=cnt;
            }
           
            System.out.printf("Loaded image %d x %d\n", width, height);
           
            fos=new FileOutputStream(inputImage.replace(".pbm", ".prn"));
           
            //reset printer
            fos.write(new byte[]{0x1b,0x40});
           
            //set linespacing 0
            fos.write(new byte[]{0x1b,0x33,0x00});
           
            //print image
            printBitmap24pinDoubleDensity(width, height, data, fos);
           
        }
        catch (Exception e){
            e.printStackTrace();
        }
        finally {
            if (fos!=null) try {fos.close();}catch (Exception e){};
            if (fis!=null) try {fos.close();}catch (Exception e){};
        }
    }

    /**
     * Sends image to the printer in the right format with the right commands
     * @param width
     * @param height
     * @param data
     * @param fos
     * @throws IOException
     */
    private static void printBitmap24pinDoubleDensity(int width, int height, byte[] data,
            FileOutputStream fos) throws IOException {
        int bWidth=width%8 == 0 ? width / 8 : width / 8 +1;
        int hLow=(height) & 0x000000ff;
        int hHigh= ( (height) >> 8 )& 0x000000ff;
       
        for (int x=0; x< bWidth; x+=3){
            // ESC * m nL nH d1... dk
            // m - 0x21 for 24 pin printer double density
            // nL/nH - low/high byte of number of "strokes", each stroke consists of 24 dots (8 bytes)
            // data should be (nL+nH*256)*3 bytes
            fos.write(new byte[]{0x1b,0x2a,0x21,(byte)hLow,(byte)hHigh});
           
            //get appropriate data from image - for the operation concider image rotated 90 deg CCW
            for (int y=height-1; y>=0; y--){
                fos.write(new byte[]{
                        get(data,x,y,bWidth, height),
                        get(data,x+1,y,bWidth, height),
                        get(data,x+2,y,bWidth, height),
                });
            }
            fos.write('\n');
        }
    }
   
    /**
     * Extracts byte of image data - the image is like this in mempory 
     *  ----------------X----------------------->
     * |          0        1        2        3        4        5
     * | 0 ........ ........ ........ ........ ........ ........
     * | 1 ........ ........ ........ ........ ........ ........
     * | 2 ........ ........ .####### ######## ........ ........
     * Y 3 ........ ........ .####### ####.... ........ ........
     * | 4 ........ ........ .######. ........ ........ ........
     * | 5 ........ ........ .#...... ........ ........ ........
     * | 6 ........ ........ .#...... ........ ........ ........
     * V 7 #.#.#.#. .#.#.#.# ....#... ........ ........ ........
     *
     * First triplet sent to printer is
     * this [0,7],[1,7],[2,7]
     * then [0,6],[1,6],[2,6]
     * then [0,5],[1,5],[2,5]
     * and so on
     *
     * This effectively rotates image CCW 90 deg.   
     *
     * @param data
     * @param x
     * @param y
     * @param bWidth
     * @param height
     * @return
     */
    public static byte get(byte[] data, int x, int y, int bWidth, int height){
        if( x>= bWidth ) return 0; //handle request beyond image for images not %24 print height
        int idx=y*bWidth+x;
        if (idx<0 || idx>=data.length)
            return 0;
        return data[idx];
    }
   
    public static int expectChar(InputStream is, int expectedChar)
        throws IOException
    {
        int readChar=is.read();
        if (readChar==expectedChar) return readChar;
       
        throw
            new IllegalStateException(String.format("Expected %c found %c",expectedChar, readChar));
    }
   
    public static int expectWhitespace(InputStream is)
            throws IOException
    {
        int readChar=is.read();
        if (isWhitespace(readChar)) return readChar;
        throw
            new IllegalStateException(String.format("Expected whitecpace found %c",readChar));
    }
   
    public static int readDecimal(InputStream is)
        throws IOException
    {
        int accumulator=0;
        while (true){
            int c=is.read();
            if (isWhitespace(c)) return accumulator;
            else
                if (c>='0' && c<='9') accumulator=accumulator*10+(c-'0');
                else
                    throw
                        new IllegalStateException(String.format("Expected digit or whitecpace found %c",c));
        }       
    }
   
    public static boolean isWhitespace(int c){
        for (int i=0; i<wsChars.length; i++){
            if (c==wsChars[i]) return true;
        }
        return false;
    }


    /** not tested */
    private static void printBitmap9pin(int width, int height, byte[] data,
            FileOutputStream fos) throws IOException {
        int bWidth=width%8 == 0 ? width / 8 : width / 8 +1;
        int hLow=height & 0x000000ff;
        int hHigh= ( height >> 8 )& 0x000000ff;
       
        for (int x=0; x< bWidth; x++){
            fos.write(new byte[]{0x1b,0x2a,0x00,(byte)hLow,(byte)hHigh});
            for (int y=height-1; y>=0; y--){
                fos.write(get(data,x,y,bWidth, height));
            }
            fos.write('\n');
        }
    }

    /** not tested */
    private static void printBitmap9pinDoubleDensity(int width, int height, byte[] data,
            FileOutputStream fos) throws IOException {
        int bWidth=width%8 == 0 ? width / 8 : width / 8 +1;
        int hLow=height & 0x000000ff;
        int hHigh= ( height >> 8 )& 0x000000ff;
       
        for (int x=0; x< bWidth; x++){
            fos.write(new byte[]{0x1b,0x2a,0x01,(byte)hLow,(byte)hHigh});
            for (int y=height-1; y>=0; y--){
                fos.write(get(data,x,y,bWidth, height));
            }
            fos.write('\n');
        }
    }
}








Licence Creative Commons
Graphical printing tool for POS printer, jehož autorem je LRA, podléhá licenci Creative Commons Uveďte původ-Zachovejte licenci 4.0 Mezinárodní .