Filip Matys
Filip Matys

Reputation: 195

Ionic 3: Print image using Bluetooth printer

I am currently struggling with our Ionic3 app. I can't seem to find out how to print an image using BT printer and Bluetooth Serial plugin. Printing text is just fine.

We are testing RPP02N-BU printer using this documentation (found by my ex-colleague) Driver Commands Documentation, but I can not get the

SELECT BIT IMAGE MODE

to work.

We first resize uploaded image to not exceed 300px and we convert it to Black and White. When printing, we iterate the image and create binary strings, which we then convert to bytes. Those bytes follow the SELECT BIT IMAGE MODE commands.

Our code (in TypeScript) is as follows:

/**
 * Image to printer command
 * @param image 
 */
public static getImagePrintData(image: HTMLImageElement): Buffer {
    // Initialize list of commands
    let command: number[] = [ 0x1b, 0x2a, 33, 255, 3 ];

    // Get image bytes
    let bytes = this.getImageBytes(image);

    // Add bytes to command
    bytes.forEach((byte) => command.push(byte));

    // Return command
    return new Buffer(command);
}

/**
 * Get image bytes
 * @param image 
 */
private static getImageBytes(image: HTMLImageElement): number[] {
    // Init result
    let result = [];

    // Create image padding
    let padding = this.getZeroBytePadding(image.width);

    // Get image context
    let ctx = this.getImageContext(image);

    // Iterate image pixels
    for (let y = 0; y < image.height; y++) {
        // Init row string
        let row = "";

        // Go through row of pixels
        for (let x = 0; x < image.width; x++) {
            // Get pixel
            let pixel = ctx.getImageData(x, y, 1, 1).data;

            // Get rgb value
            let rgbValue = pixel[0] + pixel[1] + pixel[2];

            // Add 0 or 1 based on value
            row = row + (rgbValue > 0 ? 0 : 1);
        }

        // Add padding
        row = row + padding;

        // Now we need to split the row into byte chunks
        let byteChunks = row.match(/.{1,8}/g);

        // Now add those byte chunks to result
        byteChunks.forEach((chunk) => result.push(parseInt(chunk, 2)));
    }

    // Return result
    return result;
}

/**
 * Get zero byte padding
 * @param value 
 */
private static getZeroBytePadding(value: number): string {
    // Init padding
    let padding = "";

    // Get difference
    let diff = value % 8;

    // Create padding
    for (let i = 0; i < (8 - diff); i++) {
        padding = padding + "0";
    }

    // Return result
    return padding;
}

/**
 * Get image context
 * @param image 
 */
private static getImageContext(image: HTMLImageElement): CanvasRenderingContext2D {
    // Prepare canvas to draw image into
    var canvas = document.createElement('canvas');
    canvas.width = image.width;
    canvas.height = image.height;

    // Context
    var ctx = canvas.getContext('2d');
    ctx.drawImage(image, 0, 0);

    return ctx;
}

Then the command is sent to BT printer:

// Initialize image
let image = new Image();
// Set data
image.src = options.logo.content;
// Get image print data
let imagePrintData = PrinterUtils.getImagePrintData(image);

// Push image to print queue
printQueue.push(this.bluetoothUtility.write(imagePrintData));

The resulting image looks like this:

Printed image

And the original is:

Original image

Upvotes: 2

Views: 5692

Answers (3)

Filip Matys
Filip Matys

Reputation: 195

UPDATE 9.6.2023

For anyone still struggling, we recently found this library: https://github.com/NielsLeenheer/EscPosEncoder

It does most of the work and also offers QR code and Barcode encoding. Hope that helps!


So I finally succeeded. I used parts of this code: https://github.com/song940/node-escpos and managed to print logo, even centered.

Following code is in TypeScript

I created ESCPOSImage class:

/**
 * RGB interface
 */
interface IRGB {
    r: number;
    g: number;
    b: number;
    a: number;
}

/**
 * ESCPOS image
 */
export class ESCPOSImage {

    // Data
    private data: number[] = [];

    // Width
    private width: number = 0;

    // Height
    private height: number = 0;

    /**
     * Constructor
     * @param image 
     */
    constructor(image: HTMLImageElement) {
        // Set width 
        this.width = image.width;

        // Set height
        this.height = image.height;

        // Get image context
        let ctx = this.getContext(image);

        // Get data
        this.data = this.getBitData(ctx);
    }

    /**
     * Get bit data
     * @param ctx 
     */
    private getBitData(ctx: CanvasRenderingContext2D): number[] {
        // Init result
        let result: number[] = [];

        // Iterate rows
        for (let y = 0; y < this.height; y++) {
            // Iterate columns
            for (let x = 0; x < this.width; x++) {
                // Get pixel
                let pixel = ctx.getImageData(x, y, 1, 1).data;

                // Get rgb
                let rgb = this.getRGB(pixel);

                // Get rgb value
                let value = rgb.r + rgb.g + rgb.b;

                // Add bit to result
                result.push(value > 0 ? 0 : 1);
            }
        }

        // Return result
        return result;
    }

    /**
     * Get image context
     * @param image 
     */
    private getContext(image: HTMLImageElement): CanvasRenderingContext2D {
        // Create canvas
        var canvas = document.createElement('canvas');
        canvas.width = image.width;
        canvas.height = image.height;

        // Set context
        var context = canvas.getContext('2d');
        context.drawImage(image, 0, 0);

        // Return context
        return context;
    }

    /**
     * Get RGB
     * @param pixel 
     */
    private getRGB(pixel: any): IRGB {
        // Return RGB
        return {
            r: pixel[0],
            g: pixel[1],
            b: pixel[2],
            a: pixel[3]
        }
    }

    /**
     * To raster
     */
    public toRaster() {
        // Init result
        let result = [];

        // Get width and height
        let width = this.width;
        let height = this.height;

        // N block lines
        let n = Math.ceil(width / 8);

        // Iterate
        for (let y = 0; y < height; y++) {
            for (let x = 0; x < n; x++) {
                for (let b = 0; b < 8; b++) {
                    let i = x * 8 + b;

                    if (result[y * n + x] === undefined) {
                        result[y * n + x] = 0;
                    }

                    let c = x * 8 + b;

                    if (c < width) {
                        if (this.data[y * width + i]) {
                            result[y * n + x] += (0x80 >> (b & 0x7));
                        }
                    }
                }
            }
        }

        // Return result
        return {
            data: result,
            width: n,
            height: height
        };
    }   
}

The class implements toRaster method, that is later used in ESCPOSPrinter class:

/**
 * ESCPOS printer
 */
export class ESCPOSPrinter {

    // Buffer
    private buffer: Buffer;

    /**
     * Constructor
     * @param buffer 
     */
    constructor(buffer: Buffer) {
        // Init buffer
        this.buffer = buffer;
    }

    /**
     * Write buffer
     * @param buffer 
     */
    private write(buffer: Buffer) {
        this.buffer = Buffer.concat([this.buffer, buffer]);
    }

    /**
     * Print raster
     * @param image 
     * @param mode 
     */
    public raster(image: ESCPOSImage, mode: string = 'normal') {
        // Get header
        let header = COMMANDS.S_RASTER_N;

        // Get raster
        let raster = image.toRaster();

        // Set alignment
        this.align('center');

        // Write header
        this.write(new Buffer(header));
        this.write(new Buffer([raster.width, 0]));
        this.write(new Buffer([raster.height, 0]));
        // Write data
        this.write(new Buffer(raster.data));
    }

    /**
     * Print line
     */
    public printLn() {
        this.write(new Buffer([COMMANDS.CTL_CR, COMMANDS.CTL_LF]));
    }

    /**
     * Align 
     * @param alignment ['left', 'center', 'right'] 
     */
    public align(alignment: string = 'left') {
        // Create alignment dictionary
        const aligments = {
            ['left']: COMMANDS.TXT_ALIGN_LT,
            ['center']: COMMANDS.TXT_ALIGN_CT,
            ['right']: COMMANDS.TXT_ALIGN_RT
        }

        // Write alignment
        this.write(new Buffer(aligments[alignment]));
    }

    /**
     * Get buffer
     */
    public getBuffer(): Buffer {
        return this.buffer;
    }
}

And finally, all together used:

// Create image
let image = new Image();

// Set data
image.src = options.logo.content;

// Create ESCPOS image
let escposImage = new ESCPOSImage(image);

// Initialize ESCPOS printer
let escposPrinter = new ESCPOSPrinter(new Buffer([]));

// Print image
escposPrinter.raster(escposImage);

// And also print new line
escposPrinter.printLn();

// Assign data to print queue
printData = Buffer.concat([escposPrinter.getBuffer(), printData]);

The data is then divided into packets and sent using write method of Bluetooth Serial plugin.

If you wonder about the commands:

TXT_ALIGN_LT: [0x1b, 0x61, 0x00],     // Left justification
TXT_ALIGN_CT: [0x1b, 0x61, 0x01],     // Centering
TXT_ALIGN_RT: [0x1b, 0x61, 0x02],     // Right justification

S_RASTER_N: [0x1d, 0x76, 0x30, 0x00], // Set raster image normal size

CTL_LF: [0x0a], // Print and line feed
CTL_CR: [0x0d], // Carriage return

Hope this helps someone. Cheers

Upvotes: 6

Fractal Mind
Fractal Mind

Reputation: 462

I was able to print to the escpos compatible printer using an external library.

Follow these Guides:

  1. https://levelup.gitconnected.com/how-to-print-on-a-bluetooth-printer-using-your-ionic-application-ceabc45abf75

  2. https://www.neodynamic.com/articles/How-to-generate-and-print-raw-ESC-POS-commands-from-Javascript/

enter image description here

  • Device: Samsung Tab A 10.1
  • Printer: MUNBYN POS
  • Ionic 4, Angular 8, Ionic Native Bluetooth Serial

Upvotes: 0

Fractal Mind
Fractal Mind

Reputation: 462

testtest2

Hi Filip Matys

I try to print your StarWars image with your class and having mixed results. From my understanding you try to reuse the node-escpos did you find a way to have access to all packags/printer/index.js fonctions?

Like:

printer
  .font('a')
  .align('ct')
  .style('bu')
  .size(1, 1)
  .text('The quick brown fox jumps over the lazy dog')
  .text('敏捷的棕色狐狸跳过懒狗')
  .barcode('1234567', 'EAN8')
  .table(["One", "Two", "Three"])
  .tableCustom([
    { text:"Left", align:"LEFT", width:0.33 },
     { text:"Center", align:"CENTER", width:0.33},
     { text:"Right", align:"RIGHT", width:0.33 }
  ])
  .qrimage('https://github.com/song940/node-escpos', function(err){
     this.cut();
     this.close();
  });

Upvotes: 0

Related Questions