Skip to main content

oximedia_codec/webp/
vp8l_encoder.rs

1//! VP8L (WebP lossless) encoder.
2//!
3//! Implements the VP8L bitstream format as specified in the WebP Lossless
4//! Bitstream Specification (IETF draft). Produces valid VP8L bitstreams
5//! that standard decoders (libwebp, Chrome, etc.) can read.
6//!
7//! # Encoding strategy
8//!
9//! 1. Write VP8L header (signature 0x2F + dimensions + alpha flag)
10//! 2. Apply subtract-green transform (always, very effective)
11//! 3. Optionally apply predictor transform at higher effort levels
12//! 4. Encode pixel data with Huffman coding (5 code groups)
13//! 5. Write Huffman-coded pixel literals (no LZ77 back-references in simple path)
14
15use crate::error::{CodecError, CodecResult};
16
17// ---------------------------------------------------------------------------
18// Constants
19// ---------------------------------------------------------------------------
20
21/// VP8L signature byte.
22const VP8L_SIGNATURE: u8 = 0x2f;
23
24/// Maximum image dimension for VP8L.
25const VP8L_MAX_DIMENSION: u32 = 16384;
26
27/// Code length code order as specified by the VP8L spec.
28const CODE_LENGTH_CODE_ORDER: [usize; 19] = [
29    17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
30];
31
32/// Maximum Huffman code length allowed in VP8L.
33const MAX_HUFFMAN_CODE_LENGTH: u32 = 15;
34
35/// Number of code length codes.
36const NUM_CODE_LENGTH_CODES: usize = 19;
37
38/// VP8L transform types.
39const TRANSFORM_PREDICTOR: u32 = 0;
40const TRANSFORM_SUBTRACT_GREEN: u32 = 2;
41
42/// VP8L uses 5 Huffman trees per code group:
43/// 0: green + length prefix (256 + 24 = 280 symbols)
44/// 1: red (256 symbols)
45/// 2: blue (256 symbols)
46/// 3: alpha (256 symbols)
47/// 4: distance prefix (40 symbols)
48const HUFFMAN_CODES_PER_META_CODE: usize = 5;
49
50/// Number of symbols for the green/length-prefix tree.
51/// 256 literals + 24 length prefix codes.
52const GREEN_ALPHABET_SIZE: usize = 280;
53
54/// Number of symbols for R/B/A trees.
55const CHANNEL_ALPHABET_SIZE: usize = 256;
56
57/// Distance alphabet size.
58const DISTANCE_ALPHABET_SIZE: usize = 40;
59
60// ---------------------------------------------------------------------------
61// VP8L Bit Writer (LSB-first)
62// ---------------------------------------------------------------------------
63
64/// VP8L bit writer that packs bits LSB-first.
65///
66/// VP8L uses a little-endian bit packing order, where the first bit written
67/// occupies the least significant bit position.
68#[derive(Debug)]
69pub struct Vp8lBitWriter {
70    /// Accumulated output bytes.
71    data: Vec<u8>,
72    /// Bit accumulator.
73    bits: u64,
74    /// Number of valid bits in the accumulator.
75    num_bits: u32,
76}
77
78impl Vp8lBitWriter {
79    /// Creates a new VP8L bit writer.
80    pub fn new() -> Self {
81        Self {
82            data: Vec::with_capacity(4096),
83            bits: 0,
84            num_bits: 0,
85        }
86    }
87
88    /// Creates a new VP8L bit writer with a size hint.
89    pub fn with_capacity(capacity: usize) -> Self {
90        Self {
91            data: Vec::with_capacity(capacity),
92            bits: 0,
93            num_bits: 0,
94        }
95    }
96
97    /// Writes `n_bits` (1..=32) of `value` in LSB-first order.
98    pub fn put_bits(&mut self, value: u32, n_bits: u32) {
99        debug_assert!(n_bits <= 32);
100        debug_assert!(n_bits == 32 || value < (1u32 << n_bits));
101
102        self.bits |= (value as u64) << self.num_bits;
103        self.num_bits += n_bits;
104        self.flush_partial();
105    }
106
107    /// Writes a single bit.
108    pub fn put_bit(&mut self, bit: bool) {
109        self.put_bits(u32::from(bit), 1);
110    }
111
112    /// Flushes complete bytes from the accumulator to `data`.
113    fn flush_partial(&mut self) {
114        while self.num_bits >= 8 {
115            self.data.push((self.bits & 0xff) as u8);
116            self.bits >>= 8;
117            self.num_bits -= 8;
118        }
119    }
120
121    /// Finishes writing and returns the complete byte buffer.
122    /// Any remaining bits are zero-padded to a byte boundary.
123    pub fn finish(mut self) -> Vec<u8> {
124        if self.num_bits > 0 {
125            self.data.push((self.bits & 0xff) as u8);
126        }
127        self.data
128    }
129
130    /// Returns the current byte length of written data (including
131    /// unflushed bits in the accumulator).
132    pub fn byte_len(&self) -> usize {
133        self.data.len() + if self.num_bits > 0 { 1 } else { 0 }
134    }
135}
136
137// ---------------------------------------------------------------------------
138// Huffman code builder
139// ---------------------------------------------------------------------------
140
141/// A single Huffman code entry: (bit length, code word).
142#[derive(Debug, Clone, Copy, Default)]
143struct HuffmanCode {
144    /// Bit length of this code (0 means unused symbol).
145    len: u32,
146    /// The actual code word (only the lower `len` bits are valid).
147    code: u32,
148}
149
150/// Histogram for one Huffman tree.
151#[derive(Debug, Clone)]
152struct Histogram {
153    counts: Vec<u32>,
154}
155
156impl Histogram {
157    fn new(size: usize) -> Self {
158        Self {
159            counts: vec![0; size],
160        }
161    }
162
163    fn add(&mut self, symbol: usize) {
164        if symbol < self.counts.len() {
165            self.counts[symbol] += 1;
166        }
167    }
168
169    fn alphabet_size(&self) -> usize {
170        self.counts.len()
171    }
172
173    /// Finds the number of non-zero symbols.
174    fn num_used(&self) -> usize {
175        self.counts.iter().filter(|&&c| c > 0).count()
176    }
177
178    /// Returns the single used symbol if exactly one is used.
179    fn single_symbol(&self) -> Option<usize> {
180        if self.num_used() == 1 {
181            self.counts.iter().position(|&c| c > 0)
182        } else {
183            None
184        }
185    }
186}
187
188/// Build length-limited Huffman codes from a histogram.
189///
190/// Uses a tree-based algorithm to produce optimal codes, then limits
191/// code lengths to `MAX_HUFFMAN_CODE_LENGTH` using the Kraft inequality.
192fn build_huffman_codes(hist: &Histogram) -> CodecResult<Vec<HuffmanCode>> {
193    let n = hist.alphabet_size();
194    let mut codes = vec![HuffmanCode::default(); n];
195
196    let used = hist.num_used();
197    if used == 0 {
198        // All symbols unused: return all-zero codes.
199        return Ok(codes);
200    }
201    if used == 1 {
202        // Single symbol gets length 1 (VP8L requires at least length 1).
203        if let Some(sym) = hist.single_symbol() {
204            codes[sym] = HuffmanCode { len: 1, code: 0 };
205        }
206        return Ok(codes);
207    }
208    if used == 2 {
209        // Two symbols: assign lengths 1 each, codes 0 and 1.
210        let mut iter = hist
211            .counts
212            .iter()
213            .enumerate()
214            .filter(|(_, &c)| c > 0)
215            .map(|(i, _)| i);
216        if let (Some(a), Some(b)) = (iter.next(), iter.next()) {
217            codes[a] = HuffmanCode { len: 1, code: 0 };
218            codes[b] = HuffmanCode { len: 1, code: 1 };
219        }
220        return Ok(codes);
221    }
222
223    // General case: build code lengths using a frequency-sorted approach.
224    let code_lengths = build_code_lengths(&hist.counts, MAX_HUFFMAN_CODE_LENGTH)?;
225
226    // Convert code lengths to canonical Huffman codes.
227    assign_canonical_codes(&code_lengths, &mut codes);
228
229    Ok(codes)
230}
231
232/// Build code lengths from frequency counts, limited to `max_length`.
233fn build_code_lengths(counts: &[u32], max_length: u32) -> CodecResult<Vec<u32>> {
234    let n = counts.len();
235    let mut lengths = vec![0u32; n];
236
237    // Collect (count, symbol) pairs for non-zero symbols, sort by frequency.
238    let mut symbols: Vec<(u32, usize)> = counts
239        .iter()
240        .enumerate()
241        .filter(|(_, &c)| c > 0)
242        .map(|(i, &c)| (c, i))
243        .collect();
244
245    if symbols.len() < 2 {
246        for &(_, sym) in &symbols {
247            lengths[sym] = 1;
248        }
249        return Ok(lengths);
250    }
251
252    // Sort by frequency ascending, break ties by symbol index.
253    symbols.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
254
255    // Build a Huffman tree bottom-up and extract depths.
256    let tree_lengths = build_tree_lengths(&symbols, max_length);
257
258    for (i, &(_, sym)) in symbols.iter().enumerate() {
259        lengths[sym] = tree_lengths[i];
260    }
261
262    Ok(lengths)
263}
264
265/// Build Huffman tree lengths using an iterative approach.
266///
267/// Constructs a Huffman tree and extracts bit lengths, then limits them
268/// to `max_length` using the Kraft inequality adjustment.
269fn build_tree_lengths(symbols: &[(u32, usize)], max_length: u32) -> Vec<u32> {
270    let n = symbols.len();
271    if n <= 1 {
272        return vec![1; n];
273    }
274
275    // Standard Huffman tree construction using a priority queue simulation.
276    // Node: (frequency, Option<left_child_idx>, Option<right_child_idx>)
277    let mut nodes: Vec<(u64, Option<usize>, Option<usize>)> = symbols
278        .iter()
279        .map(|&(freq, _)| (freq.max(1) as u64, None, None))
280        .collect();
281
282    let mut active: Vec<usize> = (0..n).collect();
283
284    while active.len() > 1 {
285        // Find the two nodes with smallest frequency.
286        active.sort_by(|&a, &b| nodes[a].0.cmp(&nodes[b].0));
287        let left = active[0];
288        let right = active[1];
289
290        let merged_freq = nodes[left].0 + nodes[right].0;
291        let new_idx = nodes.len();
292        nodes.push((merged_freq, Some(left), Some(right)));
293
294        // Remove left and right, add merged node.
295        active = active[2..].to_vec();
296        active.push(new_idx);
297    }
298
299    // Extract depths from the tree.
300    let mut depths = vec![0u32; n];
301    if let Some(&root) = active.first() {
302        compute_depths(&nodes, root, 0, n, &mut depths);
303    }
304
305    // Limit code lengths to max_length using Kraft inequality.
306    limit_code_lengths(&mut depths, max_length);
307
308    depths
309}
310
311/// Recursively compute depths (code lengths) from a Huffman tree.
312fn compute_depths(
313    nodes: &[(u64, Option<usize>, Option<usize>)],
314    node_idx: usize,
315    depth: u32,
316    num_leaves: usize,
317    depths: &mut [u32],
318) {
319    if node_idx < num_leaves {
320        // Leaf node.
321        depths[node_idx] = depth.max(1);
322        return;
323    }
324    let (_, left, right) = nodes[node_idx];
325    if let Some(l) = left {
326        compute_depths(nodes, l, depth + 1, num_leaves, depths);
327    }
328    if let Some(r) = right {
329        compute_depths(nodes, r, depth + 1, num_leaves, depths);
330    }
331}
332
333/// Limit code lengths to `max_length` while preserving the Kraft inequality.
334///
335/// The Kraft inequality states that for a valid prefix code, the sum of
336/// 2^(-len_i) must equal 1. After clamping, we adjust to restore this.
337fn limit_code_lengths(depths: &mut [u32], max_length: u32) {
338    let mut any_over = false;
339    for d in depths.iter() {
340        if *d > max_length {
341            any_over = true;
342            break;
343        }
344    }
345    if !any_over {
346        return;
347    }
348
349    // Clamp all lengths to max_length.
350    for d in depths.iter_mut() {
351        if *d > max_length {
352            *d = max_length;
353        }
354    }
355
356    // Fix the Kraft inequality iteratively.
357    for _ in 0..1000 {
358        let kraft_sum: u64 = depths
359            .iter()
360            .filter(|&&d| d > 0)
361            .map(|&d| 1u64 << (max_length - d))
362            .sum();
363
364        let target = 1u64 << max_length;
365
366        if kraft_sum == target {
367            break;
368        }
369
370        if kraft_sum > target {
371            // Over-full: increase the length of the shortest code.
372            if let Some(pos) = depths
373                .iter()
374                .enumerate()
375                .filter(|(_, &d)| d > 0 && d < max_length)
376                .min_by_key(|(_, &d)| d)
377                .map(|(i, _)| i)
378            {
379                depths[pos] += 1;
380            } else {
381                break;
382            }
383        } else {
384            // Under-full: decrease the length of a code at max_length.
385            if let Some(pos) = depths
386                .iter()
387                .enumerate()
388                .filter(|(_, &d)| d == max_length)
389                .max_by_key(|(i, _)| *i)
390                .map(|(i, _)| i)
391            {
392                depths[pos] -= 1;
393            } else {
394                break;
395            }
396        }
397    }
398}
399
400/// Assign canonical Huffman codes from code lengths.
401///
402/// Canonical codes are assigned by sorting symbols by (length, symbol)
403/// and assigning incrementing codes within each length group. The codes
404/// are then bit-reversed for VP8L's LSB-first format.
405fn assign_canonical_codes(lengths: &[u32], codes: &mut [HuffmanCode]) {
406    let max_len = lengths.iter().copied().max().unwrap_or(0);
407    if max_len == 0 {
408        return;
409    }
410
411    // Count codes of each length.
412    let mut bl_count = vec![0u32; (max_len + 1) as usize];
413    for &l in lengths {
414        if l > 0 {
415            bl_count[l as usize] += 1;
416        }
417    }
418
419    // Compute the starting code for each length.
420    let mut next_code = vec![0u32; (max_len + 1) as usize];
421    let mut code = 0u32;
422    for bits in 1..=max_len {
423        code = (code + bl_count[(bits - 1) as usize]) << 1;
424        next_code[bits as usize] = code;
425    }
426
427    // Assign codes and reverse bits for LSB-first VP8L encoding.
428    for (sym, &len) in lengths.iter().enumerate() {
429        if len > 0 {
430            let c = next_code[len as usize];
431            next_code[len as usize] += 1;
432            codes[sym] = HuffmanCode {
433                len,
434                code: reverse_bits(c, len),
435            };
436        }
437    }
438}
439
440/// Reverse the lowest `num_bits` bits of `value`.
441fn reverse_bits(value: u32, num_bits: u32) -> u32 {
442    let mut result = 0u32;
443    let mut v = value;
444    for _ in 0..num_bits {
445        result = (result << 1) | (v & 1);
446        v >>= 1;
447    }
448    result
449}
450
451// ---------------------------------------------------------------------------
452// Transform helpers
453// ---------------------------------------------------------------------------
454
455/// Apply subtract-green transform to ARGB pixel data in place.
456///
457/// For each pixel, subtracts the green channel from red and blue:
458///   red   = (red - green) mod 256
459///   blue  = (blue - green) mod 256
460///   green and alpha are unchanged.
461fn apply_subtract_green(pixels: &mut [u32]) {
462    for px in pixels.iter_mut() {
463        let a = (*px >> 24) & 0xff;
464        let r = (*px >> 16) & 0xff;
465        let g = (*px >> 8) & 0xff;
466        let b = *px & 0xff;
467
468        let new_r = r.wrapping_sub(g) & 0xff;
469        let new_b = b.wrapping_sub(g) & 0xff;
470
471        *px = (a << 24) | (new_r << 16) | (g << 8) | new_b;
472    }
473}
474
475/// Apply predictor transform using spatial prediction.
476///
477/// Each pixel is predicted from its neighbors and the residual
478/// (pixel - prediction) mod 256 is stored. This reduces entropy
479/// for natural images with spatial correlation.
480///
481/// Returns the predictor data (mode per tile) for writing to the bitstream.
482fn apply_predictor_transform(pixels: &mut [u32], width: u32, height: u32) -> CodecResult<Vec<u8>> {
483    let w = width as usize;
484    let h = height as usize;
485
486    if pixels.len() != w * h {
487        return Err(CodecError::InvalidParameter(format!(
488            "pixel count {} != width {} * height {}",
489            pixels.len(),
490            w,
491            h
492        )));
493    }
494
495    // Tile size for the predictor data image.
496    // size_bits = 3 means tile_size = 8.
497    let tile_size = 1usize << 3;
498    let tiles_x = (w + tile_size - 1) / tile_size;
499    let tiles_y = (h + tile_size - 1) / tile_size;
500
501    // Use predictor mode 1 (left) everywhere for simplicity.
502    let predictor_mode: u8 = 1;
503    let predictor_data = vec![predictor_mode; tiles_x * tiles_y];
504
505    // Keep original pixels for reading predictions.
506    let original = pixels.to_vec();
507
508    for y in 0..h {
509        for x in 0..w {
510            let idx = y * w + x;
511            let current = original[idx];
512
513            let predicted = if x == 0 && y == 0 {
514                // Top-left corner: predict black (opaque).
515                0xff000000u32
516            } else if x == 0 {
517                // Left edge: predict top pixel.
518                original[(y - 1) * w + x]
519            } else {
520                // Default: predict left pixel.
521                original[y * w + (x - 1)]
522            };
523
524            let ca = (current >> 24) & 0xff;
525            let cr = (current >> 16) & 0xff;
526            let cg = (current >> 8) & 0xff;
527            let cb = current & 0xff;
528
529            let pa = (predicted >> 24) & 0xff;
530            let pr = (predicted >> 16) & 0xff;
531            let pg = (predicted >> 8) & 0xff;
532            let pb = predicted & 0xff;
533
534            let ra = ca.wrapping_sub(pa) & 0xff;
535            let rr = cr.wrapping_sub(pr) & 0xff;
536            let rg = cg.wrapping_sub(pg) & 0xff;
537            let rb = cb.wrapping_sub(pb) & 0xff;
538
539            pixels[idx] = (ra << 24) | (rr << 16) | (rg << 8) | rb;
540        }
541    }
542
543    Ok(predictor_data)
544}
545
546// ---------------------------------------------------------------------------
547// Huffman encoding helpers
548// ---------------------------------------------------------------------------
549
550/// Builds histograms for the 5 VP8L Huffman trees from transformed pixel data.
551fn build_histograms(pixels: &[u32]) -> [Histogram; HUFFMAN_CODES_PER_META_CODE] {
552    let mut histograms = [
553        Histogram::new(GREEN_ALPHABET_SIZE),
554        Histogram::new(CHANNEL_ALPHABET_SIZE),
555        Histogram::new(CHANNEL_ALPHABET_SIZE),
556        Histogram::new(CHANNEL_ALPHABET_SIZE),
557        Histogram::new(DISTANCE_ALPHABET_SIZE),
558    ];
559
560    for &px in pixels {
561        let g = ((px >> 8) & 0xff) as usize;
562        let r = ((px >> 16) & 0xff) as usize;
563        let b = (px & 0xff) as usize;
564        let a = ((px >> 24) & 0xff) as usize;
565
566        histograms[0].add(g);
567        histograms[1].add(r);
568        histograms[2].add(b);
569        histograms[3].add(a);
570        // No distance codes in literal-only mode.
571    }
572
573    histograms
574}
575
576/// Write a single Huffman code to the bitstream.
577fn write_huffman_symbol(writer: &mut Vp8lBitWriter, codes: &[HuffmanCode], symbol: usize) {
578    let hc = if symbol < codes.len() {
579        codes[symbol]
580    } else {
581        HuffmanCode { len: 1, code: 0 }
582    };
583    if hc.len > 0 {
584        writer.put_bits(hc.code, hc.len);
585    }
586}
587
588/// Write Huffman code lengths to the bitstream using the meta-code.
589///
590/// Implements the VP8L code length encoding:
591/// 1. Build a meta-Huffman code for the code lengths themselves
592/// 2. Write the meta-code lengths (num_code_lengths, then lengths in spec order)
593/// 3. Write the actual code lengths using the meta-code
594fn write_huffman_codes(
595    writer: &mut Vp8lBitWriter,
596    codes: &[HuffmanCode],
597    alphabet_size: usize,
598) -> CodecResult<()> {
599    let num_used = codes.iter().filter(|c| c.len > 0).count();
600
601    // Simple code (1 or 2 symbols): use the special simple code format.
602    if num_used <= 2 {
603        return write_simple_code(writer, codes, num_used);
604    }
605
606    // Normal Huffman code: write using code length codes.
607    write_normal_code(writer, codes, alphabet_size)
608}
609
610/// Write a "simple" Huffman code (1 or 2 symbols).
611///
612/// Format:
613///   1 bit: is_simple_code = 1
614///   1 bit: num_symbols - 1 (0 or 1)
615///   If 1 symbol:
616///     1 bit: is_first_8bit (0 = 1-bit symbol value, 1 = 8-bit symbol value)
617///     symbol value (1 or 8 bits)
618///   If 2 symbols:
619///     8 bits: symbol0
620///     8 bits: symbol1
621fn write_simple_code(
622    writer: &mut Vp8lBitWriter,
623    codes: &[HuffmanCode],
624    num_used: usize,
625) -> CodecResult<()> {
626    // is_simple_code = 1
627    writer.put_bit(true);
628
629    let symbols: Vec<usize> = codes
630        .iter()
631        .enumerate()
632        .filter(|(_, c)| c.len > 0)
633        .map(|(i, _)| i)
634        .collect();
635
636    if num_used == 0 {
637        // Degenerate: write single symbol 0.
638        writer.put_bit(false); // num_symbols - 1 = 0
639        writer.put_bit(false); // is_first_8bit = 0
640        writer.put_bits(0, 1);
641        return Ok(());
642    }
643
644    if num_used == 1 {
645        let sym = symbols[0];
646        writer.put_bit(false); // num_symbols - 1 = 0
647        if sym < 2 {
648            writer.put_bit(false); // is_first_8bit = 0
649            writer.put_bits(sym as u32, 1);
650        } else {
651            writer.put_bit(true); // is_first_8bit = 1
652            writer.put_bits(sym as u32, 8);
653        }
654    } else {
655        // 2 symbols.
656        let sym0 = symbols[0];
657        let sym1 = symbols[1];
658        writer.put_bit(true); // num_symbols - 1 = 1
659        writer.put_bits(sym0 as u32, 8);
660        writer.put_bits(sym1 as u32, 8);
661    }
662
663    Ok(())
664}
665
666/// Write a normal (non-simple) Huffman code using code length codes.
667fn write_normal_code(
668    writer: &mut Vp8lBitWriter,
669    codes: &[HuffmanCode],
670    alphabet_size: usize,
671) -> CodecResult<()> {
672    // is_simple_code = 0
673    writer.put_bit(false);
674
675    // Collect code lengths for all symbols up to alphabet_size.
676    let mut code_lengths: Vec<u32> = codes.iter().take(alphabet_size).map(|c| c.len).collect();
677    code_lengths.resize(alphabet_size, 0);
678
679    // Run-length encode the code lengths.
680    let rle_symbols = rle_encode_code_lengths(&code_lengths);
681
682    // Build histogram for the code length symbols (0..18).
683    let mut cl_hist = Histogram::new(NUM_CODE_LENGTH_CODES);
684    for &(sym, _) in &rle_symbols {
685        cl_hist.add(sym as usize);
686    }
687
688    // Build Huffman codes for the code length symbols.
689    let cl_codes = build_huffman_codes(&cl_hist)?;
690
691    // Determine how many code length code lengths to write (in spec order).
692    let mut num_cl_codes = 4usize; // Minimum is 4.
693    for i in (0..NUM_CODE_LENGTH_CODES).rev() {
694        let sym = CODE_LENGTH_CODE_ORDER[i];
695        if sym < cl_codes.len() && cl_codes[sym].len > 0 {
696            num_cl_codes = i + 1;
697            break;
698        }
699    }
700    num_cl_codes = num_cl_codes.max(4);
701
702    // Write num_code_lengths - 4 (4 bits).
703    writer.put_bits((num_cl_codes - 4) as u32, 4);
704
705    // Write code length code lengths in spec order (3 bits each).
706    for i in 0..num_cl_codes {
707        let sym = CODE_LENGTH_CODE_ORDER[i];
708        let len = if sym < cl_codes.len() {
709            cl_codes[sym].len
710        } else {
711            0
712        };
713        writer.put_bits(len, 3);
714    }
715
716    // Write the actual code lengths using the code length codes.
717    for &(sym, extra) in &rle_symbols {
718        write_huffman_symbol(writer, &cl_codes, sym as usize);
719        match sym {
720            16 => {
721                // Repeat previous: 2 extra bits (repeat 3..6).
722                writer.put_bits(extra, 2);
723            }
724            17 => {
725                // Repeat zero: 3 extra bits (repeat 3..10).
726                writer.put_bits(extra, 3);
727            }
728            18 => {
729                // Repeat zero: 7 extra bits (repeat 11..138).
730                writer.put_bits(extra, 7);
731            }
732            _ => {
733                // Literal code length, no extra bits.
734            }
735        }
736    }
737
738    Ok(())
739}
740
741/// Run-length encode code lengths into (symbol, extra_bits) pairs.
742///
743/// Uses symbols 0-15 as literal lengths, and:
744///   16 = repeat previous length, extra = count - 3 (2 bits, range 3..6)
745///   17 = repeat zero 3..10 times, extra = count - 3 (3 bits)
746///   18 = repeat zero 11..138 times, extra = count - 11 (7 bits)
747fn rle_encode_code_lengths(lengths: &[u32]) -> Vec<(u32, u32)> {
748    let mut result = Vec::new();
749    let mut i = 0;
750
751    while i < lengths.len() {
752        let val = lengths[i];
753
754        if val == 0 {
755            // Count consecutive zeros.
756            let mut run = 1;
757            while i + run < lengths.len() && lengths[i + run] == 0 {
758                run += 1;
759            }
760
761            let mut remaining = run;
762            while remaining > 0 {
763                if remaining >= 11 {
764                    let count = remaining.min(138);
765                    result.push((18, (count - 11) as u32));
766                    remaining -= count;
767                } else if remaining >= 3 {
768                    let count = remaining.min(10);
769                    result.push((17, (count - 3) as u32));
770                    remaining -= count;
771                } else {
772                    result.push((0, 0));
773                    remaining -= 1;
774                }
775            }
776
777            i += run;
778        } else {
779            // Emit the literal value.
780            result.push((val, 0));
781            i += 1;
782
783            // Check for repeats of this value.
784            let mut run = 0;
785            while i + run < lengths.len() && lengths[i + run] == val {
786                run += 1;
787            }
788
789            let mut remaining = run;
790            while remaining >= 3 {
791                let count = remaining.min(6);
792                result.push((16, (count - 3) as u32));
793                remaining -= count;
794            }
795            while remaining > 0 {
796                result.push((val, 0));
797                remaining -= 1;
798            }
799
800            i += run;
801        }
802    }
803
804    result
805}
806
807// ---------------------------------------------------------------------------
808// VP8L Encoder
809// ---------------------------------------------------------------------------
810
811/// VP8L (WebP lossless) encoder.
812///
813/// Produces valid VP8L bitstreams from ARGB pixel data. The encoder
814/// supports multiple effort levels that control which transforms are
815/// applied before entropy coding.
816///
817/// # Example
818///
819/// ```ignore
820/// use oximedia_codec::webp::Vp8lEncoder;
821///
822/// let encoder = Vp8lEncoder::new(50);
823/// let pixels: Vec<u32> = vec![0xff_ff_00_00; 64]; // 8x8 red image
824/// let data = encoder.encode(&pixels, 8, 8, false)?;
825/// ```
826pub struct Vp8lEncoder {
827    /// Compression effort level (0-100).
828    /// Higher values enable more transforms and produce smaller output.
829    effort: u8,
830}
831
832impl Vp8lEncoder {
833    /// Creates a new VP8L encoder with the given effort level (0-100).
834    ///
835    /// - effort 0-30: Only subtract-green transform
836    /// - effort 31-100: Subtract-green + predictor transform
837    pub fn new(effort: u8) -> Self {
838        Self {
839            effort: effort.min(100),
840        }
841    }
842
843    /// Encode ARGB pixels to a VP8L bitstream.
844    ///
845    /// # Arguments
846    ///
847    /// * `pixels` - Slice of ARGB u32 values (one per pixel, row-major order).
848    ///   Format: `(alpha << 24) | (red << 16) | (green << 8) | blue`.
849    /// * `width` - Image width in pixels (1..16384).
850    /// * `height` - Image height in pixels (1..16384).
851    /// * `has_alpha` - Whether the alpha channel contains meaningful data.
852    ///
853    /// # Returns
854    ///
855    /// VP8L encoded data starting with the 0x2F signature byte.
856    pub fn encode(
857        &self,
858        pixels: &[u32],
859        width: u32,
860        height: u32,
861        has_alpha: bool,
862    ) -> CodecResult<Vec<u8>> {
863        self.validate_inputs(pixels, width, height)?;
864
865        // Make a mutable copy for transform application.
866        let mut transformed = pixels.to_vec();
867
868        // Decide which transforms to apply.
869        let use_predictor = self.effort > 30;
870
871        // Collect transform metadata.
872        let mut predictor_data: Option<(Vec<u8>, u32, u32, u32)> = None;
873
874        // Apply predictor transform first (if enabled).
875        if use_predictor {
876            let size_bits: u32 = 3;
877            let tile_size = 1u32 << size_bits;
878            let tiles_x = (width + tile_size - 1) / tile_size;
879            let tiles_y = (height + tile_size - 1) / tile_size;
880
881            let pred_data = apply_predictor_transform(&mut transformed, width, height)?;
882            predictor_data = Some((pred_data, size_bits, tiles_x, tiles_y));
883        }
884
885        // Always apply subtract-green transform.
886        apply_subtract_green(&mut transformed);
887
888        // Build the bitstream.
889        let estimated_size = (pixels.len() * 4) + 256;
890        let mut writer = Vp8lBitWriter::with_capacity(estimated_size);
891
892        // Write VP8L header.
893        self.write_header(&mut writer, width, height, has_alpha)?;
894
895        // Write transforms (in reverse application order for LIFO decoding).
896        self.write_transforms(&mut writer, use_predictor, &predictor_data)?;
897
898        // Signal no more transforms.
899        writer.put_bit(false);
900
901        // Encode and write pixel data.
902        self.write_image_data(&mut writer, &transformed)?;
903
904        Ok(writer.finish())
905    }
906
907    /// Validate encoder inputs.
908    fn validate_inputs(&self, pixels: &[u32], width: u32, height: u32) -> CodecResult<()> {
909        if width == 0 || height == 0 {
910            return Err(CodecError::InvalidParameter(
911                "width and height must be > 0".to_string(),
912            ));
913        }
914        if width > VP8L_MAX_DIMENSION || height > VP8L_MAX_DIMENSION {
915            return Err(CodecError::InvalidParameter(format!(
916                "dimensions {}x{} exceed VP8L maximum {}",
917                width, height, VP8L_MAX_DIMENSION
918            )));
919        }
920        let expected = (width as usize) * (height as usize);
921        if pixels.len() != expected {
922            return Err(CodecError::InvalidParameter(format!(
923                "pixel count {} != expected {} ({}x{})",
924                pixels.len(),
925                expected,
926                width,
927                height
928            )));
929        }
930        Ok(())
931    }
932
933    /// Write VP8L bitstream header.
934    ///
935    /// Format:
936    ///   1 byte: signature (0x2F)
937    ///   14 bits: width - 1
938    ///   14 bits: height - 1
939    ///   1 bit: alpha_is_used
940    ///   3 bits: version (always 0)
941    fn write_header(
942        &self,
943        writer: &mut Vp8lBitWriter,
944        width: u32,
945        height: u32,
946        has_alpha: bool,
947    ) -> CodecResult<()> {
948        // Signature byte (written through the bit writer as 8 bits).
949        writer.put_bits(u32::from(VP8L_SIGNATURE), 8);
950
951        // 14 bits: width - 1
952        writer.put_bits(width - 1, 14);
953
954        // 14 bits: height - 1
955        writer.put_bits(height - 1, 14);
956
957        // 1 bit: alpha_is_used
958        writer.put_bit(has_alpha);
959
960        // 3 bits: version (always 0)
961        writer.put_bits(0, 3);
962
963        Ok(())
964    }
965
966    /// Write transform indicators to the bitstream.
967    ///
968    /// Transforms are written in reverse application order (LIFO for decoding).
969    /// Application order: predictor -> subtract-green
970    /// Write order: subtract-green first, then predictor
971    fn write_transforms(
972        &self,
973        writer: &mut Vp8lBitWriter,
974        use_predictor: bool,
975        predictor_data: &Option<(Vec<u8>, u32, u32, u32)>,
976    ) -> CodecResult<()> {
977        // Subtract-green transform (always applied).
978        writer.put_bit(true); // transform present
979        writer.put_bits(TRANSFORM_SUBTRACT_GREEN, 2);
980        // Subtract-green has no extra data.
981
982        // Predictor transform (if enabled).
983        if use_predictor {
984            if let Some((ref pred_data, size_bits, _tiles_x, _tiles_y)) = *predictor_data {
985                writer.put_bit(true); // transform present
986                writer.put_bits(TRANSFORM_PREDICTOR, 2);
987                // 3 bits: size_bits (log2 of tile size minus 2).
988                writer.put_bits(size_bits, 3);
989
990                // Write the predictor data as a sub-image.
991                // Each pixel encodes the predictor mode in the green channel.
992                let pred_pixels: Vec<u32> =
993                    pred_data.iter().map(|&mode| (mode as u32) << 8).collect();
994
995                self.write_image_data(writer, &pred_pixels)?;
996            }
997        }
998
999        Ok(())
1000    }
1001
1002    /// Encode and write pixel data using Huffman coding.
1003    ///
1004    /// This is the core encoding routine that:
1005    /// 1. Writes color cache flag (disabled)
1006    /// 2. Builds histograms for each of the 5 symbol types
1007    /// 3. Constructs optimal Huffman codes
1008    /// 4. Writes code descriptions to the bitstream
1009    /// 5. Writes Huffman-coded pixel data
1010    fn write_image_data(&self, writer: &mut Vp8lBitWriter, pixels: &[u32]) -> CodecResult<()> {
1011        // Color cache: disabled (1 bit = false).
1012        writer.put_bit(false);
1013
1014        // Meta-Huffman (entropy image): not used (1 bit = false).
1015        // The decoder always reads this bit before the 5 Huffman trees.
1016        writer.put_bit(false);
1017
1018        // Build histograms for the 5 Huffman trees.
1019        let histograms = build_histograms(pixels);
1020
1021        // Build Huffman codes for each tree and write their descriptions.
1022        let alphabet_sizes = [
1023            GREEN_ALPHABET_SIZE,
1024            CHANNEL_ALPHABET_SIZE,
1025            CHANNEL_ALPHABET_SIZE,
1026            CHANNEL_ALPHABET_SIZE,
1027            DISTANCE_ALPHABET_SIZE,
1028        ];
1029
1030        let mut all_codes: Vec<Vec<HuffmanCode>> = Vec::with_capacity(HUFFMAN_CODES_PER_META_CODE);
1031
1032        for (i, hist) in histograms.iter().enumerate() {
1033            let codes = build_huffman_codes(hist)?;
1034            write_huffman_codes(writer, &codes, alphabet_sizes[i])?;
1035            all_codes.push(codes);
1036        }
1037
1038        // Write encoded pixel data as Huffman-coded literals.
1039        for &px in pixels {
1040            let g = ((px >> 8) & 0xff) as usize;
1041            let r = ((px >> 16) & 0xff) as usize;
1042            let b = (px & 0xff) as usize;
1043            let a = ((px >> 24) & 0xff) as usize;
1044
1045            write_huffman_symbol(writer, &all_codes[0], g);
1046            write_huffman_symbol(writer, &all_codes[1], r);
1047            write_huffman_symbol(writer, &all_codes[2], b);
1048            write_huffman_symbol(writer, &all_codes[3], a);
1049        }
1050
1051        Ok(())
1052    }
1053}
1054
1055// ---------------------------------------------------------------------------
1056// Tests
1057// ---------------------------------------------------------------------------
1058
1059#[cfg(test)]
1060mod tests {
1061    use super::*;
1062
1063    // -- Bit Writer tests --
1064
1065    #[test]
1066    fn test_bit_writer_basic() {
1067        let mut w = Vp8lBitWriter::new();
1068        w.put_bits(0b101, 3);
1069        w.put_bits(0b1100, 4);
1070        w.put_bit(true);
1071        let data = w.finish();
1072        // LSB-first: bits are 1,0,1, 0,0,1,1, 1
1073        // byte = 0b1_1100_101 = 0xe5
1074        assert_eq!(data.len(), 1);
1075        assert_eq!(data[0], 0xe5);
1076    }
1077
1078    #[test]
1079    fn test_bit_writer_multi_byte() {
1080        let mut w = Vp8lBitWriter::new();
1081        w.put_bits(0xff, 8);
1082        w.put_bits(0x00, 8);
1083        w.put_bits(0xab, 8);
1084        let data = w.finish();
1085        assert_eq!(data, vec![0xff, 0x00, 0xab]);
1086    }
1087
1088    #[test]
1089    fn test_bit_writer_empty() {
1090        let w = Vp8lBitWriter::new();
1091        let data = w.finish();
1092        assert!(data.is_empty());
1093    }
1094
1095    #[test]
1096    fn test_bit_writer_single_bit() {
1097        let mut w = Vp8lBitWriter::new();
1098        w.put_bit(true);
1099        let data = w.finish();
1100        assert_eq!(data, vec![0x01]);
1101    }
1102
1103    #[test]
1104    fn test_bit_writer_byte_len() {
1105        let mut w = Vp8lBitWriter::new();
1106        assert_eq!(w.byte_len(), 0);
1107        w.put_bits(0xff, 8);
1108        assert_eq!(w.byte_len(), 1);
1109        w.put_bit(true);
1110        assert_eq!(w.byte_len(), 2);
1111    }
1112
1113    // -- Reverse bits --
1114
1115    #[test]
1116    fn test_reverse_bits() {
1117        assert_eq!(reverse_bits(0b110, 3), 0b011);
1118        assert_eq!(reverse_bits(0b1010, 4), 0b0101);
1119        assert_eq!(reverse_bits(0b1, 1), 0b1);
1120        assert_eq!(reverse_bits(0b0, 1), 0b0);
1121        assert_eq!(reverse_bits(0b11001, 5), 0b10011);
1122    }
1123
1124    // -- Subtract green transform --
1125
1126    #[test]
1127    fn test_subtract_green() {
1128        // Pixel: A=0xff, R=0x80, G=0x40, B=0xc0
1129        let mut pixels = vec![0xff_80_40_c0u32];
1130        apply_subtract_green(&mut pixels);
1131
1132        let a = (pixels[0] >> 24) & 0xff;
1133        let r = (pixels[0] >> 16) & 0xff;
1134        let g = (pixels[0] >> 8) & 0xff;
1135        let b = pixels[0] & 0xff;
1136
1137        assert_eq!(a, 0xff);
1138        assert_eq!(g, 0x40);
1139        assert_eq!(r, (0x80u32.wrapping_sub(0x40)) & 0xff);
1140        assert_eq!(b, (0xc0u32.wrapping_sub(0x40)) & 0xff);
1141    }
1142
1143    #[test]
1144    fn test_subtract_green_wraparound() {
1145        // R=0x10, G=0x80: R-G = 0x10 - 0x80 = 0x90 (mod 256)
1146        let mut pixels = vec![0xff_10_80_20u32];
1147        apply_subtract_green(&mut pixels);
1148        let r = (pixels[0] >> 16) & 0xff;
1149        assert_eq!(r, 0x90);
1150    }
1151
1152    // -- Histogram --
1153
1154    #[test]
1155    fn test_histogram_basic() {
1156        let mut hist = Histogram::new(256);
1157        hist.add(0);
1158        hist.add(0);
1159        hist.add(1);
1160        hist.add(255);
1161
1162        assert_eq!(hist.num_used(), 3);
1163        assert_eq!(hist.counts[0], 2);
1164        assert_eq!(hist.counts[1], 1);
1165        assert_eq!(hist.counts[255], 1);
1166    }
1167
1168    #[test]
1169    fn test_histogram_single_symbol() {
1170        let mut hist = Histogram::new(256);
1171        hist.add(42);
1172        hist.add(42);
1173        hist.add(42);
1174
1175        assert_eq!(hist.num_used(), 1);
1176        assert_eq!(hist.single_symbol(), Some(42));
1177    }
1178
1179    #[test]
1180    fn test_histogram_out_of_range() {
1181        let mut hist = Histogram::new(10);
1182        hist.add(100); // Out of range, should be ignored.
1183        assert_eq!(hist.num_used(), 0);
1184    }
1185
1186    // -- Huffman code building --
1187
1188    #[test]
1189    fn test_build_huffman_empty() {
1190        let hist = Histogram::new(256);
1191        let codes = build_huffman_codes(&hist).expect("should build");
1192        assert!(codes.iter().all(|c| c.len == 0));
1193    }
1194
1195    #[test]
1196    fn test_build_huffman_single_symbol() {
1197        let mut hist = Histogram::new(256);
1198        hist.add(100);
1199        hist.add(100);
1200
1201        let codes = build_huffman_codes(&hist).expect("should build");
1202        assert_eq!(codes[100].len, 1);
1203        assert_eq!(codes[100].code, 0);
1204    }
1205
1206    #[test]
1207    fn test_build_huffman_two_symbols() {
1208        let mut hist = Histogram::new(256);
1209        hist.add(10);
1210        hist.add(20);
1211
1212        let codes = build_huffman_codes(&hist).expect("should build");
1213        assert_eq!(codes[10].len, 1);
1214        assert_eq!(codes[20].len, 1);
1215        assert_ne!(codes[10].code, codes[20].code);
1216    }
1217
1218    #[test]
1219    fn test_build_huffman_multiple_symbols() {
1220        let mut hist = Histogram::new(256);
1221        for _ in 0..100 {
1222            hist.add(0);
1223        }
1224        for _ in 0..50 {
1225            hist.add(1);
1226        }
1227        for _ in 0..25 {
1228            hist.add(2);
1229        }
1230        for _ in 0..10 {
1231            hist.add(3);
1232        }
1233
1234        let codes = build_huffman_codes(&hist).expect("should build");
1235
1236        // Most frequent symbol should have shortest or equal code length.
1237        assert!(codes[0].len <= codes[3].len);
1238        for i in 0..4 {
1239            assert!(codes[i].len > 0, "symbol {} should have nonzero length", i);
1240            assert!(
1241                codes[i].len <= MAX_HUFFMAN_CODE_LENGTH,
1242                "symbol {} code length {} exceeds max",
1243                i,
1244                codes[i].len
1245            );
1246        }
1247    }
1248
1249    #[test]
1250    fn test_build_huffman_all_equal_frequencies() {
1251        let mut hist = Histogram::new(4);
1252        for i in 0..4 {
1253            hist.add(i);
1254        }
1255
1256        let codes = build_huffman_codes(&hist).expect("should build");
1257        // 4 symbols with equal frequency should get length 2 each.
1258        for i in 0..4 {
1259            assert_eq!(codes[i].len, 2, "symbol {} should have length 2", i);
1260        }
1261    }
1262
1263    #[test]
1264    fn test_canonical_codes_prefix_free() {
1265        let mut hist = Histogram::new(8);
1266        hist.add(0);
1267        hist.add(0);
1268        hist.add(0);
1269        hist.add(0);
1270        hist.add(1);
1271        hist.add(1);
1272        hist.add(2);
1273        hist.add(3);
1274
1275        let codes = build_huffman_codes(&hist).expect("should build");
1276
1277        // Verify prefix-free property: no code is a prefix of another.
1278        let used: Vec<(usize, &HuffmanCode)> = codes
1279            .iter()
1280            .enumerate()
1281            .filter(|(_, c)| c.len > 0)
1282            .collect();
1283
1284        for i in 0..used.len() {
1285            for j in (i + 1)..used.len() {
1286                let (_, ci) = used[i];
1287                let (_, cj) = used[j];
1288                let min_len = ci.len.min(cj.len);
1289                let mask = (1u32 << min_len) - 1;
1290                if ci.len != cj.len {
1291                    assert_ne!(ci.code & mask, cj.code & mask, "codes are not prefix-free");
1292                } else {
1293                    assert_ne!(ci.code, cj.code, "duplicate codes");
1294                }
1295            }
1296        }
1297    }
1298
1299    // -- RLE encoding --
1300
1301    #[test]
1302    fn test_rle_encode_zeros() {
1303        let lengths = vec![0; 20];
1304        let rle = rle_encode_code_lengths(&lengths);
1305        assert_eq!(rle.len(), 1);
1306        assert_eq!(rle[0].0, 18); // repeat zero 11..138
1307        assert_eq!(rle[0].1, (20 - 11) as u32);
1308    }
1309
1310    #[test]
1311    fn test_rle_encode_small_zeros() {
1312        let lengths = vec![0, 0, 0, 0, 0];
1313        let rle = rle_encode_code_lengths(&lengths);
1314        assert_eq!(rle.len(), 1);
1315        assert_eq!(rle[0].0, 17); // repeat zero 3..10
1316        assert_eq!(rle[0].1, (5 - 3) as u32);
1317    }
1318
1319    #[test]
1320    fn test_rle_encode_mixed() {
1321        let lengths = vec![3, 3, 3, 3, 0, 0, 0, 5];
1322        let rle = rle_encode_code_lengths(&lengths);
1323        assert!(rle.len() >= 3);
1324        assert_eq!(rle[0].0, 3); // literal 3
1325        assert_eq!(rle[1].0, 16); // repeat previous
1326    }
1327
1328    #[test]
1329    fn test_rle_encode_single_values() {
1330        let lengths = vec![1, 2, 3, 4, 5];
1331        let rle = rle_encode_code_lengths(&lengths);
1332        // No repeats, all literals.
1333        assert_eq!(rle.len(), 5);
1334        for (i, &(sym, _)) in rle.iter().enumerate() {
1335            assert_eq!(sym, (i + 1) as u32);
1336        }
1337    }
1338
1339    // -- Limit code lengths --
1340
1341    #[test]
1342    fn test_limit_code_lengths() {
1343        let mut depths = vec![1, 2, 16, 16, 16];
1344        limit_code_lengths(&mut depths, 8);
1345        for &d in &depths {
1346            assert!(d <= 8, "depth {} exceeds max 8", d);
1347        }
1348    }
1349
1350    #[test]
1351    fn test_limit_code_lengths_no_change() {
1352        let original = vec![1, 2, 3, 4, 5];
1353        let mut depths = original.clone();
1354        limit_code_lengths(&mut depths, 15);
1355        assert_eq!(depths, original);
1356    }
1357
1358    // -- Header encoding --
1359
1360    #[test]
1361    fn test_header_signature() {
1362        let encoder = Vp8lEncoder::new(0);
1363        let mut writer = Vp8lBitWriter::new();
1364        encoder
1365            .write_header(&mut writer, 100, 200, true)
1366            .expect("header should write");
1367        let data = writer.finish();
1368        assert_eq!(data[0], VP8L_SIGNATURE);
1369    }
1370
1371    #[test]
1372    fn test_header_dimensions_encoded() {
1373        let encoder = Vp8lEncoder::new(0);
1374        let mut writer = Vp8lBitWriter::new();
1375        encoder
1376            .write_header(&mut writer, 100, 200, false)
1377            .expect("write header");
1378        let data = writer.finish();
1379
1380        assert_eq!(data[0], 0x2f);
1381
1382        // Parse the 32-bit value after signature (LSB-first).
1383        let val = (data[1] as u32)
1384            | ((data[2] as u32) << 8)
1385            | ((data[3] as u32) << 16)
1386            | ((data[4] as u32) << 24);
1387
1388        let w_minus_1 = val & 0x3fff;
1389        let h_minus_1 = (val >> 14) & 0x3fff;
1390        let alpha_used = (val >> 28) & 1;
1391        let version = (val >> 29) & 0x7;
1392
1393        assert_eq!(w_minus_1, 99);
1394        assert_eq!(h_minus_1, 199);
1395        assert_eq!(alpha_used, 0);
1396        assert_eq!(version, 0);
1397    }
1398
1399    #[test]
1400    fn test_header_with_alpha() {
1401        let encoder = Vp8lEncoder::new(0);
1402        let mut writer = Vp8lBitWriter::new();
1403        encoder
1404            .write_header(&mut writer, 50, 75, true)
1405            .expect("write header");
1406        let data = writer.finish();
1407
1408        let val = (data[1] as u32)
1409            | ((data[2] as u32) << 8)
1410            | ((data[3] as u32) << 16)
1411            | ((data[4] as u32) << 24);
1412
1413        let alpha_used = (val >> 28) & 1;
1414        assert_eq!(alpha_used, 1);
1415    }
1416
1417    // -- Full encoder tests --
1418
1419    #[test]
1420    fn test_encoder_1x1_image() {
1421        let encoder = Vp8lEncoder::new(0);
1422        let pixels = vec![0xff_80_40_20u32];
1423        let result = encoder.encode(&pixels, 1, 1, true);
1424        assert!(result.is_ok());
1425        let data = result.expect("encode should succeed");
1426        assert_eq!(data[0], VP8L_SIGNATURE);
1427    }
1428
1429    #[test]
1430    fn test_encoder_small_image() {
1431        let encoder = Vp8lEncoder::new(0);
1432        let pixels = vec![0xff_ff_00_00u32; 4]; // 2x2 red
1433        let result = encoder.encode(&pixels, 2, 2, false);
1434        assert!(result.is_ok());
1435        let data = result.expect("encode should succeed");
1436        assert_eq!(data[0], VP8L_SIGNATURE);
1437        assert!(data.len() > 5);
1438    }
1439
1440    #[test]
1441    fn test_encoder_with_alpha() {
1442        let encoder = Vp8lEncoder::new(0);
1443        let pixels = vec![0x80_00_ff_00u32; 16]; // 4x4 semi-transparent green
1444        let result = encoder.encode(&pixels, 4, 4, true);
1445        assert!(result.is_ok());
1446    }
1447
1448    #[test]
1449    fn test_encoder_all_black() {
1450        let encoder = Vp8lEncoder::new(0);
1451        let pixels = vec![0xff_00_00_00u32; 64];
1452        let result = encoder.encode(&pixels, 8, 8, false);
1453        assert!(result.is_ok());
1454    }
1455
1456    #[test]
1457    fn test_encoder_gradient() {
1458        let encoder = Vp8lEncoder::new(0);
1459        let mut pixels = Vec::with_capacity(256);
1460        for i in 0..256u32 {
1461            pixels.push(0xff_00_00_00 | (i << 16) | (i << 8) | i);
1462        }
1463        let result = encoder.encode(&pixels, 16, 16, false);
1464        assert!(result.is_ok());
1465    }
1466
1467    #[test]
1468    fn test_encoder_with_predictor() {
1469        let encoder = Vp8lEncoder::new(50); // effort > 30 enables predictor
1470        let pixels = vec![0xff_ff_00_00u32; 64];
1471        let result = encoder.encode(&pixels, 8, 8, false);
1472        assert!(result.is_ok());
1473    }
1474
1475    #[test]
1476    fn test_encoder_invalid_zero_dimensions() {
1477        let encoder = Vp8lEncoder::new(0);
1478        let pixels = vec![0u32; 4];
1479
1480        assert!(encoder.encode(&pixels, 0, 2, false).is_err());
1481        assert!(encoder.encode(&pixels, 2, 0, false).is_err());
1482    }
1483
1484    #[test]
1485    fn test_encoder_dimension_overflow() {
1486        let encoder = Vp8lEncoder::new(0);
1487        let pixels = vec![0u32; 1];
1488        let result = encoder.encode(&pixels, VP8L_MAX_DIMENSION + 1, 1, false);
1489        assert!(result.is_err());
1490    }
1491
1492    #[test]
1493    fn test_encoder_pixel_count_mismatch() {
1494        let encoder = Vp8lEncoder::new(0);
1495        let pixels = vec![0u32; 10];
1496        let result = encoder.encode(&pixels, 4, 4, false);
1497        assert!(result.is_err());
1498    }
1499
1500    #[test]
1501    fn test_encoder_large_uniform_compresses() {
1502        let encoder = Vp8lEncoder::new(0);
1503        let pixels = vec![0xff_00_80_ff_u32; 256 * 256];
1504        let result = encoder.encode(&pixels, 256, 256, false);
1505        assert!(result.is_ok());
1506        let data = result.expect("encode should succeed");
1507        // Uniform image should compress well below raw size.
1508        assert!(
1509            data.len() < 256 * 256 * 4,
1510            "compressed size {} should be less than raw size {}",
1511            data.len(),
1512            256 * 256 * 4
1513        );
1514    }
1515
1516    #[test]
1517    fn test_effort_affects_output() {
1518        let pixels = vec![0xff_ff_00_00u32; 64];
1519
1520        let low = Vp8lEncoder::new(0);
1521        let high = Vp8lEncoder::new(50);
1522
1523        let data_low = low.encode(&pixels, 8, 8, false).expect("low effort");
1524        let data_high = high.encode(&pixels, 8, 8, false).expect("high effort");
1525
1526        // Different effort levels should produce different bitstreams.
1527        assert_ne!(data_low, data_high);
1528    }
1529
1530    // -- Predictor transform --
1531
1532    #[test]
1533    fn test_predictor_transform_uniform() {
1534        let w = 4u32;
1535        let h = 4u32;
1536        let original = vec![0xff_80_40_20u32; (w * h) as usize];
1537        let mut transformed = original.clone();
1538        let result = apply_predictor_transform(&mut transformed, w, h);
1539        assert!(result.is_ok());
1540
1541        // For identical pixels with left predictor, residual should be 0
1542        // for all pixels after the first.
1543        for i in 1..(w * h) as usize {
1544            let g = (transformed[i] >> 8) & 0xff;
1545            let r = (transformed[i] >> 16) & 0xff;
1546            let b = transformed[i] & 0xff;
1547            assert_eq!(g, 0, "green residual should be 0 for uniform image");
1548            assert_eq!(r, 0, "red residual should be 0 for uniform image");
1549            assert_eq!(b, 0, "blue residual should be 0 for uniform image");
1550        }
1551    }
1552
1553    #[test]
1554    fn test_predictor_transform_dimension_mismatch() {
1555        let mut pixels = vec![0u32; 10];
1556        let result = apply_predictor_transform(&mut pixels, 4, 4);
1557        assert!(result.is_err());
1558    }
1559
1560    // -- Max dimensions --
1561
1562    #[test]
1563    fn test_encoder_max_valid_dimension() {
1564        let encoder = Vp8lEncoder::new(0);
1565        let pixels = vec![0xff_00_00_00u32; 1];
1566        let result = encoder.encode(&pixels, 1, 1, false);
1567        assert!(result.is_ok());
1568    }
1569
1570    #[test]
1571    fn test_encoder_effort_clamped() {
1572        // Effort > 100 should be clamped to 100.
1573        let encoder = Vp8lEncoder::new(255);
1574        let pixels = vec![0xff_ff_ff_ffu32; 4];
1575        let result = encoder.encode(&pixels, 2, 2, true);
1576        assert!(result.is_ok());
1577    }
1578}