androidthings / contrib-drivers

Open source peripheral drivers
Apache License 2.0
560 stars 174 forks source link

[thermalprinter] Bitmap printing doesn't work #119

Open smnvdl opened 5 years ago

smnvdl commented 5 years ago

Today I wanted to print an image (bitmap) captured with the Pi's camera on the thermal printer. The bitmap is captured properly, but the printer only prints "empty" lines instead of the picture. This is my code snippet:

private` void printImage(Bitmap bitmap) {
    if (bitmap != null) {
      Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 384, 256, false);
      // Print a bitmap
      mThermalPrinter.enqueue(new ThermalPrinter.BitmapJob().printBitmap(resizedBitmap));
    } else {
      Log.d(TAG, "Bitmap == null");
    }
  }

I also tried printing other bitmaps created from drawable resources but nothing changed. The logcat doesn't show any errors or warnings at all.

The printer is initialized in the onCreate() method of the activity. The printer itself is on firmware v2.69, printing texts works without issues.

mThermalPrinter = new ThermalPrinter("UART0");

Has anyone experienced similar issues? #

Fleker commented 5 years ago

There's a limitation in the printer driver where it runs through each pixel and only prints out dark pixels and doesn't print light pixels. You may need to do additional image processing before it can work. There are some techniques to doing a halftone effect for the printer. I've included a star bitmap that you can use as a test to verify the printer is outputting something.

star

smnvdl commented 5 years ago

There's a limitation in the printer driver where it runs through each pixel and only prints out dark pixels and doesn't print light pixels. You may need to do additional image processing before it can work. There are some techniques to doing a halftone effect for the printer. I've included a star bitmap that you can use as a test to verify the printer is outputting something.

star

Thanks alot for your quick reply! Unfortunately, neither of your suggested solutions seem to work, I'm still getting only blank lines out of the printer (it does the "printing sound" but prints nothing; text is printed anyway). This is my updated code:

private void printImage(Bitmap bitmap) {

    // Print text
    mThermalPrinter.enqueue(
        new ThermalPrinter.TextJob().printText("Hello World! \n\n\n")); // Is printed

    if (bitmap != null) {
      Bitmap resizedBitmap = Bitmap.createScaledBitmap(bitmap, 384, 256, false);
      Bitmap star = BitmapFactory.decodeResource(getResources(), R.drawable.star);
      Bitmap monochrome = processBitmap(resizedBitmap);

      // Print bitmaps
      mThermalPrinter.enqueue(new ThermalPrinter.BitmapJob().printBitmap(resizedBitmap));
      mThermalPrinter.enqueue(new ThermalPrinter.BitmapJob().printBitmap(star));
      mThermalPrinter.enqueue(new ThermalPrinter.BitmapJob().printBitmap(monochrome));

    } else {
      Log.d(TAG, "Bitmap == null");
    }
  }

  private Bitmap processBitmap(Bitmap bitmap) {
    // https://stackoverflow.com/questions/9377786/android-converting-a-bitmap-to-a-monochrome-bitmap-1-bit-per-pixel
    Bitmap bmpMonochrome = Bitmap.createBitmap(384, 256, Bitmap.Config.ARGB_8888);
    Canvas canvas = new Canvas(bmpMonochrome);
    ColorMatrix ma = new ColorMatrix();
    ma.setSaturation(0);
    Paint paint = new Paint();
    paint.setColorFilter(new ColorMatrixColorFilter(ma));
    canvas.drawBitmap(bitmap, 0, 0, paint);
    return bmpMonochrome;
  }
Fleker commented 5 years ago

It's possible that there's a bug in the implementation, as it's been a while since I've played with a printer, but the original code looks the same as the current implementations.

Here's the original code that uses a basic handler instead of the global queuing system.

public void printBitmap(Bitmap bitmap) throws IOException {
        // When I'm printing a bitmap, block characters from printing
        // Maximum bitmap width is 256
        if (bitmap.getWidth() > 384) {
            throw new IllegalArgumentException("Bitmap must be no larger than 384 pixels wide. Bitm"
                + "ap width: " + bitmap.getWidth() + ".");
        }
        final byte[] PRINTER_SET_LINE_SPACE_24 = new byte[]{ASCII_ESC, 0x33, 24};
        final byte[] PRINTER_SELECT_BIT_IMAGE_MODE = {0x1B, 0x2A, 33};
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        final byte[] controlByte = {(byte) (0x00ff & width), (byte) ((0xff00 & width) >> 8)};
        int[] pixels = new int[width * height];
        bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
        final int BAND_HEIGHT = 24;
        // Bands of pixels are sent that are 8 pixels high.  Iterate through bitmap
        // 24 rows of pixels at a time, capturing bytes representing vertical slices 1 pixel wide.
        // Each bit indicates if the pixel at that position in the slice should be dark or not.
        for (int row = 0; row < height - 8; row += BAND_HEIGHT) {
            final ByteArrayOutputStream imageData = new ByteArrayOutputStream();
            imageData.write(PRINTER_SET_LINE_SPACE_24);
            // Need to send these two sets of bytes at the beginning of each row.
            imageData.write(PRINTER_SELECT_BIT_IMAGE_MODE);
            imageData.write(controlByte);
            // Columns, unlike rows, are one at a time.
            for (int col = 0; col < width; col++) {
                byte[] bandBytes = {0x0, 0x0, 0x0};
                // For each starting row/col position, evaluate each pixel in a column, or "band",
                // 24 pixels high.  Convert into 3 bytes.
                for (int rowOffset = 0; rowOffset < 8; rowOffset++) {
                    // Because the printer only maintains correct height/width ratio
                    // at the highest density, where it takes 24 bit-deep slices, process
                    // a 24-bit-deep slice as 3 bytes.
                    int[] pixelSlice = new int[3];
                    int pixel2Row = row + rowOffset + 8;
                    int pixel3Row = row + rowOffset + 16;
                    // If we go past the bottom of the image, just send white pixels so the printer
                    // doesn't do anything.  Everything still needs to be sent in sets of 3 rows.
                    pixelSlice[0] = bitmap.getPixel(col, row + rowOffset);
                    pixelSlice[1] = (pixel2Row >= bitmap.getHeight()) ?
                            Color.TRANSPARENT : bitmap.getPixel(col, pixel2Row);
                    pixelSlice[2] = (pixel3Row >= bitmap.getHeight()) ?
                        Color.TRANSPARENT : bitmap.getPixel(col, pixel3Row);

                    boolean[] isDark = new boolean[3];
                    for (int slice = 0; slice < 3; slice++) {
                        float[] pixelSliceHsv = new float[3];
                        Color.colorToHSV(pixelSlice[slice], pixelSliceHsv);
                        isDark[slice] = pixelSliceHsv[2] < 25; // Hsv[2] -> Value should be 10% dark
                        if (Color.alpha(pixelSlice[slice]) < 25) {
                            isDark[slice] = false;
                        }
                        if (isDark[slice]) {
                            bandBytes[slice] |= 1 << (7 - rowOffset);
                        }
                    }
                }
                imageData.write(bandBytes);
            }
            enqueuePrinterAction(new Runnable() {
                @Override
                public void run() {
                    try {
                        mUartDevice.write(imageData.toByteArray(), imageData.size());
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }, 1000, "write image row");
            enqueuePrinterAction(new Runnable() {
                @Override
                public void run() {
                    try {
                        mUartDevice.write(new byte[] {ASCII_ESC, (byte) 'd', (byte) 1}, 3);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }, 10, "feedline");
        }
        enqueuePrinterAction(new Runnable() {
            @Override
            public void run() {
                try {
                    mUartDevice.write(new byte[] {ASCII_ESC, (byte) 'd', (byte) 1}, 3);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }, 500, "finish image");
    }
smnvdl commented 5 years ago

Ok, with this code I am able to print the star icon.

When I try to print other simple icons, the output is "compressed" vertically. Printing my original bitmap is also compressed like this (and completely black).

Do you have any suggestions regarding the compression?

Fleker commented 5 years ago

By compressed, do you mean that it'll omit certain rows?

The black issue may be due to the internal rendering process from your bitmap to the pixels. Maybe it's due to the background?

It's interesting that the code snippet above does work, as I didn't think anything should be different.

smnvdl commented 5 years ago

By compressed I mean that the width of the printed image is okay, but the height is only like 1/3 of the size the printed image should have. What really confuses me is that this doesn't happen with your star image but with all images and icons I tried.

talhamughal10 commented 4 years ago

Hi i am working on the printing image with ESC * command i have tried everything to get the image printer in 48mm paper. but when it prints if the mode is 33 so it gives the too much between the andother piece of the image with garbage values. this is the code which i am reusing to print the image.

public void printImage(Bitmap image) { try { BitSet imageBits = getBitsImageData(image);

        byte widthLSB = (byte)(image.getWidth() & 0xFF);
        byte widthMSB = (byte)((image.getWidth() >> 8) & 0xFF);

        // COMMANDS
        byte[] selectBitImageModeCommand = buildPOSCommand(SELECT_BIT_IMAGE_MODE, (byte) 33, widthLSB, widthMSB);
        byte[] setLineSpacing24Dots = buildPOSCommand(SET_LINE_SPACING, (byte) 0);
        byte[] setLineSpacing30Dots = buildPOSCommand(SET_LINE_SPACING, (byte) 0);

        printOutput.write(INITIALIZE_PRINTER);
        //printOutput.write(setLineSpacing24Dots);

        int offset = 0;
        while (offset < image.getHeight()) {
            printOutput.write(selectBitImageModeCommand);

            int imageDataLineIndex = 0;
            byte[] imageDataLine = new byte[image.getWidth()*3];

            for (int x = 0; x < image.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 < 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 * image.getWidth()) + x;

                        // If the image (or this stripe of the image)
                        // is shorter than 24 dots, pad with zero.
                        boolean v = false;
                        if (i < imageBits.length()) {
                            v = imageBits.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) << (7 - b));
                    }

                    imageDataLine[imageDataLineIndex + k] = slice;

                    // Phew! Write the damn byte to the buffer
                    //printOutput.write(slice);
                }

                imageDataLineIndex += 3;
            }

            printOutput.write(imageDataLine);
            offset +=24 ;
            printOutput.write(PRINT_AND_FEED_PAPER);
        }

        //printOutput.write(setLineSpacing30Dots);
    } catch (IOException ex) {
        Logger.getLogger(ESCPOSApi.class
                .getName()).log(Level.SEVERE, null, ex);
    }
}

Please guide me i am stuck in solving this. i want to print the entire image with ESC * command how can i achieve this. i have googled a lot on this but didnt found any link working for me. Any help would be appreciated