mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 09:04:24 +01:00
378 lines
12 KiB
JavaScript
378 lines
12 KiB
JavaScript
//
|
|
// Group5
|
|
// A 1-bpp image decoder
|
|
//
|
|
// Written by Larry Bank
|
|
// Copyright (c) 2024 BitBank Software, Inc.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the file ./LICENSE.
|
|
//
|
|
// As of the Change Date specified in that file, in accordance with
|
|
// the Business Source License, use of this software will be governed
|
|
// by the Apache License, Version 2.0, included in the file
|
|
// ./APL.txt.
|
|
|
|
// Converted from C to Javascript by Nic Limper
|
|
|
|
// Define constants
|
|
const MAX_IMAGE_FLIPS = 640;
|
|
|
|
// Horizontal prefix bits
|
|
const HORIZ_SHORT_SHORT = 0;
|
|
const HORIZ_SHORT_LONG = 1;
|
|
const HORIZ_LONG_SHORT = 2;
|
|
const HORIZ_LONG_LONG = 3;
|
|
|
|
// Return code for encoder and decoder
|
|
const G5_SUCCESS = 0;
|
|
const G5_INVALID_PARAMETER = 1;
|
|
const G5_DECODE_ERROR = 2;
|
|
const G5_UNSUPPORTED_FEATURE = 3;
|
|
const G5_ENCODE_COMPLETE = 4;
|
|
const G5_DECODE_COMPLETE = 5;
|
|
const G5_NOT_INITIALIZED = 6;
|
|
const G5_DATA_OVERFLOW = 7;
|
|
const G5_MAX_FLIPS_EXCEEDED = 8;
|
|
|
|
// Utility function equivalent to the TIFFMOTOLONG macro
|
|
function TIFFMOTOLONG(p, ix) {
|
|
let value = 0;
|
|
if (ix < p.length) value |= p[ix] << 24;
|
|
if (ix + 1 < p.length) value |= p[ix + 1] << 16;
|
|
if (ix + 2 < p.length) value |= p[ix + 2] << 8;
|
|
if (ix + 3 < p.length) value |= p[ix + 3];
|
|
return value;
|
|
}
|
|
|
|
// Constants for bit manipulation
|
|
const REGISTER_WIDTH = 32; // Must align with a 32-bit system in C++
|
|
|
|
/*
|
|
The code tree that follows has: bit_length, decode routine
|
|
These codes are for Group 4 (MMR) decoding
|
|
|
|
01 = vertneg1, 11h = vert1, 20h = horiz, 30h = pass, 12h = vert2
|
|
02 = vertneg2, 13h = vert3, 03 = vertneg3, 90h = trash
|
|
*/
|
|
|
|
const code_table = [
|
|
0x90, 0, 0x40, 0, // trash, uncompressed mode - codes 0 and 1
|
|
3, 7, // V(-3) pos = 2
|
|
0x13, 7, // V(3) pos = 3
|
|
2, 6, 2, 6, // V(-2) pos = 4,5
|
|
0x12, 6, 0x12, 6, // V(2) pos = 6,7
|
|
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4, // pass pos = 8->F
|
|
0x30, 4, 0x30, 4, 0x30, 4, 0x30, 4,
|
|
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, // horiz pos = 10->1F
|
|
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
|
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3,
|
|
0x20, 3, 0x20, 3, 0x20, 3, 0x20, 3, // V(-1) pos = 20->2F
|
|
1, 3, 1, 3, 1, 3, 1, 3,
|
|
1, 3, 1, 3, 1, 3, 1, 3,
|
|
1, 3, 1, 3, 1, 3, 1, 3,
|
|
1, 3, 1, 3, 1, 3, 1, 3,
|
|
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3, // V(1) pos = 30->3F
|
|
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
|
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3,
|
|
0x11, 3, 0x11, 3, 0x11, 3, 0x11, 3
|
|
];
|
|
|
|
class G5DECIMAGE {
|
|
constructor() {
|
|
this.iWidth = 0;
|
|
this.iHeight = 0;
|
|
this.iError = 0;
|
|
this.y = 0;
|
|
this.iVLCSize = 0;
|
|
this.iHLen = 0;
|
|
this.iPitch = 0;
|
|
this.u32Accum = 0;
|
|
this.ulBitOff = 0;
|
|
this.ulBits = 0;
|
|
this.pSrc = null; // Input buffer
|
|
this.pBuf = null; // Current buffer index
|
|
this.pBufIndex = 0;
|
|
this.pCur = new Int16Array(MAX_IMAGE_FLIPS); // Current state
|
|
this.pRef = new Int16Array(MAX_IMAGE_FLIPS); // Reference state
|
|
}
|
|
}
|
|
|
|
|
|
//static int g5_decode_init(G5DECIMAGE *pImage, int iWidth, int iHeight, uint8_t *pData, int iDataSize)
|
|
function g5_decode_init(pImage, iWidth, iHeight, pData, iDataSize) {
|
|
if (
|
|
pImage == null ||
|
|
iWidth < 1 ||
|
|
iHeight < 1 ||
|
|
pData == null ||
|
|
iDataSize < 1
|
|
) {
|
|
return G5_INVALID_PARAMETER;
|
|
}
|
|
|
|
pImage.iVLCSize = iDataSize;
|
|
pImage.pSrc = pData;
|
|
pImage.ulBitOff = 0;
|
|
pImage.y = 0;
|
|
pImage.ulBits = TIFFMOTOLONG(pData, 0); // Preload the first 32 bits of data
|
|
pImage.iWidth = iWidth;
|
|
pImage.iHeight = iHeight;
|
|
|
|
return G5_SUCCESS;
|
|
}
|
|
|
|
|
|
//static void G5DrawLine(G5DECIMAGE *pPage, int16_t *pCurFlips, uint8_t *pOut)
|
|
function G5DrawLine(pPage, pCurFlips, pOut) {
|
|
const xright = pPage.iWidth;
|
|
let pCurIndex = 0;
|
|
|
|
// Initialize output to white (0xff)
|
|
const len = (xright + 7) >> 3; // Number of bytes to generate
|
|
pOut.fill(0xff, 0, len);
|
|
|
|
let x = 0;
|
|
while (x < xright) { // While the scaled x is within the window bounds
|
|
const startX = pCurFlips[pCurIndex++]; // Black starting point
|
|
const run = pCurFlips[pCurIndex++] - startX; // Get the black run
|
|
|
|
if (startX >= xright || run <= 0) break;
|
|
|
|
// Calculate visible run
|
|
let visibleX = Math.max(0, startX);
|
|
let visibleRun = Math.min(xright, startX + run) - visibleX;
|
|
|
|
if (visibleRun > 0) {
|
|
const startByte = visibleX >> 3;
|
|
const endByte = (visibleX + visibleRun) >> 3;
|
|
|
|
const lBit = (0xff << (8 - (visibleX & 7))) & 0xff; // Left bitmask based on the starting x position
|
|
const rBit = 0xff >> ((visibleX + visibleRun) & 7); // Right bitmask based on the ending x position
|
|
|
|
if (endByte == startByte) {
|
|
// If the run fits in a single byte, combine left and right bit masks
|
|
pOut[startByte] &= (lBit | rBit);
|
|
} else {
|
|
// Mask the left-most byte
|
|
pOut[startByte] &= lBit;
|
|
|
|
// Set intermediate bytes to 0
|
|
for (let i = startByte + 1; i < endByte; i++) {
|
|
pOut[i] = 0x00;
|
|
}
|
|
|
|
// Mask the right-most byte if it's not fully aligned
|
|
pOut[endByte] &= rBit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Initialize internal structures to decode the image
|
|
//
|
|
function Decode_Begin(pPage) {
|
|
const xsize = pPage.iWidth;
|
|
|
|
// Seed the current and reference lines with xsize for V(0) codes
|
|
for (let i = 0; i < MAX_IMAGE_FLIPS - 2; i++) {
|
|
pPage.pRef[i] = xsize;
|
|
pPage.pCur[i] = xsize;
|
|
}
|
|
|
|
// Prefill both current and reference lines with 0x7fff to prevent walking off the end
|
|
// if the data gets bunged and the current X is > XSIZE
|
|
pPage.pCur[MAX_IMAGE_FLIPS - 2] = pPage.pRef[MAX_IMAGE_FLIPS - 2] = 0x7fff;
|
|
pPage.pCur[MAX_IMAGE_FLIPS - 1] = pPage.pRef[MAX_IMAGE_FLIPS - 1] = 0x7fff;
|
|
|
|
pPage.pBuf = pPage.pSrc; // Start buffer
|
|
pPage.pBufIndex = 0;
|
|
|
|
// Load 32 bits to start (use a helper function to interpret bytes as a 32-bit integer)
|
|
pPage.ulBits = TIFFMOTOLONG(pPage.pSrc, 0);
|
|
pPage.ulBitOff = 0;
|
|
|
|
// Calculate the number of bits needed for a long horizontal code
|
|
pPage.iHLen = 32 - Math.clz32(pPage.iWidth); // clz32 counts leading zeroes in JavaScript
|
|
}
|
|
|
|
|
|
// Decode a single line of G5 data
|
|
//
|
|
function DecodeLine(pPage) {
|
|
let a0 = -1;
|
|
let a0_p, b1;
|
|
let pCurIndex = 0, pRefIndex = 0;
|
|
const pCur = pPage.pCur;
|
|
const pRef = pPage.pRef;
|
|
let ulBits = pPage.ulBits;
|
|
let ulBitOff = pPage.ulBitOff;
|
|
let pBufIndex = pPage.pBufIndex;
|
|
const pBuf = pPage.pBuf;
|
|
const xsize = pPage.iWidth;
|
|
const u32HLen = pPage.iHLen;
|
|
const u32HMask = (1 << u32HLen) - 1;
|
|
let tot_run, tot_run1;
|
|
|
|
while (a0 < xsize) {
|
|
if (ulBitOff > (REGISTER_WIDTH - 8)) {
|
|
pBufIndex += (ulBitOff >> 3);
|
|
ulBitOff &= 7;
|
|
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
|
}
|
|
|
|
if (((ulBits << ulBitOff) & 0x80000000) !== 0) {
|
|
a0 = pRef[pRefIndex++];
|
|
pCur[pCurIndex++] = a0;
|
|
ulBitOff++;
|
|
} else {
|
|
const lBits = (ulBits >> (REGISTER_WIDTH - 8 - ulBitOff)) & 0xfe;
|
|
const sCode = code_table[lBits];
|
|
ulBitOff += code_table[lBits + 1];
|
|
switch (sCode) {
|
|
case 1: case 2: case 3: // V(-1), V(-2), V(-3)
|
|
a0 = pRef[pRefIndex] - sCode; // A0 = B1 - x
|
|
pCur[pCurIndex++] = a0;
|
|
if (pRefIndex == 0) {
|
|
pRefIndex += 2;
|
|
}
|
|
pRefIndex--;
|
|
while (a0 >= pRef[pRefIndex]) {
|
|
pRefIndex += 2;
|
|
}
|
|
break;
|
|
|
|
case 0x11: case 0x12: case 0x13: // V(1), V(2), V(3)
|
|
a0 = pRef[pRefIndex++];
|
|
b1 = a0;
|
|
a0 += sCode & 7;
|
|
if (b1 !== xsize && a0 < xsize) {
|
|
while (a0 >= pRef[pRefIndex]) {
|
|
pRefIndex += 2;
|
|
}
|
|
}
|
|
if (a0 > xsize) {
|
|
a0 = xsize;
|
|
}
|
|
pCur[pCurIndex++] = a0;
|
|
break;
|
|
|
|
case 0x20: // Horizontal codes
|
|
if (ulBitOff > (REGISTER_WIDTH - 16)) {
|
|
pBufIndex += (ulBitOff >> 3);
|
|
ulBitOff &= 7;
|
|
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
|
}
|
|
|
|
a0_p = Math.max(0, a0);
|
|
const lBits = (ulBits >> ((REGISTER_WIDTH - 2) - ulBitOff)) & 0x3;
|
|
ulBitOff += 2;
|
|
|
|
switch (lBits) {
|
|
case HORIZ_SHORT_SHORT:
|
|
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
|
ulBitOff += 3;
|
|
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
|
ulBitOff += 3;
|
|
break;
|
|
|
|
case HORIZ_SHORT_LONG:
|
|
tot_run = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
|
ulBitOff += 3;
|
|
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
|
ulBitOff += u32HLen;
|
|
break;
|
|
|
|
case HORIZ_LONG_SHORT:
|
|
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
|
ulBitOff += u32HLen;
|
|
tot_run1 = (ulBits >> ((REGISTER_WIDTH - 3) - ulBitOff)) & 0x7;
|
|
ulBitOff += 3;
|
|
break;
|
|
|
|
case HORIZ_LONG_LONG:
|
|
tot_run = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
|
ulBitOff += u32HLen;
|
|
if (ulBitOff > (REGISTER_WIDTH - 16)) {
|
|
pBufIndex += (ulBitOff >> 3);
|
|
ulBitOff &= 7;
|
|
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
|
}
|
|
tot_run1 = (ulBits >> ((REGISTER_WIDTH - u32HLen) - ulBitOff)) & u32HMask;
|
|
ulBitOff += u32HLen;
|
|
break;
|
|
}
|
|
a0 = a0_p + tot_run;
|
|
pCur[pCurIndex++] = a0;
|
|
a0 += tot_run1;
|
|
|
|
if (a0 < xsize) {
|
|
while (a0 >= pRef[pRefIndex]) {
|
|
pRefIndex += 2;
|
|
}
|
|
}
|
|
pCur[pCurIndex++] = a0;
|
|
break;
|
|
|
|
case 0x30: // Pass code
|
|
pRefIndex++;
|
|
a0 = pRef[pRefIndex++];
|
|
break;
|
|
|
|
default: // ERROR
|
|
pPage.iError = G5_DECODE_ERROR;
|
|
return pPage.iError;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pCur[pCurIndex++] = xsize;
|
|
pCur[pCurIndex++] = xsize;
|
|
|
|
pPage.ulBits = ulBits;
|
|
pPage.ulBitOff = ulBitOff;
|
|
pPage.pBufIndex = pBufIndex;
|
|
|
|
return pPage.iError;
|
|
}
|
|
|
|
|
|
function processG5(data, width, height) {
|
|
try {
|
|
let decoder = new G5DECIMAGE();
|
|
let initResult = g5_decode_init(decoder, width, height, data, data.length);
|
|
|
|
if (initResult !== G5_SUCCESS) {
|
|
throw new Error("Initialization failed with code: " + initResult);
|
|
}
|
|
|
|
Decode_Begin(decoder);
|
|
|
|
let outputBuffer = new Uint8Array(height * ((width + 7) >> 3)); // Adjust for byte alignment
|
|
|
|
for (let y = 0; y < height; y++) {
|
|
let lineBuffer = outputBuffer.subarray(y * ((width + 7) >> 3), (y + 1) * ((width + 7) >> 3));
|
|
decoder.y = y;
|
|
let decodeResult = DecodeLine(decoder);
|
|
|
|
if (decodeResult !== G5_SUCCESS) {
|
|
console.log("Decoding error on line " + y + ": " + decoder.iError);
|
|
}
|
|
|
|
G5DrawLine(decoder, decoder.pCur, lineBuffer);
|
|
const temp = decoder.pRef;
|
|
decoder.pRef = decoder.pCur;
|
|
decoder.pCur = temp;
|
|
}
|
|
|
|
return outputBuffer;
|
|
} catch (error) {
|
|
console.error("Error during G5 decoding:", error.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
|