mirror of
https://github.com/OpenEPaperLink/OpenEPaperLink.git
synced 2026-03-21 12:05:51 +01:00
added javascript g5 decoder for previews
This commit is contained in:
392
ESP32_AP-Flasher/wwwroot/g5decoder.js
Normal file
392
ESP32_AP-Flasher/wwwroot/g5decoder.js
Normal file
@@ -0,0 +1,392 @@
|
||||
//
|
||||
// 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 (pPage.y > 580) console.log("line " + visibleX + "-" + visibleRun);
|
||||
|
||||
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;
|
||||
if (pPage.y > 580) console.log("ix: " + pBufIndex + " bit " + ulBitOff);
|
||||
ulBits = TIFFMOTOLONG(pBuf, pBufIndex);
|
||||
}
|
||||
if (pPage.y > 580) console.log(ulBits.toString(2).padStart(32, '0') + " offset " + ulBitOff);
|
||||
|
||||
if (((ulBits << ulBitOff) & 0x80000000) !== 0) {
|
||||
if (pPage.y > 580) console.log("1> a0: " + pRef[pRefIndex] + " prefindex " + pRefIndex);
|
||||
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];
|
||||
if (pPage.y > 580) console.log("s: " + sCode);
|
||||
switch (sCode) {
|
||||
case 1: case 2: case 3: // V(-1), V(-2), V(-3)
|
||||
a0 = pRef[pRefIndex] - sCode; // A0 = B1 - x
|
||||
if (pPage.y > 580) console.log("01> a0: " + pRef[pRefIndex] + " scode " + sCode + " prefindex " + pRefIndex);
|
||||
pCur[pCurIndex++] = a0;
|
||||
if (pRefIndex == 0) {
|
||||
pRefIndex += 2;
|
||||
if (pPage.y > 580) console.log("01> =0 new prefindex " + pRefIndex);
|
||||
}
|
||||
pRefIndex--;
|
||||
while (a0 >= pRef[pRefIndex]) {
|
||||
pRefIndex += 2;
|
||||
if (pPage.y > 580) console.log("01> >= new prefindex " + pRefIndex);
|
||||
}
|
||||
if (pPage.y > 580) console.log("01> new prefindex " + pRefIndex);
|
||||
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;
|
||||
}
|
||||
if (pPage.y > 580) console.log("H: " + tot_run + " - " + tot_run1);
|
||||
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
|
||||
console.log("scode " + sCode);
|
||||
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++) {
|
||||
console.log(y + " " + decoder.pBufIndex + " " + data.length);
|
||||
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);
|
||||
}
|
||||
|
||||
if (y > 580) console.log("draw line " + y);
|
||||
G5DrawLine(decoder, decoder.pCur, lineBuffer);
|
||||
const temp = decoder.pRef;
|
||||
decoder.pRef = decoder.pCur;
|
||||
decoder.pCur = temp;
|
||||
}
|
||||
|
||||
console.log("Decoding complete.");
|
||||
return outputBuffer;
|
||||
} catch (error) {
|
||||
console.error("Error during G5 decoding:", error.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<title>Open EPaper Link Access Point</title>
|
||||
<script src="main.js" defer></script>
|
||||
<script src="g5decoder.js?2"></script>
|
||||
<link rel="stylesheet" href="main.css" type="text/css" />
|
||||
<!--<link rel="icon" type="image/vnd.icon" href="favicon.ico">-->
|
||||
<link rel="stylesheet"
|
||||
|
||||
@@ -1207,6 +1207,11 @@ function drawCanvas(buffer, canvas, hwtype, tagmac, doRotate) {
|
||||
if (data.length > 0 && tagTypes[hwtype].zlib > 0 && $('#tag' + tagmac).dataset.ver >= tagTypes[hwtype].zlib) {
|
||||
data = processZlib(data);
|
||||
}
|
||||
if (data.length > 0 && tagTypes[hwtype].g5 > 0 && $('#tag' + tagmac).dataset.ver >= tagTypes[hwtype].g5) {
|
||||
console.log("calling G5 decoder");
|
||||
console.log(tagTypes[hwtype].width);
|
||||
data = processG5(data, tagTypes[hwtype].height, tagTypes[hwtype].width * 2);
|
||||
}
|
||||
|
||||
[canvas.width, canvas.height] = [tagTypes[hwtype].width, tagTypes[hwtype].height] || [0, 0];
|
||||
if (tagTypes[hwtype].rotatebuffer % 2) [canvas.width, canvas.height] = [canvas.height, canvas.width];
|
||||
@@ -1521,6 +1526,7 @@ async function getTagtype(hwtype) {
|
||||
contentids: Object.values(jsonData.contentids ?? []),
|
||||
options: Object.values(jsonData.options ?? []),
|
||||
zlib: parseInt(jsonData.zlib_compression || "0", 16),
|
||||
g5: parseInt(jsonData.g5_compression || "0", 16),
|
||||
shortlut: parseInt(jsonData.shortlut),
|
||||
busy: false,
|
||||
usetemplate: parseInt(jsonData.usetemplate || "0", 10)
|
||||
@@ -1947,4 +1953,4 @@ function showPreview(previewWindow, element) {
|
||||
console.error('fetch preview image error:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user