Skip to main content

oxihuman_core/
image_webp.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3
4//! WebP VP8L (lossless) encoder and decoder — pure Rust, no external codec deps.
5//!
6//! # Format overview
7//!
8//! ```text
9//! RIFF header  : "RIFF" + file_size(u32 LE) + "WEBP"
10//! VP8L chunk   : "VP8L" + chunk_size(u32 LE) + signature(0x2F) + VP8L bitstream
11//!
12//! VP8L bitstream (LSB-first bit packing):
13//!   [14 bits] width  - 1
14//!   [14 bits] height - 1
15//!   [ 1 bit ] alpha_is_used
16//!   [ 3 bits] version (must be 0)
17//!   [ 1 bit ] transform_present = 0   (no transforms)
18//!   [ 1 bit ] color_cache_code_bits = 0 (no color cache)
19//!   [ 1 bit ] huffman_meta = 0        (no meta Huffman image)
20//!   [ 5 Huffman trees: G, R, B, A, Distance ]
21//!   [ pixel data encoded via those trees ]
22//! ```
23//!
24//! ## Huffman tree encoding
25//!
26//! Each Huffman tree is written in one of two forms:
27//!
28//! ### Simple tree (≤ 2 distinct symbols)
29//! ```text
30//! [1]  is_simple = 1
31//! [1]  num_symbols_minus1   (0 → 1 symbol, 1 → 2 symbols)
32//! if 1 symbol: [8] symbol_value
33//! if 2 symbols:
34//!    [1]  first_symbol_uses_8bits  (always 1 here — we never rely on the short 1-bit path)
35//!    [8]  first_symbol
36//!    [8]  second_symbol
37//! ```
38//!
39//! ### Standard tree (> 2 distinct symbols, up to 256)
40//! ```text
41//! [1]  is_simple = 0
42//! [1]  use_length_limit = 0  (max_symbol = full alphabet)
43//! [4]  num_code_length_codes_minus4   (value 0..=15 → means 4..=19 CL-codes written)
44//! For each of the 19 code-length permuted positions (order 17,18,0,1,2,3,4,5,16,6..15):
45//!    [3]  code_length_code_length  (only the first num_code_length_codes_minus4+4 are non-zero)
46//! Then: encode `alphabet_size` code lengths using those 19-symbol meta-Huffman codes.
47//! ```
48//!
49//! ## Pixel encoding
50//! For each pixel (ARGB packed):
51//!   encode G using G-tree, then R using R-tree, then B using B-tree, then A using A-tree.
52//! Distance tree is never used (literal-only mode).
53
54#![allow(dead_code)]
55
56use super::image_codec::RawDecodeResult;
57
58// ── Error ─────────────────────────────────────────────────────────────────────
59
60/// Errors that can occur during WebP encode/decode.
61#[derive(Debug, thiserror::Error)]
62pub enum WebpError {
63    #[error("Invalid WebP: {0}")]
64    Invalid(String),
65    #[error("Unsupported WebP feature: {0}")]
66    Unsupported(String),
67    #[error("Truncated data")]
68    Truncated,
69}
70
71// ── Bit writer (LSB-first, little-endian) ─────────────────────────────────────
72
73struct BitWriter {
74    data: Vec<u8>,
75    cur_byte: u32,
76    bit_count: u8, // bits filled in cur_byte so far (0..=7)
77}
78
79impl BitWriter {
80    fn new() -> Self {
81        Self {
82            data: Vec::new(),
83            cur_byte: 0,
84            bit_count: 0,
85        }
86    }
87
88    /// Write `n` bits from `value` (LSB first).
89    fn write_bits(&mut self, value: u64, n: u8) {
90        debug_assert!(n <= 64);
91        let mut remaining = n;
92        let mut val = value;
93        while remaining > 0 {
94            let can_write = 8 - self.bit_count;
95            let to_write = remaining.min(can_write);
96            let mask = (1u64 << to_write) - 1;
97            let bits = (val & mask) as u32;
98            self.cur_byte |= bits << self.bit_count;
99            self.bit_count += to_write;
100            val >>= to_write;
101            remaining -= to_write;
102            if self.bit_count == 8 {
103                self.data.push(self.cur_byte as u8);
104                self.cur_byte = 0;
105                self.bit_count = 0;
106            }
107        }
108    }
109
110    /// Flush any partial byte with zero padding.
111    fn flush(&mut self) {
112        if self.bit_count > 0 {
113            self.data.push(self.cur_byte as u8);
114            self.cur_byte = 0;
115            self.bit_count = 0;
116        }
117    }
118
119    fn into_bytes(mut self) -> Vec<u8> {
120        self.flush();
121        self.data
122    }
123}
124
125// ── Bit reader (LSB-first) ────────────────────────────────────────────────────
126
127struct BitReader<'a> {
128    data: &'a [u8],
129    byte_pos: usize,
130    bit_pos: u8, // bits consumed in current byte (0..=7)
131}
132
133impl<'a> BitReader<'a> {
134    fn new(data: &'a [u8]) -> Self {
135        Self {
136            data,
137            byte_pos: 0,
138            bit_pos: 0,
139        }
140    }
141
142    /// Read `n` bits (LSB first). Returns error on truncation.
143    fn read_bits(&mut self, n: u8) -> Result<u64, WebpError> {
144        debug_assert!(n <= 64);
145        let mut result: u64 = 0;
146        let mut filled = 0u8;
147        let mut remaining = n;
148        while remaining > 0 {
149            if self.byte_pos >= self.data.len() {
150                return Err(WebpError::Truncated);
151            }
152            let cur = self.data[self.byte_pos];
153            let available = 8 - self.bit_pos;
154            let to_read = remaining.min(available);
155            let mask = if to_read == 8 {
156                0xFFu8
157            } else {
158                (1u8 << to_read) - 1
159            };
160            let bits = ((cur >> self.bit_pos) & mask) as u64;
161            result |= bits << filled;
162            filled += to_read;
163            self.bit_pos += to_read;
164            remaining -= to_read;
165            if self.bit_pos == 8 {
166                self.byte_pos += 1;
167                self.bit_pos = 0;
168            }
169        }
170        Ok(result)
171    }
172}
173
174// ── Canonical Huffman code generation ─────────────────────────────────────────
175
176/// Compute Huffman code lengths for `symbols` (0..alphabet_size) from frequency counts.
177/// Returns a Vec<u8> of length `alphabet_size` where entry `i` is the code length for symbol `i`.
178/// Lengths are capped at `max_bits` (≤ 15 for VP8L).
179///
180/// Algorithm: standard optimal Huffman tree built with a min-heap, then depths are capped
181/// at `max_bits` and the Kraft inequality is restored by shortening codes of the least-frequent
182/// symbols (which are already long).  For VP8L's ≤256 symbols with max_bits=15 this is always
183/// feasible — optimal depths never exceed ~log2(256)=8.
184fn compute_code_lengths(freqs: &[u32], alphabet_size: usize, max_bits: u8) -> Vec<u8> {
185    use std::cmp::Reverse;
186    use std::collections::BinaryHeap;
187
188    let mut lengths = vec![0u8; alphabet_size];
189
190    // Non-zero symbols, sorted by ascending frequency (for tiebreaking, use symbol index).
191    let mut non_zero: Vec<usize> = (0..alphabet_size)
192        .filter(|&i| freqs.get(i).copied().unwrap_or(0) > 0)
193        .collect();
194
195    match non_zero.len() {
196        0 => return lengths,
197        1 => {
198            lengths[non_zero[0]] = 1;
199            return lengths;
200        }
201        _ => {}
202    }
203
204    non_zero.sort_unstable_by_key(|&i| (freqs[i], i));
205
206    let n = non_zero.len();
207
208    // We represent the tree as a flat array of (freq, parent, depth) for each node.
209    // Leaves: indices 0..n (mapped to symbols via non_zero[i]).
210    // Internal nodes: indices n..2n-1.
211    let mut node_freq = vec![0u64; 2 * n];
212    let mut depth = vec![0u8; 2 * n];
213
214    for (i, &sym) in non_zero.iter().enumerate() {
215        node_freq[i] = freqs[sym] as u64;
216    }
217
218    // Min-heap: (freq, counter, node_id) — counter for stable ordering.
219    let mut heap: BinaryHeap<Reverse<(u64, usize, usize)>> =
220        (0..n).map(|i| Reverse((node_freq[i], i, i))).collect();
221
222    let mut next_node = n;
223    let mut counter = n;
224    let mut parent = vec![usize::MAX; 2 * n];
225
226    while heap.len() > 1 {
227        let Reverse((f1, _, id1)) = heap.pop().expect("heap non-empty");
228        let Reverse((f2, _, id2)) = heap.pop().expect("heap non-empty");
229
230        let new_id = next_node;
231        next_node += 1;
232        node_freq[new_id] = f1 + f2;
233        depth[new_id] = 0; // set by tree traversal below
234        parent[id1] = new_id;
235        parent[id2] = new_id;
236
237        heap.push(Reverse((node_freq[new_id], counter, new_id)));
238        counter += 1;
239    }
240
241    // Compute depths by traversing from each leaf to the root.
242    let root = if next_node > n { next_node - 1 } else { 0 };
243    parent[root] = root;
244
245    for i in 0..n {
246        let mut d = 0u8;
247        let mut cur = i;
248        while parent[cur] != cur {
249            d += 1;
250            cur = parent[cur];
251            if d > max_bits + 1 {
252                break; // safety cap; handled below
253            }
254        }
255        lengths[non_zero[i]] = d.min(max_bits);
256    }
257
258    // ── Restore the Kraft inequality after capping ────────────────────────────
259    //
260    // Canonical Huffman trees satisfy sum(2^{-l_i}) = 1.  Capping lengths can only
261    // increase this sum (shorter codes → more "weight").  We restore it by
262    // increassing the lengths of the cheapest (most-frequent) symbols, which shortens
263    // their codes and thus decreases the Kraft sum — but we need to make the tree
264    // *valid*, so we increase codes of the *least-frequent* symbols (already the
265    // longest) to free up code space.
266    //
267    // In practice, for ≤256 symbols and max_bits=15 the optimal depth is at most 8,
268    // so no adjustment is ever needed.  The loop below handles corner cases.
269
270    // Use integer arithmetic scaled to 2^max_bits.
271    let scale: i64 = 1i64 << max_bits;
272    loop {
273        let kraft: i64 = non_zero
274            .iter()
275            .map(|&sym| {
276                let l = lengths[sym] as u32;
277                if l > 0 {
278                    scale >> l
279                } else {
280                    0i64
281                }
282            })
283            .sum();
284
285        if kraft <= scale {
286            break;
287        }
288
289        // Kraft sum > 1: increase the length of the cheapest (first in `non_zero`) symbol
290        // that hasn't yet hit `max_bits`.
291        let mut changed = false;
292        for &sym in non_zero.iter() {
293            if lengths[sym] < max_bits {
294                lengths[sym] += 1;
295                changed = true;
296                break;
297            }
298        }
299        if !changed {
300            break; // all at max_bits; can't do more
301        }
302    }
303
304    lengths
305}
306
307/// Given code lengths, assign canonical Huffman codes (shorter = smaller symbol index).
308/// Returns a Vec<(code: u16, length: u8)> of length `alphabet_size`.
309/// Symbols with length=0 get code=0 and are not used.
310/// Assign canonical Huffman codes to each symbol and **bit-reverse** each code so it is
311/// ready for use with a LSB-first bitstream.
312///
313/// Standard canonical Huffman code assignment (MSB-first) followed by per-code bit-reversal:
314///   code_for_symbol_s = reverse_bits(canonical_code(s), len_s)
315///
316/// This is required because VP8L writes and reads bits LSB-first, but Huffman code values
317/// are assigned canonically (MSB-first ordering).  The bit-reversal ensures the decoder
318/// reading `len` bits from an LSB-first stream recovers the canonical code value.
319fn canonical_codes_from_lengths(lengths: &[u8]) -> Vec<(u16, u8)> {
320    let alphabet_size = lengths.len();
321    let max_len = *lengths.iter().max().unwrap_or(&0) as usize;
322    let mut codes = vec![(0u16, 0u8); alphabet_size];
323
324    if max_len == 0 {
325        return codes;
326    }
327
328    // Count symbols per length.
329    let mut bl_count = vec![0u32; max_len + 1];
330    for &l in lengths {
331        if l > 0 {
332            bl_count[l as usize] += 1;
333        }
334    }
335
336    // Compute starting (MSB-first) canonical code for each length group.
337    let mut next_code = vec![0u32; max_len + 2];
338    let mut code: u32 = 0;
339    bl_count[0] = 0;
340    for bits in 1..=max_len {
341        code = (code + bl_count[bits - 1]) << 1;
342        next_code[bits] = code;
343    }
344
345    // Assign codes and bit-reverse each one for LSB-first use.
346    for s in 0..alphabet_size {
347        let l = lengths[s] as usize;
348        if l > 0 {
349            let canon = next_code[l] as u16;
350            next_code[l] += 1;
351            // Reverse the `l` bits of `canon` to get the LSB-first code.
352            let reversed = reverse_bits_u16(canon, l as u8);
353            codes[s] = (reversed, lengths[s]);
354        }
355    }
356
357    codes
358}
359
360/// Reverse the low `n` bits of `v`.
361#[inline]
362fn reverse_bits_u16(v: u16, n: u8) -> u16 {
363    debug_assert!(n <= 16);
364    let mut r = 0u16;
365    let mut x = v;
366    for _ in 0..n {
367        r = (r << 1) | (x & 1);
368        x >>= 1;
369    }
370    r
371}
372
373// ── Huffman decode table ──────────────────────────────────────────────────────
374
375/// A flat, direct-lookup Huffman decode table.
376///
377/// Indexed by `max_bits`-bit window (read LSB-first from the bitstream).
378/// Each entry stores (symbol, actual_code_length).  We read `max_bits` bits,
379/// index the table, get the symbol and how many bits it actually consumed,
380/// then "unread" the remaining bits via the bit reader.
381struct HuffTable {
382    /// `table[i]` = (symbol, code_length) for the code whose low `code_length`
383    /// bits equal `i & ((1 << code_length) - 1)`.  Entries with `code_length == 0`
384    /// indicate invalid/unused slots (should not be reached for valid streams).
385    table: Vec<(u16, u8)>,
386    /// Maximum code length present in this table.
387    max_bits: u8,
388    /// Number of distinct symbols (for the single-symbol special case).
389    single_symbol: Option<u16>,
390}
391
392impl HuffTable {
393    /// Build a decode table from canonical code lengths.
394    fn build(lengths: &[u8]) -> Self {
395        let max_bits = lengths.iter().copied().max().unwrap_or(0);
396
397        // Count non-zero symbols.
398        let non_zero_syms: Vec<u16> = (0..lengths.len() as u16)
399            .filter(|&i| lengths[i as usize] > 0)
400            .collect();
401
402        if non_zero_syms.is_empty() {
403            return HuffTable {
404                table: vec![(0, 0); 1],
405                max_bits: 0,
406                single_symbol: None,
407            };
408        }
409
410        // Single-symbol degenerate case: no bits read, always return this symbol.
411        if non_zero_syms.len() == 1 {
412            return HuffTable {
413                table: vec![(non_zero_syms[0], 1); 2],
414                max_bits: 1,
415                single_symbol: Some(non_zero_syms[0]),
416            };
417        }
418
419        // Build canonical codes.
420        let codes = canonical_codes_from_lengths(lengths);
421
422        // Allocate the full decode table: 2^max_bits entries.
423        let table_size = 1usize << max_bits;
424        let mut table = vec![(0u16, 0u8); table_size];
425
426        for (sym, &(code, len)) in codes.iter().enumerate() {
427            if len == 0 {
428                continue;
429            }
430            // For each entry whose low `len` bits equal `code`, fill the table.
431            // The remaining `max_bits - len` bits are "don't care" → enumerate all.
432            let extra_bits = max_bits - len;
433            let num_fill = 1usize << extra_bits;
434            for fill in 0..num_fill {
435                // LSB-first: code occupies the low `len` bits; `fill` occupies the upper bits.
436                let idx = (fill << len) | (code as usize);
437                table[idx] = (sym as u16, len);
438            }
439        }
440
441        HuffTable {
442            table,
443            max_bits,
444            single_symbol: None,
445        }
446    }
447
448    /// Decode one symbol: reads `max_bits` bits, looks up the table, then "unreads" the
449    /// bits that belong to the next symbol.
450    fn decode(&self, reader: &mut BitReader<'_>) -> Result<u16, WebpError> {
451        if self.max_bits == 0 {
452            return Err(WebpError::Invalid("Huffman table has max_bits=0".into()));
453        }
454
455        // Single-symbol shortcut: read 1 bit (always 0), return the symbol.
456        if let Some(sym) = self.single_symbol {
457            let _ = reader.read_bits(1)?; // consume 1 bit (code = 0b0 or 0b1 doesn't matter)
458            return Ok(sym);
459        }
460
461        let bits = reader.read_bits(self.max_bits)? as usize;
462        let (sym, code_len) = self.table[bits & (self.table.len() - 1)];
463
464        if code_len == 0 {
465            return Err(WebpError::Invalid(format!(
466                "invalid Huffman prefix bits={:b}",
467                bits
468            )));
469        }
470
471        // Unread the bits that weren't part of this code.
472        let unused = self.max_bits - code_len;
473        if unused > 0 {
474            reader.unread_bits(unused);
475        }
476
477        Ok(sym)
478    }
479}
480
481// Alias for consistency with the rest of the code.
482type HuffTree = HuffTable;
483
484impl<'a> BitReader<'a> {
485    /// Undo `n` bits that were just read (wind the position back).
486    ///
487    /// The bits are guaranteed to be at or very near the current position, so
488    /// unwinding the byte_pos/bit_pos bookkeeping is straightforward.
489    fn unread_bits(&mut self, n: u8) {
490        let mut remaining = n as u32;
491        while remaining > 0 {
492            if (self.bit_pos as u32) >= remaining {
493                self.bit_pos -= remaining as u8;
494                remaining = 0;
495            } else {
496                remaining -= self.bit_pos as u32;
497                if self.byte_pos > 0 {
498                    self.byte_pos -= 1;
499                    self.bit_pos = 8;
500                } else {
501                    self.bit_pos = 0;
502                    break;
503                }
504            }
505        }
506    }
507}
508
509// ── Huffman tree encoding (write to VP8L bitstream) ───────────────────────────
510
511/// Description of a Huffman tree to be written or decoded.
512#[derive(Debug)]
513enum HuffSpec {
514    /// 1 distinct symbol.
515    Simple1 { symbol: u16 },
516    /// 2 distinct symbols.
517    Simple2 { sym0: u16, sym1: u16 },
518    /// Standard (code-length-coded) tree.
519    Standard { lengths: Vec<u8> },
520}
521
522/// Analyse `freqs[0..alphabet_size]` and choose the simplest HuffSpec.
523fn analyse_freqs(freqs: &[u32], alphabet_size: usize) -> HuffSpec {
524    let mut distinct: Vec<u16> = (0..alphabet_size as u16)
525        .filter(|&i| freqs.get(i as usize).copied().unwrap_or(0) > 0)
526        .collect();
527    distinct.sort_unstable();
528
529    match distinct.len() {
530        0 => HuffSpec::Simple1 { symbol: 0 }, // degenerate: single symbol=0
531        1 => HuffSpec::Simple1 {
532            symbol: distinct[0],
533        },
534        2 => HuffSpec::Simple2 {
535            sym0: distinct[0],
536            sym1: distinct[1],
537        },
538        _ => {
539            let lengths = compute_code_lengths(freqs, alphabet_size, 15);
540            HuffSpec::Standard { lengths }
541        }
542    }
543}
544
545/// Write a HuffSpec into `bw`.
546fn write_huff_tree(bw: &mut BitWriter, spec: &HuffSpec) {
547    match spec {
548        HuffSpec::Simple1 { symbol } => {
549            bw.write_bits(1, 1); // is_simple = 1
550            bw.write_bits(0, 1); // num_symbols_minus1 = 0  (one symbol)
551            bw.write_bits(*symbol as u64, 8); // 8-bit symbol value
552        }
553        HuffSpec::Simple2 { sym0, sym1 } => {
554            bw.write_bits(1, 1); // is_simple = 1
555            bw.write_bits(1, 1); // num_symbols_minus1 = 1  (two symbols)
556            bw.write_bits(1, 1); // first_symbol_uses_8bits = 1
557            bw.write_bits(*sym0 as u64, 8);
558            bw.write_bits(*sym1 as u64, 8);
559        }
560        HuffSpec::Standard { lengths } => {
561            write_standard_huff_tree(bw, lengths);
562        }
563    }
564}
565
566/// Write the standard (code-length-coded) Huffman tree spec.
567fn write_standard_huff_tree(bw: &mut BitWriter, lengths: &[u8]) {
568    bw.write_bits(0, 1); // is_simple = 0
569
570    let alphabet_size = lengths.len();
571
572    // use_length_limit: 0 (we always write full alphabet)
573    bw.write_bits(0, 1);
574
575    // Build meta-Huffman (code-length codes) from lengths.
576    // The 19 possible code-length values (0..=18), where 16/17/18 are RLE codes.
577    // We only use values 0..=15 (no RLE for simplicity), so 19 meta-symbol freqs.
578    const NUM_META: usize = 19;
579    let mut cl_freqs = [0u32; NUM_META];
580    for &l in lengths.iter().take(alphabet_size) {
581        cl_freqs[l as usize] += 1;
582    }
583
584    // Permuted order for code-length codes per VP8L spec.
585    const CL_ORDER: [usize; 19] = [
586        17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
587    ];
588
589    let cl_lengths = compute_code_lengths(&cl_freqs, NUM_META, 7);
590
591    // Find how many code-length codes to write (trailing zeros trimmed, min 4).
592    let num_cl = {
593        let mut last = 4usize;
594        for i in 0..19 {
595            if cl_lengths[CL_ORDER[i]] > 0 {
596                last = i + 1;
597            }
598        }
599        last.max(4)
600    };
601
602    // num_code_length_codes_minus4 (4 bits)
603    bw.write_bits((num_cl - 4) as u64, 4);
604
605    // Write the code-length code lengths.
606    for i in 0..num_cl {
607        bw.write_bits(cl_lengths[CL_ORDER[i]] as u64, 3);
608    }
609
610    // Encode `lengths` using the meta-Huffman.
611    let cl_codes = canonical_codes_from_lengths(&cl_lengths);
612    for &l in &lengths[..alphabet_size] {
613        let (code, len) = cl_codes[l as usize];
614        if len == 0 {
615            bw.write_bits(0, 1);
616        } else {
617            bw.write_bits(code as u64, len);
618        }
619    }
620}
621
622/// Read a HuffSpec from the bitstream and build a HuffTree.
623fn read_huff_tree(br: &mut BitReader<'_>, alphabet_size: usize) -> Result<HuffTree, WebpError> {
624    let is_simple = br.read_bits(1)? != 0;
625    if is_simple {
626        let num_syms_minus1 = br.read_bits(1)?;
627        if num_syms_minus1 == 0 {
628            // One symbol.
629            let sym = br.read_bits(8)? as usize;
630            let mut lengths = vec![0u8; alphabet_size];
631            if sym < alphabet_size {
632                lengths[sym] = 1;
633            }
634            return Ok(HuffTree::build(&lengths));
635        } else {
636            // Two symbols.
637            let _first_uses_8bits = br.read_bits(1)?; // always 1 in our encoder
638            let sym0 = br.read_bits(8)? as usize;
639            let sym1 = br.read_bits(8)? as usize;
640            let mut lengths = vec![0u8; alphabet_size];
641            if sym0 < alphabet_size {
642                lengths[sym0] = 1;
643            }
644            if sym1 < alphabet_size && sym1 != sym0 {
645                lengths[sym1] = 1;
646            }
647            return Ok(HuffTree::build(&lengths));
648        }
649    }
650
651    // Standard tree.
652    let _use_length_limit = br.read_bits(1)?; // we wrote 0; ignoring for decoding
653
654    const NUM_META: usize = 19;
655    const CL_ORDER: [usize; 19] = [
656        17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
657    ];
658
659    let num_cl = (br.read_bits(4)? as usize) + 4;
660    let mut cl_lengths = [0u8; NUM_META];
661    for i in 0..num_cl {
662        cl_lengths[CL_ORDER[i]] = br.read_bits(3)? as u8;
663    }
664
665    let cl_tree = HuffTree::build(&cl_lengths);
666
667    // Decode `alphabet_size` code lengths using cl_tree.
668    let mut lengths = vec![0u8; alphabet_size];
669    for i in 0..alphabet_size {
670        let sym = cl_tree.decode(br)?;
671        // Values 0..=15 → direct code length; 16/17/18 → RLE (our encoder never emits these)
672        match sym {
673            0..=15 => lengths[i] = sym as u8,
674            16 => {
675                // Repeat previous length 3+extra(2bits) times
676                let extra = br.read_bits(2)? as usize;
677                let prev = if i > 0 { lengths[i - 1] } else { 0 };
678                for j in 0..(3 + extra) {
679                    if i + j < alphabet_size {
680                        lengths[i + j] = prev;
681                    }
682                }
683            }
684            17 => {
685                // Repeat zero length 3+extra(3bits) times
686                let extra = br.read_bits(3)? as usize;
687                for j in 0..(3 + extra) {
688                    if i + j < alphabet_size {
689                        lengths[i + j] = 0;
690                    }
691                }
692            }
693            18 => {
694                // Repeat zero length 11+extra(7bits) times
695                let extra = br.read_bits(7)? as usize;
696                for j in 0..(11 + extra) {
697                    if i + j < alphabet_size {
698                        lengths[i + j] = 0;
699                    }
700                }
701            }
702            _ => return Err(WebpError::Invalid(format!("unexpected CL code {}", sym))),
703        }
704    }
705
706    Ok(HuffTree::build(&lengths))
707}
708
709// ── VP8L alphabet sizes ────────────────────────────────────────────────────────
710
711/// Size of the "green" alphabet: 256 literal green values + 24 copy-length codes = 280.
712/// For literal-only encoding we use 256.
713const ALPHA_G: usize = 256;
714const ALPHA_R: usize = 256;
715const ALPHA_B: usize = 256;
716const ALPHA_A: usize = 256;
717const ALPHA_D: usize = 40; // distance alphabet (backward references — unused in literal mode)
718
719// ── Encoder ───────────────────────────────────────────────────────────────────
720
721/// Encode raw RGB24 pixels (row-major, top-to-bottom) as a WebP file containing a VP8L stream.
722///
723/// `pixels` must be exactly `width * height * 3` bytes.
724pub fn webp_encode_rgb(width: u32, height: u32, pixels: &[u8]) -> Result<Vec<u8>, WebpError> {
725    let pixel_count = (width as usize) * (height as usize);
726    if pixels.len() != pixel_count * 3 {
727        return Err(WebpError::Invalid(format!(
728            "pixel buffer length {} != expected {}",
729            pixels.len(),
730            pixel_count * 3
731        )));
732    }
733    if width == 0 || height == 0 {
734        return Err(WebpError::Invalid("zero dimension".into()));
735    }
736    if width > 16384 || height > 16384 {
737        return Err(WebpError::Invalid(
738            "dimension exceeds VP8L max of 16384".into(),
739        ));
740    }
741
742    // Convert RGB → ARGB (alpha = 255).
743    let mut argb: Vec<[u8; 4]> = Vec::with_capacity(pixel_count);
744    for px in pixels.chunks_exact(3) {
745        argb.push([255, px[0], px[1], px[2]]); // A, R, G, B
746    }
747
748    // Build frequency tables for G, R, B, A channels.
749    let mut freq_g = [0u32; ALPHA_G];
750    let mut freq_r = [0u32; ALPHA_R];
751    let mut freq_b = [0u32; ALPHA_B];
752    let mut freq_a = [0u32; ALPHA_A];
753    let freq_d = [0u32; ALPHA_D]; // no backward refs
754
755    for &[a, r, g, b] in &argb {
756        freq_g[g as usize] += 1;
757        freq_r[r as usize] += 1;
758        freq_b[b as usize] += 1;
759        freq_a[a as usize] += 1;
760    }
761
762    // Choose HuffSpec for each channel.
763    let spec_g = analyse_freqs(&freq_g, ALPHA_G);
764    let spec_r = analyse_freqs(&freq_r, ALPHA_R);
765    let spec_b = analyse_freqs(&freq_b, ALPHA_B);
766    let spec_a = analyse_freqs(&freq_a, ALPHA_A);
767    let spec_d = analyse_freqs(&freq_d, ALPHA_D);
768
769    // Build encode code tables.
770    let codes_g = build_encode_table(&spec_g, ALPHA_G);
771    let codes_r = build_encode_table(&spec_r, ALPHA_R);
772    let codes_b = build_encode_table(&spec_b, ALPHA_B);
773    let codes_a = build_encode_table(&spec_a, ALPHA_A);
774
775    // ── Write VP8L bitstream ──────────────────────────────────────────────────
776    let mut bw = BitWriter::new();
777
778    // Dimensions (14 bits each, value = actual_dim - 1).
779    bw.write_bits((width - 1) as u64, 14);
780    bw.write_bits((height - 1) as u64, 14);
781
782    // alpha_is_used = 0 (all pixels have alpha=255, so not "meaningful" alpha).
783    bw.write_bits(0, 1);
784
785    // version = 0 (3 bits).
786    bw.write_bits(0, 3);
787
788    // transform_present = 0.
789    bw.write_bits(0, 1);
790
791    // color_cache_code_bits = 0 (no color cache; 1 bit).
792    bw.write_bits(0, 1);
793
794    // huffman_meta = 0 (single meta group, 1 bit).
795    bw.write_bits(0, 1);
796
797    // Write 5 Huffman trees in order: G, R, B, A, Distance.
798    write_huff_tree(&mut bw, &spec_g);
799    write_huff_tree(&mut bw, &spec_r);
800    write_huff_tree(&mut bw, &spec_b);
801    write_huff_tree(&mut bw, &spec_a);
802    write_huff_tree(&mut bw, &spec_d);
803
804    // Write pixel data: for each pixel, emit G, R, B, A symbols.
805    for &[a, r, g, b] in &argb {
806        encode_symbol(&mut bw, &codes_g, g as usize);
807        encode_symbol(&mut bw, &codes_r, r as usize);
808        encode_symbol(&mut bw, &codes_b, b as usize);
809        encode_symbol(&mut bw, &codes_a, a as usize);
810    }
811
812    let vp8l_data = bw.into_bytes();
813
814    // ── Assemble RIFF/WEBP container ─────────────────────────────────────────
815    // VP8L chunk payload = 1-byte signature (0x2F) + vp8l_data
816    let chunk_payload_size = 1 + vp8l_data.len();
817    let chunk_size = chunk_payload_size as u32;
818
819    // RIFF total file size = 4 (WEBP) + 4 (VP8L) + 4 (chunk_size) + chunk_payload
820    // Chunks must be padded to even size per RIFF spec.
821    let padded_chunk = (chunk_payload_size + 1) & !1;
822    let riff_size = 4 + 4 + 4 + padded_chunk;
823
824    let mut out: Vec<u8> = Vec::with_capacity(12 + padded_chunk);
825    out.extend_from_slice(b"RIFF");
826    out.extend_from_slice(&(riff_size as u32).to_le_bytes());
827    out.extend_from_slice(b"WEBP");
828    out.extend_from_slice(b"VP8L");
829    out.extend_from_slice(&chunk_size.to_le_bytes());
830    out.push(0x2F); // VP8L signature byte
831    out.extend_from_slice(&vp8l_data);
832    if chunk_payload_size & 1 == 1 {
833        out.push(0x00); // pad to even
834    }
835
836    Ok(out)
837}
838
839/// Build a flat encode table (symbol → (code: u16, len: u8)) from a HuffSpec.
840fn build_encode_table(spec: &HuffSpec, alphabet_size: usize) -> Vec<(u16, u8)> {
841    match spec {
842        HuffSpec::Simple1 { symbol } => {
843            let mut table = vec![(0u16, 0u8); alphabet_size];
844            if (*symbol as usize) < alphabet_size {
845                table[*symbol as usize] = (0, 1);
846            }
847            table
848        }
849        HuffSpec::Simple2 { sym0, sym1 } => {
850            let mut table = vec![(0u16, 0u8); alphabet_size];
851            if (*sym0 as usize) < alphabet_size {
852                table[*sym0 as usize] = (0, 1); // code 0
853            }
854            if (*sym1 as usize) < alphabet_size {
855                table[*sym1 as usize] = (1, 1); // code 1
856            }
857            table
858        }
859        HuffSpec::Standard { lengths } => canonical_codes_from_lengths(lengths),
860    }
861}
862
863/// Emit one symbol from a pre-built encode table.
864#[inline]
865fn encode_symbol(bw: &mut BitWriter, codes: &[(u16, u8)], sym: usize) {
866    if sym < codes.len() {
867        let (code, len) = codes[sym];
868        if len > 0 {
869            bw.write_bits(code as u64, len);
870        } else {
871            // len=0 means single-symbol simple tree → write nothing (implicit).
872        }
873    }
874}
875
876// ── Decoder ───────────────────────────────────────────────────────────────────
877
878/// Decode a WebP file (VP8L lossless only) into raw RGB24 pixels.
879pub fn webp_decode(bytes: &[u8]) -> Result<RawDecodeResult, WebpError> {
880    // ── Parse RIFF container ──────────────────────────────────────────────────
881    if bytes.len() < 12 {
882        return Err(WebpError::Truncated);
883    }
884    if &bytes[0..4] != b"RIFF" {
885        return Err(WebpError::Invalid("missing RIFF header".into()));
886    }
887    if &bytes[8..12] != b"WEBP" {
888        return Err(WebpError::Invalid("missing WEBP fourcc".into()));
889    }
890
891    // Find VP8L chunk.
892    let mut pos = 12usize;
893    let file_end = bytes.len();
894
895    while pos + 8 <= file_end {
896        let fourcc = &bytes[pos..pos + 4];
897        let chunk_size = u32::from_le_bytes(
898            bytes[pos + 4..pos + 8]
899                .try_into()
900                .map_err(|_| WebpError::Truncated)?,
901        ) as usize;
902        let data_start = pos + 8;
903        let data_end = data_start + chunk_size;
904
905        if fourcc == b"VP8L" {
906            if bytes.len() < data_end {
907                return Err(WebpError::Truncated);
908            }
909            let chunk_data = &bytes[data_start..data_end];
910            return decode_vp8l(chunk_data);
911        } else if fourcc == b"VP8 " || fourcc == b"VP8X" {
912            return Err(WebpError::Unsupported(
913                "only VP8L (lossless) is supported".into(),
914            ));
915        }
916
917        // Advance to next chunk (RIFF pads chunks to even size).
918        let padded = (chunk_size + 1) & !1;
919        pos = data_start + padded;
920    }
921
922    Err(WebpError::Invalid("VP8L chunk not found".into()))
923}
924
925/// Decode the VP8L chunk payload (after the chunk header, starting with 0x2F signature).
926fn decode_vp8l(data: &[u8]) -> Result<RawDecodeResult, WebpError> {
927    if data.is_empty() {
928        return Err(WebpError::Truncated);
929    }
930    if data[0] != 0x2F {
931        return Err(WebpError::Invalid(format!(
932            "VP8L signature byte expected 0x2F, got 0x{:02X}",
933            data[0]
934        )));
935    }
936
937    let mut br = BitReader::new(&data[1..]);
938
939    // Dimensions.
940    let width = (br.read_bits(14)? as u32) + 1;
941    let height = (br.read_bits(14)? as u32) + 1;
942
943    let _alpha_used = br.read_bits(1)?;
944    let version = br.read_bits(3)?;
945    if version != 0 {
946        return Err(WebpError::Invalid(format!(
947            "VP8L version must be 0, got {}",
948            version
949        )));
950    }
951
952    // Transform present flag.
953    let transform_present = br.read_bits(1)?;
954    if transform_present != 0 {
955        return Err(WebpError::Unsupported(
956            "VP8L transforms are not supported".into(),
957        ));
958    }
959
960    // Color cache code bits.
961    let color_cache_code_bits = br.read_bits(1)?;
962    if color_cache_code_bits != 0 {
963        return Err(WebpError::Unsupported(
964            "VP8L color cache not supported".into(),
965        ));
966    }
967
968    // Huffman meta.
969    let huffman_meta = br.read_bits(1)?;
970    if huffman_meta != 0 {
971        return Err(WebpError::Unsupported(
972            "VP8L meta Huffman not supported".into(),
973        ));
974    }
975
976    // Read 5 Huffman trees: G, R, B, A, Distance.
977    let tree_g = read_huff_tree(&mut br, ALPHA_G)?;
978    let tree_r = read_huff_tree(&mut br, ALPHA_R)?;
979    let tree_b = read_huff_tree(&mut br, ALPHA_B)?;
980    let tree_a = read_huff_tree(&mut br, ALPHA_A)?;
981    let _tree_d = read_huff_tree(&mut br, ALPHA_D)?;
982
983    // Decode pixel data.
984    let pixel_count = (width as usize) * (height as usize);
985    let mut rgb_pixels: Vec<u8> = Vec::with_capacity(pixel_count * 3);
986
987    for _ in 0..pixel_count {
988        let g = tree_g.decode(&mut br)? as u8;
989        let r = tree_r.decode(&mut br)? as u8;
990        let b = tree_b.decode(&mut br)? as u8;
991        let _a = tree_a.decode(&mut br)? as u8;
992        rgb_pixels.push(r);
993        rgb_pixels.push(g);
994        rgb_pixels.push(b);
995    }
996
997    Ok(RawDecodeResult {
998        width: width as usize,
999        height: height as usize,
1000        pixels: rgb_pixels,
1001    })
1002}
1003
1004// ── Tests ─────────────────────────────────────────────────────────────────────
1005
1006#[cfg(test)]
1007mod tests {
1008    use super::*;
1009
1010    fn make_solid_rgb(w: usize, h: usize, r: u8, g: u8, b: u8) -> Vec<u8> {
1011        let mut v = Vec::with_capacity(w * h * 3);
1012        for _ in 0..(w * h) {
1013            v.push(r);
1014            v.push(g);
1015            v.push(b);
1016        }
1017        v
1018    }
1019
1020    fn make_gradient(w: usize, h: usize) -> Vec<u8> {
1021        let mut v = Vec::with_capacity(w * h * 3);
1022        for y in 0..h {
1023            for x in 0..w {
1024                v.push(((x * 255) / w.max(1)) as u8);
1025                v.push(((y * 255) / h.max(1)) as u8);
1026                v.push(((x + y) % 256) as u8);
1027            }
1028        }
1029        v
1030    }
1031
1032    #[test]
1033    fn test_webp_header() {
1034        let pixels = make_solid_rgb(4, 4, 200, 100, 50);
1035        let encoded = webp_encode_rgb(4, 4, &pixels).expect("encode must succeed");
1036        assert!(encoded.starts_with(b"RIFF"), "must start with RIFF");
1037        assert_eq!(&encoded[8..12], b"WEBP", "must contain WEBP");
1038        assert!(
1039            encoded.windows(4).any(|w| w == b"VP8L"),
1040            "must contain VP8L chunk marker"
1041        );
1042    }
1043
1044    #[test]
1045    fn test_webp_roundtrip_solid_color() {
1046        let pixels = make_solid_rgb(4, 4, 127, 63, 200);
1047        let encoded = webp_encode_rgb(4, 4, &pixels).expect("encode");
1048        let decoded = webp_decode(&encoded).expect("decode");
1049        assert_eq!(decoded.width, 4);
1050        assert_eq!(decoded.height, 4);
1051        assert_eq!(
1052            decoded.pixels, pixels,
1053            "solid-color round-trip must be exact"
1054        );
1055    }
1056
1057    #[test]
1058    fn test_webp_roundtrip_gradient() {
1059        let pixels = make_gradient(8, 8);
1060        let encoded = webp_encode_rgb(8, 8, &pixels).expect("encode");
1061        let decoded = webp_decode(&encoded).expect("decode");
1062        assert_eq!(decoded.width, 8);
1063        assert_eq!(decoded.height, 8);
1064        assert_eq!(decoded.pixels, pixels, "gradient round-trip must be exact");
1065    }
1066
1067    #[test]
1068    fn test_webp_decode_invalid_returns_error() {
1069        let result = webp_decode(b"not a webp file at all 12345678");
1070        assert!(result.is_err(), "invalid input must return an error");
1071    }
1072
1073    #[test]
1074    fn test_webp_roundtrip_single_pixel() {
1075        let pixels = vec![42u8, 84u8, 168u8]; // 1×1 RGB
1076        let encoded = webp_encode_rgb(1, 1, &pixels).expect("encode");
1077        let decoded = webp_decode(&encoded).expect("decode");
1078        assert_eq!(decoded.width, 1);
1079        assert_eq!(decoded.height, 1);
1080        assert_eq!(decoded.pixels, pixels);
1081    }
1082
1083    #[test]
1084    fn test_webp_roundtrip_checkerboard() {
1085        // 4×4 checkerboard of two colors — exercises 2-symbol simple trees.
1086        let mut pixels = Vec::with_capacity(4 * 4 * 3);
1087        for y in 0..4usize {
1088            for x in 0..4usize {
1089                if (x + y) % 2 == 0 {
1090                    pixels.extend_from_slice(&[255, 0, 0]);
1091                } else {
1092                    pixels.extend_from_slice(&[0, 0, 255]);
1093                }
1094            }
1095        }
1096        let encoded = webp_encode_rgb(4, 4, &pixels).expect("encode");
1097        let decoded = webp_decode(&encoded).expect("decode");
1098        assert_eq!(decoded.pixels, pixels);
1099    }
1100}