@koiosdigital/matrx-render 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { C as Color16, a as Canvas, W as Widget, b as Color, B as Bounds, D as DrawContext } from './widget-DABrpucj.js';
|
|
2
2
|
export { c as BLACK, T as TRANSPARENT, d as WHITE, e as bounds, f as color16, m as maxFrameCount, g as modInt, p as parseColor } from './widget-DABrpucj.js';
|
|
3
3
|
import { WidgetSpec, RootSpec } from '@koiosdigital/matrx-sdk';
|
|
4
|
-
export { D as DEFAULT_SCREEN_DELAY_MILLIS, F as FrameEncoder, I as Image, W as WebpDecoder, e as encodeWebp, s as setWebpDecoder } from './webp-
|
|
4
|
+
export { D as DEFAULT_SCREEN_DELAY_MILLIS, F as FrameEncoder, I as Image, W as WebpDecoder, e as encodeWebp, s as setWebpDecoder } from './webp-DP8HPoWf.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* BDF bitmap-font parser — a line-for-line port of zachomedia/go-bdf
|
package/dist/index.js
CHANGED
|
@@ -2586,22 +2586,25 @@ async function renderRoot(spec, opts = {}) {
|
|
|
2586
2586
|
|
|
2587
2587
|
// src/encode/webp.ts
|
|
2588
2588
|
var DEFAULT_SCREEN_DELAY_MILLIS = 50;
|
|
2589
|
+
function straightPixel(pix, i, out, o) {
|
|
2590
|
+
const a = pix[i + 3];
|
|
2591
|
+
if (a === 255) {
|
|
2592
|
+
out[o] = pix[i];
|
|
2593
|
+
out[o + 1] = pix[i + 1];
|
|
2594
|
+
out[o + 2] = pix[i + 2];
|
|
2595
|
+
} else if (a !== 0) {
|
|
2596
|
+
const a16 = a * 257;
|
|
2597
|
+
out[o] = Math.floor(pix[i] * 257 * 65535 / a16) >> 8;
|
|
2598
|
+
out[o + 1] = Math.floor(pix[i + 1] * 257 * 65535 / a16) >> 8;
|
|
2599
|
+
out[o + 2] = Math.floor(pix[i + 2] * 257 * 65535 / a16) >> 8;
|
|
2600
|
+
}
|
|
2601
|
+
out[o + 3] = a;
|
|
2602
|
+
}
|
|
2589
2603
|
function toStraightRGBA(canvas) {
|
|
2590
2604
|
const { pix } = canvas;
|
|
2591
2605
|
const out = new Uint8Array(pix.length);
|
|
2592
2606
|
for (let i = 0; i < pix.length; i += 4) {
|
|
2593
|
-
|
|
2594
|
-
if (a === 255) {
|
|
2595
|
-
out[i] = pix[i];
|
|
2596
|
-
out[i + 1] = pix[i + 1];
|
|
2597
|
-
out[i + 2] = pix[i + 2];
|
|
2598
|
-
} else if (a !== 0) {
|
|
2599
|
-
const a16 = a * 257;
|
|
2600
|
-
out[i] = Math.floor(pix[i] * 257 * 65535 / a16) >> 8;
|
|
2601
|
-
out[i + 1] = Math.floor(pix[i + 1] * 257 * 65535 / a16) >> 8;
|
|
2602
|
-
out[i + 2] = Math.floor(pix[i + 2] * 257 * 65535 / a16) >> 8;
|
|
2603
|
-
}
|
|
2604
|
-
out[i + 3] = a;
|
|
2607
|
+
straightPixel(pix, i, out, i);
|
|
2605
2608
|
}
|
|
2606
2609
|
return out;
|
|
2607
2610
|
}
|
|
@@ -2683,6 +2686,55 @@ function u24(dv, offset, v) {
|
|
|
2683
2686
|
dv.setUint8(offset + 1, v >> 8 & 255);
|
|
2684
2687
|
dv.setUint8(offset + 2, v >> 16 & 255);
|
|
2685
2688
|
}
|
|
2689
|
+
function sameCanvas(a, b) {
|
|
2690
|
+
if (a === b) return true;
|
|
2691
|
+
const ap = a.pix;
|
|
2692
|
+
const bp = b.pix;
|
|
2693
|
+
for (let i = 0; i < ap.length; i++) {
|
|
2694
|
+
if (ap[i] !== bp[i]) return false;
|
|
2695
|
+
}
|
|
2696
|
+
return true;
|
|
2697
|
+
}
|
|
2698
|
+
function deltaFrame(prev, cur, durationMs) {
|
|
2699
|
+
const { width, height } = cur;
|
|
2700
|
+
const cp = cur.pix;
|
|
2701
|
+
const pp = prev.pix;
|
|
2702
|
+
let minX = width;
|
|
2703
|
+
let minY = height;
|
|
2704
|
+
let maxX = -1;
|
|
2705
|
+
let maxY = -1;
|
|
2706
|
+
let allOpaque = true;
|
|
2707
|
+
for (let y = 0; y < height; y++) {
|
|
2708
|
+
for (let x = 0; x < width; x++) {
|
|
2709
|
+
const i = (y * width + x) * 4;
|
|
2710
|
+
if (cp[i] === pp[i] && cp[i + 1] === pp[i + 1] && cp[i + 2] === pp[i + 2] && cp[i + 3] === pp[i + 3]) {
|
|
2711
|
+
continue;
|
|
2712
|
+
}
|
|
2713
|
+
if (x < minX) minX = x;
|
|
2714
|
+
if (x > maxX) maxX = x;
|
|
2715
|
+
if (y < minY) minY = y;
|
|
2716
|
+
if (y > maxY) maxY = y;
|
|
2717
|
+
if (cp[i + 3] !== 255) allOpaque = false;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (maxX < 0) return null;
|
|
2721
|
+
const x0 = minX & ~1;
|
|
2722
|
+
const y0 = minY & ~1;
|
|
2723
|
+
const w = maxX - x0 + 1;
|
|
2724
|
+
const h = maxY - y0 + 1;
|
|
2725
|
+
const rgba = new Uint8Array(w * h * 4);
|
|
2726
|
+
for (let y = 0; y < h; y++) {
|
|
2727
|
+
for (let x = 0; x < w; x++) {
|
|
2728
|
+
const i = ((y0 + y) * width + (x0 + x)) * 4;
|
|
2729
|
+
const o = (y * w + x) * 4;
|
|
2730
|
+
if (allOpaque && cp[i] === pp[i] && cp[i + 1] === pp[i + 1] && cp[i + 2] === pp[i + 2] && cp[i + 3] === pp[i + 3]) {
|
|
2731
|
+
continue;
|
|
2732
|
+
}
|
|
2733
|
+
straightPixel(cp, i, rgba, o);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return { x: x0, y: y0, w, h, rgba, blend: allOpaque, durationMs };
|
|
2737
|
+
}
|
|
2686
2738
|
async function encodeWebp(frames, opts) {
|
|
2687
2739
|
if (frames.length === 0) {
|
|
2688
2740
|
return new Uint8Array(0);
|
|
@@ -2702,10 +2754,49 @@ async function encodeWebp(frames, opts) {
|
|
|
2702
2754
|
durations.push(frameDuration);
|
|
2703
2755
|
if (maxDuration > 0 && remaining <= 0) break;
|
|
2704
2756
|
}
|
|
2757
|
+
const specs = [
|
|
2758
|
+
{
|
|
2759
|
+
x: 0,
|
|
2760
|
+
y: 0,
|
|
2761
|
+
w: width,
|
|
2762
|
+
h: height,
|
|
2763
|
+
rgba: toStraightRGBA(frames[0]),
|
|
2764
|
+
blend: false,
|
|
2765
|
+
durationMs: durations[0]
|
|
2766
|
+
}
|
|
2767
|
+
];
|
|
2768
|
+
const uniqueFrames = [frames[0]];
|
|
2769
|
+
for (let i = 1; i < durations.length; i++) {
|
|
2770
|
+
const prev = uniqueFrames[uniqueFrames.length - 1];
|
|
2771
|
+
const spec = sameCanvas(prev, frames[i]) ? null : deltaFrame(prev, frames[i], durations[i]);
|
|
2772
|
+
if (spec === null) {
|
|
2773
|
+
specs[specs.length - 1].durationMs += durations[i];
|
|
2774
|
+
} else {
|
|
2775
|
+
specs.push(spec);
|
|
2776
|
+
uniqueFrames.push(frames[i]);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2705
2779
|
const encoded = [];
|
|
2706
2780
|
let anyAlpha = false;
|
|
2707
|
-
for (let i = 0; i <
|
|
2708
|
-
const
|
|
2781
|
+
for (let i = 0; i < specs.length; i++) {
|
|
2782
|
+
const spec = specs[i];
|
|
2783
|
+
let file = await opts.encoder(spec.rgba, spec.w, spec.h);
|
|
2784
|
+
if (spec.blend || spec.w !== width || spec.h !== height) {
|
|
2785
|
+
const key = await opts.encoder(toStraightRGBA(uniqueFrames[i]), width, height);
|
|
2786
|
+
if (key.length < file.length) {
|
|
2787
|
+
file = key;
|
|
2788
|
+
specs[i] = {
|
|
2789
|
+
x: 0,
|
|
2790
|
+
y: 0,
|
|
2791
|
+
w: width,
|
|
2792
|
+
h: height,
|
|
2793
|
+
rgba: spec.rgba,
|
|
2794
|
+
// unused past this point
|
|
2795
|
+
blend: false,
|
|
2796
|
+
durationMs: spec.durationMs
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
}
|
|
2709
2800
|
const chunks = frameImageChunks(file);
|
|
2710
2801
|
if (frameHasAlpha(chunks)) anyAlpha = true;
|
|
2711
2802
|
encoded.push(chunks);
|
|
@@ -2728,19 +2819,20 @@ async function encodeWebp(frames, opts) {
|
|
|
2728
2819
|
dv.setUint16(4, opts.loopCount ?? 0, true);
|
|
2729
2820
|
w.chunk("ANIM", data);
|
|
2730
2821
|
}
|
|
2731
|
-
for (let i = 0; i <
|
|
2822
|
+
for (let i = 0; i < specs.length; i++) {
|
|
2823
|
+
const spec = specs[i];
|
|
2732
2824
|
let inner = 16;
|
|
2733
2825
|
for (const c of encoded[i]) {
|
|
2734
2826
|
inner += 8 + c.data.length + (c.data.length & 1);
|
|
2735
2827
|
}
|
|
2736
2828
|
const data = new Uint8Array(inner);
|
|
2737
2829
|
const dv = new DataView(data.buffer);
|
|
2738
|
-
u24(dv, 0,
|
|
2739
|
-
u24(dv, 3,
|
|
2740
|
-
u24(dv, 6,
|
|
2741
|
-
u24(dv, 9,
|
|
2742
|
-
u24(dv, 12,
|
|
2743
|
-
data[15] = 2;
|
|
2830
|
+
u24(dv, 0, spec.x / 2);
|
|
2831
|
+
u24(dv, 3, spec.y / 2);
|
|
2832
|
+
u24(dv, 6, spec.w - 1);
|
|
2833
|
+
u24(dv, 9, spec.h - 1);
|
|
2834
|
+
u24(dv, 12, spec.durationMs);
|
|
2835
|
+
data[15] = spec.blend ? 0 : 2;
|
|
2744
2836
|
let o = 16;
|
|
2745
2837
|
for (const c of encoded[i]) {
|
|
2746
2838
|
for (let k = 0; k < 4; k++) data[o + k] = c.fourcc.charCodeAt(k);
|
|
@@ -47,12 +47,18 @@ declare class Image implements Widget {
|
|
|
47
47
|
* (WASM libwebp — see webp-jsquash.ts); this module owns the deterministic
|
|
48
48
|
* parts: frame-duration policy (delay + maxDuration cap, ported from
|
|
49
49
|
* EncodeWebP) and the animation container mux (VP8X/ANIM/ANMF assembly,
|
|
50
|
-
* the moral equivalent of libwebp's
|
|
51
|
-
*
|
|
50
|
+
* the moral equivalent of libwebp's WebPAnimEncoder with frame diffing:
|
|
51
|
+
* frame 0 is a full keyframe; each later frame is cropped to the changed
|
|
52
|
+
* bounding rect vs the previous frame, with unchanged pixels inside the
|
|
53
|
+
* rect made transparent and alpha-blended over the canvas. Identical
|
|
54
|
+
* consecutive frames are merged into one longer-duration frame. Without
|
|
55
|
+
* this, a marquee over static album art re-encodes the art into every one
|
|
56
|
+
* of ~400 frames (spotify at 64x128: 868KB → ~100KB with deltas).
|
|
52
57
|
*
|
|
53
|
-
* Determinism: container assembly
|
|
54
|
-
* deterministic for a pinned libwebp build + settings
|
|
55
|
-
* the encoder version into the cache key at the
|
|
58
|
+
* Determinism: container assembly and frame diffing are pure; frame
|
|
59
|
+
* bitstreams are deterministic for a pinned libwebp build + settings
|
|
60
|
+
* (handoff §8 — fold the encoder version into the cache key at the
|
|
61
|
+
* render-plane layer).
|
|
56
62
|
*/
|
|
57
63
|
|
|
58
64
|
/** Encodes one straight-RGBA frame to a complete (still) WebP file. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@koiosdigital/matrx-render",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/index.js",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"@jsquash/webp": "^1.5.0",
|
|
13
13
|
"fflate": "^0.8.3",
|
|
14
14
|
"jpeg-js": "^0.4.4",
|
|
15
|
-
"@koiosdigital/matrx-sdk": "0.1.
|
|
15
|
+
"@koiosdigital/matrx-sdk": "0.1.8"
|
|
16
16
|
},
|
|
17
17
|
"devDependencies": {
|
|
18
18
|
"@types/node": "^26.1.0",
|