108 lines
3.1 KiB
TypeScript
108 lines
3.1 KiB
TypeScript
/**
|
|
* XCU PulsarCodec — TypeScript Port
|
|
* Delta-based pixel compression codec ported from Rust (xcu-wasm-sdk/pulsar.rs)
|
|
*
|
|
* Protocol: 7 bytes per changed pixel [X_hi, X_lo, Y_hi, Y_lo, R, G, B]
|
|
*
|
|
* This is the PRIMARY video codec for XCU. H.264 WebCodecs is the fallback.
|
|
* PulsarCodec sends ONLY pixels that changed since the last frame,
|
|
* achieving near-zero bandwidth when the scene is static.
|
|
*/
|
|
|
|
const NEUROMORPHIC_THRESHOLD = 25;
|
|
|
|
export class XCUPulsarCodec {
|
|
private lastFrame: Uint8Array;
|
|
private canvasBuffer: Uint8Array;
|
|
private width: number;
|
|
private height: number;
|
|
|
|
constructor(width: number, height: number) {
|
|
this.width = width;
|
|
this.height = height;
|
|
this.lastFrame = new Uint8Array(width * height * 4);
|
|
this.canvasBuffer = new Uint8Array(width * height * 4);
|
|
// Initialize canvas buffer to black with full alpha
|
|
for (let i = 3; i < this.canvasBuffer.length; i += 4) {
|
|
this.canvasBuffer[i] = 255;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Encode delta: Compare current RGBA frame to last frame.
|
|
* Output only changed pixels as 7-byte chunks [X_hi, X_lo, Y_hi, Y_lo, R, G, B]
|
|
*/
|
|
encodeDelta(currentFrame: Uint8Array): Uint8Array {
|
|
const deltaChunks: number[] = [];
|
|
|
|
for (let i = 0; i < currentFrame.length; i += 4) {
|
|
const currR = currentFrame[i];
|
|
const currG = currentFrame[i + 1];
|
|
const currB = currentFrame[i + 2];
|
|
|
|
const lastR = this.lastFrame[i];
|
|
const lastG = this.lastFrame[i + 1];
|
|
const lastB = this.lastFrame[i + 2];
|
|
|
|
const diffTotal = Math.abs(currR - lastR) + Math.abs(currG - lastG) + Math.abs(currB - lastB);
|
|
|
|
if (diffTotal > NEUROMORPHIC_THRESHOLD) {
|
|
const pixelIndex = (i / 4) | 0;
|
|
const x = pixelIndex % this.width;
|
|
const y = (pixelIndex / this.width) | 0;
|
|
|
|
// 7-byte format: [X_hi, X_lo, Y_hi, Y_lo, R, G, B]
|
|
deltaChunks.push(
|
|
(x >> 8) & 0xFF, x & 0xFF,
|
|
(y >> 8) & 0xFF, y & 0xFF,
|
|
currR, currG, currB,
|
|
);
|
|
|
|
// Update last frame (only changed pixels)
|
|
this.lastFrame[i] = currR;
|
|
this.lastFrame[i + 1] = currG;
|
|
this.lastFrame[i + 2] = currB;
|
|
}
|
|
}
|
|
|
|
return new Uint8Array(deltaChunks);
|
|
}
|
|
|
|
/**
|
|
* Decode truecolor: Render RGB asli dari delta payload ke canvas buffer.
|
|
* Berbeda dengan decode_xray di Rust yang render hijau neon.
|
|
*/
|
|
decodeTruecolor(payload: Uint8Array): Uint8Array {
|
|
for (let i = 0; i + 6 < payload.length; i += 7) {
|
|
const x = (payload[i] << 8) | payload[i + 1];
|
|
const y = (payload[i + 2] << 8) | payload[i + 3];
|
|
const r = payload[i + 4];
|
|
const g = payload[i + 5];
|
|
const b = payload[i + 6];
|
|
|
|
const bufIdx = (y * this.width + x) * 4;
|
|
if (bufIdx >= 0 && bufIdx + 3 < this.canvasBuffer.length) {
|
|
this.canvasBuffer[bufIdx] = r;
|
|
this.canvasBuffer[bufIdx + 1] = g;
|
|
this.canvasBuffer[bufIdx + 2] = b;
|
|
this.canvasBuffer[bufIdx + 3] = 255; // Full alpha
|
|
}
|
|
}
|
|
return this.canvasBuffer;
|
|
}
|
|
|
|
/**
|
|
* Reset codec state (e.g., when switching video source)
|
|
*/
|
|
reset(): void {
|
|
this.lastFrame.fill(0);
|
|
this.canvasBuffer.fill(0);
|
|
for (let i = 3; i < this.canvasBuffer.length; i += 4) {
|
|
this.canvasBuffer[i] = 255;
|
|
}
|
|
}
|
|
|
|
get bufferWidth(): number { return this.width; }
|
|
get bufferHeight(): number { return this.height; }
|
|
}
|