code wiki / (root) / nx_webp_huff.nx

nx_webp_huff.nx source

↩ module page · 241 lines · 9114 B

1// nx_webp_huff.nx -- VP8L Huffman code-group reader (WebP lossless). 2// 3// VP8L transmits every Huffman tree in one of two forms: 4// SIMPLE -- 1 or 2 symbols written literally, no code-length layer 5// NORMAL -- a 19-symbol code-length alphabet (3 bits each, sent in a 6// fixed permuted order) which then codes the real symbol 7// lengths, with run-length symbols 16/17/18 8// 9// Both forms resolve to a canonical Huffman table, so both compose onto 10// the existing nx_huffman primitive: VP8L reads bits LSB-first out of the 11// byte stream and sends each code MSB-of-code first, which is exactly what 12// nx_huffman_decode_lsb implements (contract re-verified against 13// nx_bitstream.nx and nx_huffman.nx on 2026-07-31, not assumed from docs). 14// 15// THE ZERO-BIT CASE. A tree carrying exactly one symbol must decode that 16// symbol while consuming NO bits. nx_huffman_decode_lsb always consumes at 17// least one bit, so a bare canonical table is wrong here and would desync 18// the whole stream. NxWlTree carries the single-symbol case explicitly 19// rather than leaving it to the table -- this is the defect that silently 20// corrupts every VP8L image whose alpha or red channel is constant, which 21// is most synthetic images. 22// 23// genealogy_id: webp_lossless_bitstream_spec_2012 24// lineage_id: nx_webp_huff_v1 25// license_tier: ORIGINAL 26 27import "nx_syscalls.nx" 28import "nx_bitstream.nx" 29import "nx_huffman.nx" 30 31// ===== alphabet sizes (spec-fixed, not tunable) =================== 32 33const NX_WL_CODE_LENGTH_CODES: i64 = 19 34const NX_WL_CODE_LEN_LITERALS: i64 = 16 35const NX_WL_DEFAULT_CODE_LEN: i64 = 8 36const NX_WL_TREE_BYTES: i64 = 24 37 38// ===== tree handle ================================================ 39// 40// Fields are i64 by construction -- nx_cc lays struct fields out at 41// i64 stride, so declaring them nx_int mis-offsets every read after 42// the first (see reference-nxcc-nxint-struct-field-offset-bug). 43// `table` holds a *NxHuffmanTable widened to i64. 44 45struct NxWlTree { 46 table: i64, 47 trivial: i64, 48 symbol: i64, 49} 50 51// ===== code-length code order (spec table) ======================== 52// 53// The 19 code-length symbols are transmitted in this permuted order so 54// that a truncated list still carries the most common lengths. 55 56func wl_code_order(out: *i64) -> i64 { 57 out[0] = 17; out[1] = 18; out[2] = 0; out[3] = 1 58 out[4] = 2; out[5] = 3; out[6] = 4; out[7] = 5 59 out[8] = 16; out[9] = 6; out[10] = 7; out[11] = 8 60 out[12] = 9; out[13] = 10; out[14] = 11; out[15] = 12 61 out[16] = 13; out[17] = 14; out[18] = 15 62 return NX_WL_CODE_LENGTH_CODES 63} 64 65// repeat symbols 16/17/18: extra bits read, and the count they add to 66func wl_repeat_extra_bits(slot: i64) -> i64 { 67 if slot == 0 { return 2 } 68 if slot == 1 { return 3 } 69 return 7 70} 71 72func wl_repeat_offset(slot: i64) -> i64 { 73 if slot == 0 { return 3 } 74 if slot == 1 { return 3 } 75 return 11 76} 77 78// ===== tree construction from a length vector ===================== 79// 80// Collapses to the zero-bit form when exactly one symbol is present. 81// Returns 0 when the length vector codes nothing at all -- a caller 82// that needs a mandatory tree must treat 0 as a malformed stream 83// rather than as an empty-but-valid one. 84 85func wl_tree_from_lengths(lengths: *i64, n_symbols: i64) -> *NxWlTree { 86 var nonzero: i64 = 0 87 var last_sym: i64 = 0 88 var i: i64 = 0 89 while i < n_symbols { 90 if lengths[i] != 0 { nonzero = nonzero + 1; last_sym = i } 91 i = i + 1 92 } 93 if nonzero == 0 { return 0 as *NxWlTree } 94 95 let t: *NxWlTree = sys_mmap(NX_WL_TREE_BYTES) as *NxWlTree 96 if nonzero == 1 { 97 t.table = 0 98 t.trivial = 1 99 t.symbol = last_sym 100 return t 101 } 102 let ht: *NxHuffmanTable = nx_huffman_build(lengths as *nx_int, n_symbols as nx_int) 103 if ht == (0 as *NxHuffmanTable) { return 0 as *NxWlTree } 104 t.table = ht as i64 105 t.trivial = 0 106 t.symbol = 0 107 return t 108} 109 110// ===== decode one symbol ========================================== 111// 112// Returns -1 on a malformed code or an exhausted bitstream. A trivial 113// tree consumes no bits, which is the whole point of carrying it. 114 115func wl_decode_symbol(t: *NxWlTree, bs: *NxBitStream) -> i64 { 116 if t == (0 as *NxWlTree) { return 0 - 1 } 117 if t.trivial == 1 { return t.symbol } 118 let ht: *NxHuffmanTable = t.table as *NxHuffmanTable 119 return nx_huffman_decode_lsb(ht, bs) 120} 121 122// ===== SIMPLE form ================================================ 123 124func wl_read_simple(bs: *NxBitStream, lengths: *i64, n_symbols: i64) -> i64 { 125 let num_symbols: i64 = nx_bitstream_read_lsb(bs, 1) + 1 126 let first_len_code: i64 = nx_bitstream_read_lsb(bs, 1) 127 var s0: i64 = 0 128 if first_len_code == 1 { 129 s0 = nx_bitstream_read_lsb(bs, 8) 130 } else { 131 s0 = nx_bitstream_read_lsb(bs, 1) 132 } 133 if s0 < 0 { return 0 } 134 if s0 >= n_symbols { return 0 } 135 lengths[s0] = 1 136 if num_symbols == 2 { 137 let s1: i64 = nx_bitstream_read_lsb(bs, 8) 138 if s1 >= n_symbols { return 0 } 139 if s1 == s0 { return 0 } 140 lengths[s1] = 1 141 } 142 return 1 143} 144 145// ===== NORMAL form ================================================ 146// 147// max_symbol is the spec early-stop: a stream may declare that only the 148// first N symbols carry lengths, leaving the tail implicitly zero. 149// Decrementing it per DECODED SYMBOL (not per written length) is what the 150// spec requires -- a run of 138 zeros costs one decrement, not 138. 151 152func wl_read_normal(bs: *NxBitStream, lengths: *i64, n_symbols: i64) -> i64 { 153 let order: *i64 = sys_mmap(NX_WL_CODE_LENGTH_CODES * 8 + 64) as *i64 154 wl_code_order(order) 155 156 let cl_lengths: *i64 = sys_mmap(NX_WL_CODE_LENGTH_CODES * 8 + 64) as *i64 157 var i: i64 = 0 158 while i < NX_WL_CODE_LENGTH_CODES { cl_lengths[i] = 0; i = i + 1 } 159 160 let num_code_lengths: i64 = nx_bitstream_read_lsb(bs, 4) + 4 161 if num_code_lengths > NX_WL_CODE_LENGTH_CODES { return 0 } 162 i = 0 163 while i < num_code_lengths { 164 cl_lengths[order[i]] = nx_bitstream_read_lsb(bs, 3) 165 i = i + 1 166 } 167 if bs.overflow == 1 { return 0 } 168 169 let cl_tree: *NxWlTree = wl_tree_from_lengths(cl_lengths, NX_WL_CODE_LENGTH_CODES) 170 if cl_tree == (0 as *NxWlTree) { return 0 } 171 172 var max_symbol: i64 = n_symbols 173 if nx_bitstream_read_lsb(bs, 1) == 1 { 174 let length_nbits: i64 = 2 + 2 * nx_bitstream_read_lsb(bs, 3) 175 max_symbol = 2 + nx_bitstream_read_lsb(bs, length_nbits) 176 } 177 // a hostile stream may declare a max_symbol far past the alphabet; the 178 // decode loop is already bounded by n_symbols, but clamping here keeps the 179 // early-stop counter meaningful rather than letting it run as a no-op. 180 if max_symbol > n_symbols { max_symbol = n_symbols } 181 182 var symbol: i64 = 0 183 var prev_len: i64 = NX_WL_DEFAULT_CODE_LEN 184 var ok: i64 = 1 185 var go: i64 = 1 186 while go == 1 { 187 if symbol >= n_symbols { go = 0 } else { 188 if max_symbol <= 0 { go = 0 } else { 189 max_symbol = max_symbol - 1 190 let code_len: i64 = wl_decode_symbol(cl_tree, bs) 191 if code_len < 0 { ok = 0; go = 0 } else { 192 if code_len < NX_WL_CODE_LEN_LITERALS { 193 lengths[symbol] = code_len 194 symbol = symbol + 1 195 if code_len != 0 { prev_len = code_len } 196 } else { 197 let slot: i64 = code_len - NX_WL_CODE_LEN_LITERALS 198 if slot > 2 { ok = 0; go = 0 } else { 199 let extra: i64 = wl_repeat_extra_bits(slot) 200 let offset: i64 = wl_repeat_offset(slot) 201 var repeat: i64 = nx_bitstream_read_lsb(bs, extra) + offset 202 var fill: i64 = 0 203 if slot == 0 { fill = prev_len } 204 if symbol + repeat > n_symbols { ok = 0; go = 0 } else { 205 while repeat > 0 { 206 lengths[symbol] = fill 207 symbol = symbol + 1 208 repeat = repeat - 1 209 } 210 } 211 } 212 } } 213 } } 214 } 215 if bs.overflow == 1 { return 0 } 216 return ok 217} 218 219// ===== the entry point ============================================ 220// 221// Reads one complete Huffman tree of n_symbols from the stream. 222// Returns 0 on any malformed input -- callers MUST check, because a 223// null tree fed to wl_decode_symbol returns -1 forever and would 224// otherwise spin a decode loop rather than failing it. 225 226func wl_read_huffman_code(bs: *NxBitStream, n_symbols: i64) -> *NxWlTree { 227 if n_symbols <= 0 { return 0 as *NxWlTree } 228 let lengths: *i64 = sys_mmap(n_symbols * 8 + 64) as *i64 229 var i: i64 = 0 230 while i < n_symbols { lengths[i] = 0; i = i + 1 } 231 232 let simple: i64 = nx_bitstream_read_lsb(bs, 1) 233 var ok: i64 = 0 234 if simple == 1 { 235 ok = wl_read_simple(bs, lengths, n_symbols) 236 } else { 237 ok = wl_read_normal(bs, lengths, n_symbols) 238 } 239 if ok == 0 { return 0 as *NxWlTree } 240 return wl_tree_from_lengths(lengths, n_symbols) 241}