This repository has been archived on 2024-09-16. You can view files and clone it, but cannot push or open issues or pull requests.
niimblue-nightly/niimbluelib/src/image_encoder.ts

117 lines
3.3 KiB
TypeScript

import { Utils } from "./utils";
export type ImageRow = {
dataType: "void" | "pixels";
rowNumber: number;
repeat: number;
blackPixelsCount: number;
rowData?: Uint8Array;
};
export type EncodedImage = {
cols: number;
rows: number;
rowsData: ImageRow[];
};
export type HeadPosition = "left" | "top";
export class ImageEncoder {
/** headPos = "left" rotates image for 90 degrees clockwise */
public static encodeCanvas(canvas: HTMLCanvasElement, headPos: HeadPosition = "left"): EncodedImage {
const ctx: CanvasRenderingContext2D = canvas.getContext("2d")!;
const iData: ImageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const rowsData: ImageRow[] = [];
let cols: number = canvas.width;
let rows: number = canvas.height;
if (headPos === "left") {
cols = canvas.height;
rows = canvas.width;
}
if (cols % 8 !== 0) {
throw new Error("Column count must be multiple of 8");
}
for (let row = 0; row < rows; row++) {
let isVoid: boolean = true;
let blackPixelsCount: number = 0;
const rowData = new Uint8Array(cols / 8);
for (let colOct = 0; colOct < cols / 8; colOct++) {
let pixelsOctet: number = 0;
for (let colBit = 0; colBit < 8; colBit++) {
if (ImageEncoder.isPixelNonWhite(iData, colOct * 8 + colBit, row, headPos)) {
pixelsOctet |= 1 << (7 - colBit);
isVoid = false;
blackPixelsCount++;
}
}
rowData[colOct] = pixelsOctet;
}
const newPart: ImageRow = {
dataType: isVoid ? "void" : "pixels",
rowNumber: row,
repeat: 1,
rowData: isVoid ? undefined : rowData,
blackPixelsCount,
};
// Check previous row and increment repeats instead of adding new row if data is same
if (rowsData.length === 0) {
rowsData.push(newPart);
} else {
const lastPacket: ImageRow = rowsData[rowsData.length - 1];
let same: boolean = newPart.dataType === lastPacket.dataType;
if (same && newPart.dataType === "pixels") {
same = Utils.u8ArraysEqual(newPart.rowData!, lastPacket.rowData!);
}
if (same) {
lastPacket.repeat++;
} else {
rowsData.push(newPart);
}
}
}
return { cols, rows, rowsData };
}
/** headPos = "left" rotates image to 90 degrees clockwise */
public static isPixelNonWhite(iData: ImageData, x: number, y: number, headPos: HeadPosition = "left"): boolean {
let idx = y * iData.width + x;
if (headPos === "left") {
idx = (iData.height - 1 - x) * iData.width + y;
}
idx *= 4;
return iData.data[idx] !== 255 || iData.data[idx + 1] !== 255 || iData.data[idx + 2] !== 255;
}
/**
* @param data Encoded pixels (every byte is 8 pixels)
* @returns Array of indexes where every index stored in two bytes (big endian)
*/
public static indexPixels(data: Uint8Array): Uint8Array {
const result: number[] = [];
for (let bytePos = 0; bytePos < data.byteLength; bytePos++) {
let b: number = data[bytePos];
for (let bitPos = 0; bitPos < 8; bitPos++) {
// iterate from most significant bit of byte
if (b & (1 << (7 - bitPos))) {
result.push(...Utils.u16ToBytes(bytePos * 8 + bitPos));
}
}
}
return new Uint8Array(result);
}
}