node.jsthermal-printerprintersepson

Converting an image to base64Binary data for printing on EPSON TM-M30 printer via XML request


I need to print an image on the EPSON TM-M30 printer using XML, and according to the printer documentation, I need to include the image data in a <image> tag that requires a base64Binary data.

How to convert my image file to base64Binary data in nodeJs?

This is an example of the data I want to extract from the image. As you can see, it's not a simple base64 string that can be easily obtained from a buffer. To be honest, I'm not sure what this raster image format means.

enter image description here

Edit:

To provide further insights into the question, here is how the Epson SDK calculates this raster image data in the browser.:

// context = canvas.getContext("2d")

const addImage = function (context, x, y, width, height, color, mode) {
    var s = "",
      ht = this.halftone, // Posible Values this.HALFTONE_DITHER=0;this.HALFTONE_ERROR_DIFFUSION=1;this.HALFTONE_THRESHOLD=2
      br = this.brightness, // br >= 0.1 && br <= 10
      imgdata,
      raster;

    ...

    imgdata = context.getImageData(x, y, width, height);
    raster =
      mode == this.MODE_GRAY16
        ? toGrayImage(imgdata, br)
        : toMonoImage(imgdata, ht, br);

    this.message += "<image" + s + ">" + toBase64Binary(raster) + "</image>";
    
    return this;
  };
  
  function toMonoImage(imgdata, s, g) {
    var x = String.fromCharCode,
      m8 = [
        [2, 130, 34, 162, 10, 138, 42, 170],
        [194, 66, 226, 98, 202, 74, 234, 106],
        [50, 178, 18, 146, 58, 186, 26, 154],
        [242, 114, 210, 82, 250, 122, 218, 90],
        [14, 142, 46, 174, 6, 134, 38, 166],
        [206, 78, 238, 110, 198, 70, 230, 102],
        [62, 190, 30, 158, 54, 182, 22, 150],
        [254, 126, 222, 94, 246, 118, 214, 86],
      ],
      d = imgdata.data,
      w = imgdata.width,
      h = imgdata.height,
      r = new Array(((w + 7) >> 3) * h),
      n = 0,
      p = 0,
      q = 0,
      t = 128,
      e = new Array(),
      e1,
      e2,
      b,
      v,
      f,
      i,
      j;
    if (s == 1) {
      i = w;
      while (i--) {
        e.push(0);
      }
    }
    for (j = 0; j < h; j++) {
      e1 = 0;
      e2 = 0;
      i = 0;
      while (i < w) {
        b = i & 7;
        if (s == 0) {
          t = m8[j & 7][b];
        }
        v =
          (Math.pow(
            (((d[p++] * 0.29891 + d[p++] * 0.58661 + d[p++] * 0.11448) * d[p]) /
              255 +
              255 -
              d[p++]) /
              255,
            1 / g
          ) *
            255) |
          0;
        if (s == 1) {
          v += (e[i] + e1) >> 4;
          f = v - (v < t ? 0 : 255);
          if (i > 0) {
            e[i - 1] += f;
          }
          e[i] = f * 7 + e2;
          e1 = f * 5;
          e2 = f * 3;
        }
        if (v < t) {
          n |= 128 >> b;
        }
        i++;
        if (b == 7 || i == w) {
          r[q++] = x(n == 16 ? 32 : n);
          n = 0;
        }
      }
    }
    return r.join("");
  }
  

  function toGrayImage(imgdata, g) {
    var x = String.fromCharCode,
      m4 = [
        [0, 9, 2, 11],
        [13, 4, 15, 6],
        [3, 12, 1, 10],
        [16, 7, 14, 5],
      ],
      thermal = [
        0, 7, 13, 19, 23, 27, 31, 35, 40, 44, 49, 52, 54, 55, 57, 59, 61, 62, 64,
        66, 67, 69, 70, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
        83, 84, 85, 86, 86, 87, 88, 88, 89, 90, 90, 91, 91, 92, 93, 93, 94, 94,
        95, 96, 96, 97, 98, 98, 99, 99, 100, 101, 101, 102, 102, 103, 103, 104,
        104, 105, 105, 106, 106, 107, 107, 108, 108, 109, 109, 110, 110, 111, 111,
        112, 112, 112, 113, 113, 114, 114, 115, 115, 116, 116, 117, 117, 118, 118,
        119, 119, 120, 120, 120, 121, 121, 122, 122, 123, 123, 123, 124, 124, 125,
        125, 125, 126, 126, 127, 127, 127, 128, 128, 129, 129, 130, 130, 130, 131,
        131, 132, 132, 132, 133, 133, 134, 134, 135, 135, 135, 136, 136, 137, 137,
        137, 138, 138, 139, 139, 139, 140, 140, 141, 141, 141, 142, 142, 143, 143,
        143, 144, 144, 145, 145, 146, 146, 146, 147, 147, 148, 148, 148, 149, 149,
        150, 150, 150, 151, 151, 152, 152, 152, 153, 153, 154, 154, 155, 155, 155,
        156, 156, 157, 157, 158, 158, 159, 159, 160, 160, 161, 161, 161, 162, 162,
        163, 163, 164, 164, 165, 165, 166, 166, 166, 167, 167, 168, 168, 169, 169,
        170, 170, 171, 171, 172, 173, 173, 174, 175, 175, 176, 177, 178, 178, 179,
        180, 180, 181, 182, 182, 183, 184, 184, 185, 186, 186, 187, 189, 191, 193,
        195, 198, 200, 202, 255,
      ],
      d = imgdata.data,
      w = imgdata.width,
      h = imgdata.height,
      r = new Array(((w + 1) >> 1) * h),
      n = 0,
      p = 0,
      q = 0,
      b,
      v,
      v1,
      i,
      j;
    for (j = 0; j < h; j++) {
      i = 0;
      while (i < w) {
        b = i & 1;
        v =
          thermal[
            (Math.pow(
              (((d[p++] * 0.29891 + d[p++] * 0.58661 + d[p++] * 0.11448) * d[p]) /
                255 +
                255 -
                d[p++]) /
                255,
              1 / g
            ) *
              255) |
              0
          ];
        v1 = (v / 17) | 0;
        if (m4[j & 3][i & 3] < v % 17) {
          v1++;
        }
        n |= v1 << ((1 - b) << 2);
        i++;
        if (b == 1 || i == w) {
          r[q++] = x(n);
          n = 0;
        }
      }
    }
    return r.join("");
  }

  function toBase64Binary(s) {
    var l = s.length,
      r = new Array(((l + 2) / 3) << 2),
      t = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
      p = (3 - (l % 3)) % 3,
      j = 0,
      i = 0,
      n;
    s += "\x00\x00";
    while (i < l) {
      n =
        (s.charCodeAt(i++) << 16) | (s.charCodeAt(i++) << 8) | s.charCodeAt(i++);
      r[j++] = t.charAt((n >> 18) & 63);
      r[j++] = t.charAt((n >> 12) & 63);
      r[j++] = t.charAt((n >> 6) & 63);
      r[j++] = t.charAt(n & 63);
    }
    while (p--) {
      r[--j] = "=";
    }
    return r.join("");
  }

Solution

  • For those looking for an answer:

    You can also use the exact same algorithm in node J as follows:

    Option 1:

    toGrayImage, toMonoImage & toBase64Binary are described in the question above.

        import * as fs from 'fs';
        import { createCanvas, Image } from 'canvas'
    
        ...
        
        const data = await fs.promises.readFile(imagePath);
     
        var img = new Image();
        img.src = data;
    
        var canvas = createCanvas(img.width, img.height);
        var context = canvas.getContext("2d");
        context.drawImage(img, 0, 0, img.width, img.height);
    
    
        const imgdata = context.getImageData(0, 0, img.width, img.height);
        raster = 
          mode == MODE_GRAY16
            ? toGrayImage(imgdata, br)
            : toMonoImage(imgdata, ht, br);
        
    
        result = toBase64Binary(raster);
    

    Option 2:

    You can achieve the same result using canvas-dither & canvas-flatten library:

        import { createCanvas, Image } from 'canvas';
        import * as Dither from 'canvas-dither';
        import * as Flatten from 'canvas-flatten';
        import * as fs from 'fs';
    
        ...
    
        const data = await fs.promises.readFile(imagePath);
        const image = new Image();
        image.src = data;
        
        const canvas = createCanvas(width, height);
        const context = canvas.getContext("2d");
        context.drawImage(image, 0, 0, width, height);
        let imageData = context.getImageData(0, 0, width, height);
    
        // Flatten the transparency on a white background
        image = Flatten.flatten(imageData, [0xff, 0xff, 0xff]);
    
        switch (algorithm) {
          case "threshold":
            imageData = Dither.threshold(image, threshold);
            break;
          case "bayer":
            imageData = Dither.bayer(image, threshold);
            break;
          case "floydsteinberg":
            imageData = Dither.floydsteinberg(image);
            break;
          case "atkinson":
            imageData = Dither.atkinson(image);
            break;
        }
    
    
        const getPixel = (x, y) =>
          x < width && y < height ? (image.data[(width * y + x) * 4] > 0 ? 0 : 1) : 0;
    
        const getRowData = (width, height) => {
          const bytes = new Uint8Array((width * height) >> 3);
    
          for (let y = 0; y < height; y++) {
            for (let x = 0; x < width; x = x + 8) {
              for (let b = 0; b < 8; b++) {
                bytes[y * (width >> 3) + (x >> 3)] |= getPixel(x + b, y) << (7 - b);
              }
            }
          }
    
          return bytes;
        };
        
    
       const rasterData = getRowData(width, height);
    
       const binaryData = new Uint8Array(rasterData);
    
       let base64String = "";
       for (let i = 0; i < binaryData.length; i++) {
         base64String += String.fromCharCode(binaryData[i]);
       }
        
       result = toBase64Binary(base64String);