code wiki / (root) / nx_webp.nx

nx_webp.nx source

↩ module page · 334 lines · 13290 B

1// nx_webp.nx -- WebP container + the VP8L DecodeImageStream orchestrator. 2// 3// This is the piece that ties nx_webp_vp8l's parts into a decoder: it reads 4// the transform stack, the colour cache, the meta-Huffman image and the N 5// Huffman groups, runs the pixel stream, then UNDOES the transforms in 6// reverse. Sub-images (transform data, the meta image, the palette) are 7// themselves image streams, so this function is recursive with level0=0 -- 8// only the top level carries transforms and a meta image. 9// 10// genealogy_id: webp_lossless_bitstream_spec_2012 11// lineage_id: nx_webp_v1 12// license_tier: ORIGINAL 13 14import "nx_syscalls.nx" 15import "nx_bitstream.nx" 16import "nx_huffman.nx" 17import "nx_webp_huff.nx" 18import "nx_webp_vp8l.nx" 19import "nx_vp8_kf.nx" // lossy VP8 keyframe decoder (C3, 2026-08-05) 20 21const NX_WEBP_MAX_XF: i64 = 4 22 23// palette packing: how many pixels share a byte, as a shift 24func wb_palette_bits(n_colors: i64) -> i64 { 25 if n_colors <= 2 { return 3 } 26 if n_colors <= 4 { return 2 } 27 if n_colors <= 16 { return 1 } 28 return 0 29} 30 31// ===== Huffman group reading ====================================== 32// 33// Returns a flat array of n_groups*5 tree pointers (widened to i64), or 0. 34// The five trees per group are, in stream order: green+length+cache, red, 35// blue, alpha, distance. Only the green tree's alphabet grows with the 36// colour cache. 37 38func wb_read_groups(bs: *NxBitStream, n_groups: i64, cache_bits: i64) -> *i64 { 39 let total: i64 = n_groups * NX_VP8L_GROUP_SIZE 40 let groups: *i64 = sys_mmap(total * 8 + 64) as *i64 41 var cache_size: i64 = 0 42 if cache_bits > 0 { cache_size = 1 << cache_bits } 43 let green_n: i64 = NX_VP8L_NUM_LIT + NX_VP8L_NUM_LEN + cache_size 44 45 var i: i64 = 0 46 var ok: i64 = 1 47 while i < total { 48 var alphabet: i64 = NX_VP8L_NUM_LIT 49 let slot: i64 = i % NX_VP8L_GROUP_SIZE 50 if slot == 0 { alphabet = green_n } 51 if slot == 4 { alphabet = NX_VP8L_NUM_DIST } 52 let t: *NxWlTree = wl_read_huffman_code(bs, alphabet) 53 if t == (0 as *NxWlTree) { ok = 0; i = total } else { 54 groups[i] = t as i64 55 i = i + 1 56 } 57 } 58 if ok == 0 { return 0 as *i64 } 59 return groups 60} 61 62// ===== the image stream =========================================== 63// 64// out must hold width*height i64 ARGB entries. Returns 1 on success. 65// A malformed stream returns 0 rather than a partially-filled buffer -- 66// a wrong answer is worse than an absent one. 67 68func wb_decode_stream(bs: *NxBitStream, width: i64, height: i64, level0: i64, 69 plane: *i64, out: *i64) -> i64 { 70 let xf_type: *i64 = sys_mmap(NX_WEBP_MAX_XF * 8 + 64) as *i64 71 let xf_bits: *i64 = sys_mmap(NX_WEBP_MAX_XF * 8 + 64) as *i64 72 let xf_data: *i64 = sys_mmap(NX_WEBP_MAX_XF * 8 + 64) as *i64 73 let xf_ncol: *i64 = sys_mmap(NX_WEBP_MAX_XF * 8 + 64) as *i64 74 let seen: *i64 = sys_mmap(NX_WEBP_MAX_XF * 8 + 64) as *i64 75 var i: i64 = 0 76 while i < NX_WEBP_MAX_XF { seen[i] = 0; xf_data[i] = 0; xf_ncol[i] = 0; i = i + 1 } 77 78 var n_xf: i64 = 0 79 var cur_w: i64 = width 80 var ok: i64 = 1 81 82 // ---- transform stack (top level only) ---- 83 if level0 == 1 { 84 var more: i64 = nx_bitstream_read_lsb(bs, 1) 85 while more == 1 { 86 if n_xf >= NX_WEBP_MAX_XF { ok = 0; more = 0 } else { 87 let t: i64 = nx_bitstream_read_lsb(bs, 2) 88 if seen[t] == 1 { ok = 0; more = 0 } else { 89 seen[t] = 1 90 xf_type[n_xf] = t 91 if t == NX_VP8L_XF_PALETTE { 92 let n_colors: i64 = nx_bitstream_read_lsb(bs, 8) + 1 93 let pal: *i64 = sys_mmap(n_colors * 8 + 64) as *i64 94 if wb_decode_stream(bs, n_colors, 1, 0, plane, pal) == 0 { ok = 0 } else { 95 // the palette is delta-coded along its single row 96 var c: i64 = 1 97 while c < n_colors { 98 let p: i64 = pal[c] 99 let q: i64 = pal[c - 1] 100 let a: i64 = (vl_chan(p, 24) + vl_chan(q, 24)) & 255 101 let r: i64 = (vl_chan(p, 16) + vl_chan(q, 16)) & 255 102 let g: i64 = (vl_chan(p, 8) + vl_chan(q, 8)) & 255 103 let b: i64 = (vl_chan(p, 0) + vl_chan(q, 0)) & 255 104 pal[c] = vl_argb(a, r, g, b) 105 c = c + 1 106 } 107 xf_data[n_xf] = pal as i64 108 xf_ncol[n_xf] = n_colors 109 let pb: i64 = wb_palette_bits(n_colors) 110 xf_bits[n_xf] = pb 111 cur_w = vl_subsample_size(cur_w, pb) 112 } 113 } else { 114 if t == NX_VP8L_XF_SUBGREEN { 115 xf_bits[n_xf] = 0 116 } else { 117 // predictor and colour transforms both carry a sub-image 118 let bits: i64 = nx_bitstream_read_lsb(bs, 3) + 2 119 let bw: i64 = vl_subsample_size(cur_w, bits) 120 let bh: i64 = vl_subsample_size(height, bits) 121 let sub: *i64 = sys_mmap(bw * bh * 8 + 64) as *i64 122 if wb_decode_stream(bs, bw, bh, 0, plane, sub) == 0 { ok = 0 } else { 123 xf_bits[n_xf] = bits 124 xf_data[n_xf] = sub as i64 125 } 126 } } 127 if ok == 0 { more = 0 } else { 128 n_xf = n_xf + 1 129 more = nx_bitstream_read_lsb(bs, 1) 130 } 131 } 132 } 133 } 134 } 135 if ok == 0 { return 0 } 136 137 // ---- colour cache ---- 138 var cache_bits: i64 = 0 139 if nx_bitstream_read_lsb(bs, 1) == 1 { 140 cache_bits = nx_bitstream_read_lsb(bs, 4) 141 if cache_bits < 1 { return 0 } 142 if cache_bits > 11 { return 0 } 143 } 144 145 // ---- meta-Huffman image (top level only) ---- 146 var meta: *i64 = 0 as *i64 147 var meta_bits: i64 = 0 148 var meta_w: i64 = 0 149 var n_groups: i64 = 1 150 if level0 == 1 { 151 if nx_bitstream_read_lsb(bs, 1) == 1 { 152 meta_bits = nx_bitstream_read_lsb(bs, 3) + 2 153 meta_w = vl_subsample_size(cur_w, meta_bits) 154 let mh: i64 = vl_subsample_size(height, meta_bits) 155 let raw: *i64 = sys_mmap(meta_w * mh * 8 + 64) as *i64 156 if wb_decode_stream(bs, meta_w, mh, 0, plane, raw) == 0 { return 0 } 157 // the group index is packed into the red and green bytes 158 let ncell: i64 = meta_w * mh 159 var k: i64 = 0 160 var maxg: i64 = 0 161 while k < ncell { 162 let g: i64 = (vl_chan(raw[k], 16) << 8) | vl_chan(raw[k], 8) 163 raw[k] = g 164 if g > maxg { maxg = g } 165 k = k + 1 166 } 167 meta = raw 168 n_groups = maxg + 1 169 } 170 } 171 172 // ---- Huffman groups + the pixel stream ---- 173 let groups: *i64 = wb_read_groups(bs, n_groups, cache_bits) 174 if groups == (0 as *i64) { return 0 } 175 176 var pixels: *i64 = out 177 if cur_w != width { 178 // palette-packed: decode narrow, expand later 179 pixels = sys_mmap(cur_w * height * 8 + 64) as *i64 180 } 181 if vl_decode_pixels(bs, pixels, cur_w, height, groups, meta, meta_bits, 182 meta_w, cache_bits, plane) == 0 { return 0 } 183 184 // ---- inverse transforms, in REVERSE order ---- 185 var x: i64 = n_xf - 1 186 while x >= 0 { 187 let t: i64 = xf_type[x] 188 if t == NX_VP8L_XF_SUBGREEN { 189 vl_inv_subtract_green(out, width * height) 190 } else { 191 if t == NX_VP8L_XF_PREDICT { 192 vl_inv_predict(out, width, height, xf_bits[x], xf_data[x] as *i64) 193 } else { 194 if t == NX_VP8L_XF_COLOR { 195 vl_inv_color(out, width, height, xf_bits[x], xf_data[x] as *i64) 196 } else { 197 // palette: expand packed indices back to full width 198 let pal: *i64 = xf_data[x] as *i64 199 let pb: i64 = xf_bits[x] 200 let ncol: i64 = xf_ncol[x] 201 let per: i64 = 1 << pb 202 let mask: i64 = (1 << (8 >> pb)) - 1 203 var y: i64 = 0 204 while y < height { 205 var px: i64 = 0 206 while px < width { 207 let packed: i64 = pixels[y * cur_w + (px >> pb)] 208 let shift: i64 = (px & (per - 1)) * (8 >> pb) 209 var idx: i64 = (vl_chan(packed, 8) >> shift) & mask 210 if idx >= ncol { idx = 0 } 211 out[y * width + px] = pal[idx] 212 px = px + 1 213 } 214 y = y + 1 215 } 216 } } } 217 x = x - 1 218 } 219 return 1 220} 221 222// ===== RIFF container ============================================= 223// 224// Accepts "RIFF"<size>"WEBP" then walks chunks to VP8L OR 'VP8 ' (lossy 225// keyframe, decoded by nx_vp8_kf since 2026-08-05). A simple-format 226// lossless file is RIFF/WEBP/VP8L; an extended file is RIFF/WEBP/VP8X/.../VP8L. 227// Returns a width*height ARGB buffer, or 0. out_wh receives w,h. 228 229func wb_u32le(b: *u8, off: i64) -> i64 { 230 return ((b[off] as i64) & 255) | (((b[off+1] as i64) & 255) << 8) 231 | (((b[off+2] as i64) & 255) << 16) | (((b[off+3] as i64) & 255) << 24) 232} 233 234func wb_fourcc_is(b: *u8, off: i64, a: i64, c: i64, d: i64, e: i64) -> i64 { 235 if (b[off] as i64 & 255) != a { return 0 } 236 if (b[off+1] as i64 & 255) != c { return 0 } 237 if (b[off+2] as i64 & 255) != d { return 0 } 238 if (b[off+3] as i64 & 255) != e { return 0 } 239 return 1 240} 241 242func webp_decode(raw: *u8, n: i64, out_wh: *i64) -> *i64 { 243 if n < 20 { return 0 as *i64 } 244 if wb_fourcc_is(raw, 0, 0x52, 0x49, 0x46, 0x46) == 0 { return 0 as *i64 } 245 if wb_fourcc_is(raw, 8, 0x57, 0x45, 0x42, 0x50) == 0 { return 0 as *i64 } 246 247 // walk chunks from offset 12 to the VP8L payload 248 var pos: i64 = 12 249 var found: i64 = 0 250 var isvp8: i64 = 0 251 var payload: i64 = 0 252 var paylen: i64 = 0 253 var go: i64 = 1 254 while go == 1 { 255 if pos + 8 > n { go = 0 } else { 256 let clen: i64 = wb_u32le(raw, pos + 4) 257 if wb_fourcc_is(raw, pos, 0x56, 0x50, 0x38, 0x4c) == 1 { 258 payload = pos + 8 259 paylen = clen 260 found = 1 261 go = 0 262 } else { 263 if wb_fourcc_is(raw, pos, 0x56, 0x50, 0x38, 0x20) == 1 { 264 // 'VP8 ' -- lossy keyframe (simple or inside VP8X) 265 payload = pos + 8 266 paylen = clen 267 found = 1 268 isvp8 = 1 269 go = 0 270 } else { 271 var adv: i64 = clen 272 if (adv & 1) == 1 { adv = adv + 1 } 273 pos = pos + 8 + adv 274 } } 275 } 276 } 277 if found == 0 { return 0 as *i64 } 278 if payload + paylen > n { paylen = n - payload } 279 if paylen <= 5 { return 0 as *i64 } 280 281 // ---- lossy VP8 keyframe: sovereign C3 decoder + BT.601 YUV->ARGB ---- 282 if isvp8 == 1 { 283 let vb: *i64 = sys_mmap(64) as *i64 284 if nx_vp8_kf_decode(raw + payload, paylen, vb) == 0 { return 0 as *i64 } 285 let py: *u8 = vb[NX_VP8KF_OUT_Y] as *u8 286 let pu: *u8 = vb[NX_VP8KF_OUT_U] as *u8 287 let pv: *u8 = vb[NX_VP8KF_OUT_V] as *u8 288 let vw: i64 = vb[NX_VP8KF_OUT_W] 289 let vh: i64 = vb[NX_VP8KF_OUT_H] 290 let vys: i64 = vb[NX_VP8KF_OUT_YS] 291 let vus: i64 = vb[NX_VP8KF_OUT_UVS] 292 let vargb: *i64 = sys_mmap(vw * vh * 8 + 64) as *i64 293 var vy: i64 = 0 294 while vy < vh { 295 var vx: i64 = 0 296 while vx < vw { 297 let yv: i64 = ((py[vy*vys+vx] as i64) & 255) - 16 298 let uv: i64 = ((pu[(vy>>1)*vus+(vx>>1)] as i64) & 255) - 128 299 let vv: i64 = ((pv[(vy>>1)*vus+(vx>>1)] as i64) & 255) - 128 300 var tr: i64 = 298*yv + 409*vv + 128 301 var tg: i64 = 298*yv - 100*uv - 208*vv + 128 302 var tb: i64 = 298*yv + 516*uv + 128 303 if tr < 0 { tr = 0 } else { tr = tr >> 8 } 304 if tg < 0 { tg = 0 } else { tg = tg >> 8 } 305 if tb < 0 { tb = 0 } else { tb = tb >> 8 } 306 if tr > 255 { tr = 255 } 307 if tg > 255 { tg = 255 } 308 if tb > 255 { tb = 255 } 309 vargb[vy*vw+vx] = (255 << 24) | (tr << 16) | (tg << 8) | tb 310 vx = vx + 1 311 } 312 vy = vy + 1 313 } 314 out_wh[0] = vw 315 out_wh[1] = vh 316 return vargb 317 } 318 319 let bs: *NxBitStream = nx_bitstream_alloc(raw + payload, paylen) 320 let hdr: *i64 = sys_mmap(64) as *i64 321 if vp8l_read_header(bs, hdr) == 0 { return 0 as *i64 } 322 let w: i64 = hdr[0] 323 let h: i64 = hdr[1] 324 325 let plane: *i64 = sys_mmap(NX_VP8L_PLANE_CODES * 8 + 64) as *i64 326 vl_plane_table(plane) 327 328 let argb: *i64 = sys_mmap(w * h * 8 + 64) as *i64 329 if wb_decode_stream(bs, w, h, 1, plane, argb) == 0 { return 0 as *i64 } 330 331 out_wh[0] = w 332 out_wh[1] = h 333 return argb 334}