Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert image to zpl code for printing using zebra printer?

I want to print image along with some other texts through zebra printer from android application. I am able to create zpl code for text data but I am having to problem to create zpl code for image. The zpl do not supports base64 code. The image needs to be converted to hex ascii and print using ^GF command.

The raw data with both text and image is available on this Pastebin link, and can be viewed in labelary viewer.

Is there any image conversion process?

like image 267
Sagar Chapagain Avatar asked Aug 04 '18 14:08

Sagar Chapagain


2 Answers

I solved the problem and I am posting the answer so that other people can take benefit from the solution. The bitmap image can be converted to zpl code using following converter class.

public class ZPLConverter {
    private int blackLimit = 380;
    private int total;
    private int widthBytes;
    private boolean compressHex = false;
    private static Map<Integer, String> mapCode = new HashMap<Integer, String>();

    {
        mapCode.put(1, "G");
        mapCode.put(2, "H");
        mapCode.put(3, "I");
        mapCode.put(4, "J");
        mapCode.put(5, "K");
        mapCode.put(6, "L");
        mapCode.put(7, "M");
        mapCode.put(8, "N");
        mapCode.put(9, "O");
        mapCode.put(10, "P");
        mapCode.put(11, "Q");
        mapCode.put(12, "R");
        mapCode.put(13, "S");
        mapCode.put(14, "T");
        mapCode.put(15, "U");
        mapCode.put(16, "V");
        mapCode.put(17, "W");
        mapCode.put(18, "X");
        mapCode.put(19, "Y");
        mapCode.put(20, "g");
        mapCode.put(40, "h");
        mapCode.put(60, "i");
        mapCode.put(80, "j");
        mapCode.put(100, "k");
        mapCode.put(120, "l");
        mapCode.put(140, "m");
        mapCode.put(160, "n");
        mapCode.put(180, "o");
        mapCode.put(200, "p");
        mapCode.put(220, "q");
        mapCode.put(240, "r");
        mapCode.put(260, "s");
        mapCode.put(280, "t");
        mapCode.put(300, "u");
        mapCode.put(320, "v");
        mapCode.put(340, "w");
        mapCode.put(360, "x");
        mapCode.put(380, "y");
        mapCode.put(400, "z");
    }

    public String convertFromImage(Bitmap image, Boolean addHeaderFooter) {
        String hexAscii = createBody(image);
        if (compressHex) {
            hexAscii = encodeHexAscii(hexAscii);
        }

        String zplCode = "^GFA," + total + "," + total + "," + widthBytes + ", " + hexAscii;

        if (addHeaderFooter) {
            String header = "^XA " + "^FO0,0^GFA," + total + "," + total + "," + widthBytes + ", ";
            String footer = "^FS" + "^XZ";
            zplCode = header + zplCode + footer;
        }
        return zplCode;
    }

    private String createBody(Bitmap bitmapImage) {
        StringBuilder sb = new StringBuilder();
        int height = bitmapImage.getHeight();
        int width = bitmapImage.getWidth();
        int rgb, red, green, blue, index = 0;
        char auxBinaryChar[] = {'0', '0', '0', '0', '0', '0', '0', '0'};
        widthBytes = width / 8;
        if (width % 8 > 0) {
            widthBytes = (((int) (width / 8)) + 1);
        } else {
            widthBytes = width / 8;
        }
        this.total = widthBytes * height;
        for (int h = 0; h < height; h++) {
            for (int w = 0; w < width; w++) {
                rgb = bitmapImage.getPixel(w, h);
                red = (rgb >> 16) & 0x000000FF;
                green = (rgb >> 8) & 0x000000FF;
                blue = (rgb) & 0x000000FF;
                char auxChar = '1';
                int totalColor = red + green + blue;
                if (totalColor > blackLimit) {
                    auxChar = '0';
                }
                auxBinaryChar[index] = auxChar;
                index++;
                if (index == 8 || w == (width - 1)) {
                    sb.append(fourByteBinary(new String(auxBinaryChar)));
                    auxBinaryChar = new char[]{'0', '0', '0', '0', '0', '0', '0', '0'};
                    index = 0;
                }
            }
            sb.append("\n");
        }
        return sb.toString();
    }

    private String fourByteBinary(String binaryStr) {
        int decimal = Integer.parseInt(binaryStr, 2);
        if (decimal > 15) {
            return Integer.toString(decimal, 16).toUpperCase();
        } else {
            return "0" + Integer.toString(decimal, 16).toUpperCase();
        }
    }

    private String encodeHexAscii(String code) {
        int maxlinea = widthBytes * 2;
        StringBuilder sbCode = new StringBuilder();
        StringBuilder sbLinea = new StringBuilder();
        String previousLine = null;
        int counter = 1;
        char aux = code.charAt(0);
        boolean firstChar = false;
        for (int i = 1; i < code.length(); i++) {
            if (firstChar) {
                aux = code.charAt(i);
                firstChar = false;
                continue;
            }
            if (code.charAt(i) == '\n') {
                if (counter >= maxlinea && aux == '0') {
                    sbLinea.append(",");
                } else if (counter >= maxlinea && aux == 'F') {
                    sbLinea.append("!");
                } else if (counter > 20) {
                    int multi20 = (counter / 20) * 20;
                    int resto20 = (counter % 20);
                    sbLinea.append(mapCode.get(multi20));
                    if (resto20 != 0) {
                        sbLinea.append(mapCode.get(resto20)).append(aux);
                    } else {
                        sbLinea.append(aux);
                    }
                } else {
                    sbLinea.append(mapCode.get(counter)).append(aux);
                }
                counter = 1;
                firstChar = true;
                if (sbLinea.toString().equals(previousLine)) {
                    sbCode.append(":");
                } else {
                    sbCode.append(sbLinea.toString());
                }
                previousLine = sbLinea.toString();
                sbLinea.setLength(0);
                continue;
            }
            if (aux == code.charAt(i)) {
                counter++;
            } else {
                if (counter > 20) {
                    int multi20 = (counter / 20) * 20;
                    int resto20 = (counter % 20);
                    sbLinea.append(mapCode.get(multi20));
                    if (resto20 != 0) {
                        sbLinea.append(mapCode.get(resto20)).append(aux);
                    } else {
                        sbLinea.append(aux);
                    }
                } else {
                    sbLinea.append(mapCode.get(counter)).append(aux);
                }
                counter = 1;
                aux = code.charAt(i);
            }
        }
        return sbCode.toString();
    }

    public void setCompressHex(boolean compressHex) {
        this.compressHex = compressHex;
    }

    public void setBlacknessLimitPercentage(int percentage) {
        blackLimit = (percentage * 768 / 100);
    }

   }

Example usage: You need to convert the your image to bitmap, convert to monochrome image and do hex acii conversion. The generated zpl code can be checked at labelary viewer.

public class Utils {

    public static String getZplCode(Bitmap bitmap, Boolean addHeaderFooter) {
        ZPLConverter zp = new ZPLConverter();
        zp.setCompressHex(true);
        zp.setBlacknessLimitPercentage(50);
        Bitmap grayBitmap = toGrayScale(bitmap);
        return zp.convertFromImage(grayBitmap, addHeaderFooter);
    }

    public static Bitmap toGrayScale(Bitmap bmpOriginal) {
        int width, height;
        height = bmpOriginal.getHeight();
        width = bmpOriginal.getWidth();

        Bitmap grayScale = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Canvas c = new Canvas(grayScale);
        Paint paint = new Paint();
        ColorMatrix cm = new ColorMatrix();
        cm.setSaturation(0);
        ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
        paint.setColorFilter(f);
        c.drawBitmap(bmpOriginal, 0, 0, paint);
        return grayScale;
    }
}

The converter code has been referenced from here and added support for android use.

like image 150
Sagar Chapagain Avatar answered Oct 23 '22 00:10

Sagar Chapagain


I know this is kind of done deal but I still struggle a lot with the current answer. I wanted to share my experience for those who might need it.

First of all,^GFA stands for hexadecial representation of the pixel but it has to be converted into readable text (ASCII). Here is an example : Pixels white = 1, black = 1

1011 0100 translate into 0xB4

In the data section of ^GFA, you need to have B4 as data.

If we go with

Pixel line 1 : 1011 0100 1001 1100 = 0xBA 0x9C Pixel line 2 : 0011 0110 0001 1111 = 0x36 0x1F

the resulting ZPL code will be :

^XA (needed to start a ZPL file)

^F10,0 (offsets 10 pixel horizontal, 0 pixel vertical)

^GFA,4,4,2,BA9C361F (4 is the total number of byte, 2 is the number of byte per line)

^F0^XZ (end of file)

Now, the interesting point. How to get to code that :

You need a gray-scale bitmap. You need pixel-wise access to the bitmap. In another words, an array containing integer which values varies from 0 to 255.

With that array you take each bunch of 8 pixels, convert it to hexadecimal value and then to text representation of these hexadecimals. Here is the c++ code made with Borland :

Graphics::TBitmap *imageFax = new Graphics::TBitmap();

unsigned char r;
unsigned char b;
ofstream outFile;
char listeHex[16];
int lineByteWidth;
int j;
int bytesCount = 0;
int widthHeight;
AnsiString testOut;

listeHex[0] = '0';
listeHex[1] = '1';
listeHex[2] = '2';
listeHex[3] = '3';
listeHex[4] = '4';
listeHex[5] = '5';
listeHex[6] = '6';
listeHex[7] = '7';
listeHex[8] = '8';
listeHex[9] = '9';
listeHex[10] = 'A';
listeHex[11] = 'B';
listeHex[12] = 'C';
listeHex[13] = 'D';
listeHex[14] = 'E';
listeHex[15] = 'F';

imageFax->Monochrome = true;
imageFax->PixelFormat = pf8bit;

imageFax->LoadFromFile("c:/testEtiquette/test.bmp"); //1200x300pixels bitmap test image

testOut = "c:/testEtiquette/outputfile.txt";

outFile.open(testOut.c_str());

imageFax->PixelFormat = pf8bit;


lineByteWidth = imageFax->Width/8;//Number of byte per line
widthHeight = lineByteWidth*imageFax->Height;//number of total byte to be written into the output file


testOut = "^XA^FO10,0^GFA,";
outFile << testOut.c_str() << widthHeight << ',' << widthHeight << ',' << lineByteWidth << ',' ;
for(int i = 0; i < imageFax->Height; i++)
{
     unsigned char * pixel = (unsigned char *)imageFax->ScanLine[i];
     bytesCount = 0;
     b=0x00;
     for(j = 0; j < imageFax->Width; j++)
     {
        //Here is the "switch" : what is not white (255) bit = 0, is black bit = 1.
        //You can set your switch at whatever value you think is best. 0, 255 or anything between.
        //I think 255 (white) is a good for my application
        if(pixel[j] != 255)
        {
            b = b<<1;
            //It is not white (hence black), we force value 1 into current position
            b = b|0x01;
        }
        else
        {
            //Since it white, we move 1 bit to the left, pushing 0 into current position
            b = b<<1;
            b = b&0xFE;//Forcing a 0 in the current position
        }

        //If we've got a full byte (8-bits), we write it into the file
        //This will lead into cutting off part of images that width is not a multiple of 8
        if(j%8 == 7)
        {
            bytesCount++;

            r = b;
            r = r&0xF0; //Cleaning last digits
            r=r>>4; //Moving the bits to the left 0xF0 => 0x0F
            outFile << listeHex[r%16]; //Reaching into the conversion array listeHex, ASCII representation of hex value
            r = listeHex[r%16]; //For debug only

            r = b;
            r = r&0x0F;//Cleaning first digits
            outFile << listeHex[r%16];//Reaching into the conversion array listeHex, ASCII representation of hex value
            r = listeHex[r%16]; //For debug only

            b = 0x00; //Reseting for next Byte
        }
     }
}
testOut = "^F0^XZ";
outFile << testOut.c_str();
outFile.close();
delete imageFax;

Some links : ZPL PDF doc (see page 191 for Graphic conversion) https://www.zebra.com/content/dam/zebra/manuals/printers/common/programming/zpl-zbi2-pm-en.pdf (If link does not work, try "zpl-zbi2-pm-en.pdf" on google)

https://www.rapidtables.com/convert/number/binary-to-hex.html

like image 5
antoine Avatar answered Oct 22 '22 23:10

antoine