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.
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("");
}
For those looking for an answer:
You can also use the exact same algorithm in node J as follows:
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);
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);