nx_webp_decode.nx source
↩ module page · 101 lines · 5303 B
1// nx_webp_decode.nx -- SOVEREIGN WebP decoder, built from the first byte up (operator 2026-07-29:
2// "build all the missing codecs... anything WebKit does and beyond").
3// WHY IT MATTERS: Wikimedia CONTENT-NEGOTIATES and serves WebP under .png URLs, so with PNG/JPEG/GIF/
4// BMP only, EVERY image on a wikipedia article decoded to nothing (measured: nsrc=11 spawn=6 dec=0).
5// WebP is not optional in 2026 — it is the default web image format.
6//
7// LAYER 1 (this file, LANDED): RIFF/WEBP container + VP8L header + chunk routing. Exposes exact
8// dimensions and the bitstream span, so the pipeline can size boxes and the KAT can prove the parse.
9// LAYER 2 (next): VP8L LOSSLESS pixel decode -- LSB-first bit reader, canonical prefix (Huffman)
10// code build, meta-prefix groups, colour cache, and the 4 transforms (predictor / colour /
11// subtract-green / colour-indexing). That is what these thumbnails use.
12// LAYER 3: VP8 LOSSY (boolean/arith decoder, DCT, prediction, loop filter) then VP8X/ALPH + animation.
13// ★NOTHING HERE FABRICATES PIXELS: until layer 2 lands, decode_rgb reports NOT-YET (0), never a
14// plausible-looking buffer -- a codec that guesses is worse than one that declines.
15import "nx_syscalls.nx"
16import "nx_webp_vp8l.nx"
17
18const NX_WEBP_OK: i64 = 0
19const NX_WEBP_NOT_WEBP: i64 = 1
20const NX_WEBP_TRUNCATED: i64 = 2
21const NX_WEBP_UNSUP: i64 = 3 // a WebP variant whose PIXEL decode is not built yet
22
23// out[0]=width out[1]=height out[2]=chunk-kind (0=VP8L lossless,1=VP8 lossy,2=VP8X extended)
24// out[3]=bitstream byte offset out[4]=bitstream length out[5]=alpha flag (VP8L)
25func nx_webp_parse(buf: *u8, len: i64, out: *i64) -> i64 {
26 var k: i64 = 0
27 while k < 8 { out[k] = 0; k = k + 1 }
28 if len < 20 { return NX_WEBP_TRUNCATED }
29 if (buf[0]&0xff) != 82 { return NX_WEBP_NOT_WEBP } // R
30 if (buf[1]&0xff) != 73 { return NX_WEBP_NOT_WEBP } // I
31 if (buf[2]&0xff) != 70 { return NX_WEBP_NOT_WEBP } // F
32 if (buf[3]&0xff) != 70 { return NX_WEBP_NOT_WEBP } // F
33 if (buf[8]&0xff) != 87 { return NX_WEBP_NOT_WEBP } // W
34 if (buf[9]&0xff) != 69 { return NX_WEBP_NOT_WEBP } // E
35 if (buf[10]&0xff) != 66 { return NX_WEBP_NOT_WEBP } // B
36 if (buf[11]&0xff) != 80 { return NX_WEBP_NOT_WEBP } // P
37 // fourth FourCC = the chunk: "VP8L" (lossless) / "VP8 " (lossy) / "VP8X" (extended)
38 let c0: i64 = buf[12]&0xff
39 let c1: i64 = buf[13]&0xff
40 let c2: i64 = buf[14]&0xff
41 let c3: i64 = buf[15]&0xff
42 let csz: i64 = (buf[16]&0xff) + ((buf[17]&0xff)*256) + ((buf[18]&0xff)*65536) + ((buf[19]&0xff)*16777216)
43 if c0 != 86 { return NX_WEBP_NOT_WEBP } // V
44 if c1 != 80 { return NX_WEBP_NOT_WEBP } // P
45 if c2 != 56 { return NX_WEBP_NOT_WEBP } // 8
46 if c3 == 76 { // 'L' -- VP8L LOSSLESS
47 if len < 26 { return NX_WEBP_TRUNCATED }
48 if (buf[20]&0xff) != 47 { return NX_WEBP_NOT_WEBP } // VP8L signature 0x2F
49 // header is LSB-first bit-packed: 14 bits (w-1), 14 bits (h-1), 1 bit alpha, 3 bits version
50 let b1: i64 = buf[21]&0xff
51 let b2: i64 = buf[22]&0xff
52 let b3: i64 = buf[23]&0xff
53 let b4: i64 = buf[24]&0xff
54 let bits: i64 = b1 + b2*256 + b3*65536 + b4*16777216
55 let w: i64 = (bits & 16383) + 1
56 let h: i64 = ((bits / 16384) & 16383) + 1
57 let alpha: i64 = (bits / 268435456) & 1
58 out[0] = w
59 out[1] = h
60 out[2] = 0
61 out[3] = 21
62 out[4] = csz - 1
63 out[5] = alpha
64 return NX_WEBP_OK
65 }
66 if c3 == 32 { // ' ' -- VP8 LOSSY
67 if len < 30 { return NX_WEBP_TRUNCATED }
68 // keyframe header: 3-byte frame tag, then start code 9D 01 2A, then 14-bit w / 14-bit h
69 if (buf[23]&0xff) != 157 { return NX_WEBP_UNSUP }
70 if (buf[24]&0xff) != 1 { return NX_WEBP_UNSUP }
71 if (buf[25]&0xff) != 42 { return NX_WEBP_UNSUP }
72 out[0] = ((buf[26]&0xff) + ((buf[27]&0xff)*256)) & 16383
73 out[1] = ((buf[28]&0xff) + ((buf[29]&0xff)*256)) & 16383
74 out[2] = 1
75 out[3] = 20
76 out[4] = csz
77 return NX_WEBP_OK
78 }
79 if c3 == 88 { // 'X' -- VP8X extended (canvas size only)
80 if len < 30 { return NX_WEBP_TRUNCATED }
81 out[0] = (buf[24]&0xff) + ((buf[25]&0xff)*256) + ((buf[26]&0xff)*65536) + 1
82 out[1] = (buf[27]&0xff) + ((buf[28]&0xff)*256) + ((buf[29]&0xff)*65536) + 1
83 out[2] = 2
84 out[3] = 20
85 out[4] = csz
86 return NX_WEBP_OK
87 }
88 return NX_WEBP_NOT_WEBP
89}
90
91// Pixel decode. Layer 2 lands VP8L here. Returns 0 (and sets no pixels) until then -- DECLINING is
92// correct; fabricating is not. The consumer already treats 0 as "no image" safely.
93func nx_webp_decode_rgb(buf: *u8, len: i64, wh: *i64) -> *u8 {
94 let info: *i64 = sys_mmap(64) as *i64
95 if nx_webp_parse(buf, len, info) != NX_WEBP_OK { return 0 as *u8 }
96 wh[0] = info[0]
97 wh[1] = info[1]
98 // VP8L LOSSLESS -> real pixels (layer 2). Other variants still DECLINE rather than fabricate.
99 if info[2] == 0 { return nx_vp8l_decode(buf, len, 20, info[0], info[1], wh) }
100 return 0 as *u8
101}