use crate::error::{CodecError, CodecResult};
const VP8L_SIGNATURE: u8 = 0x2f;
const VP8L_MAX_DIMENSION: u32 = 16384;
const CODE_LENGTH_CODE_ORDER: [usize; 19] = [
17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
];
const MAX_HUFFMAN_CODE_LENGTH: u32 = 15;
const NUM_CODE_LENGTH_CODES: usize = 19;
const TRANSFORM_PREDICTOR: u32 = 0;
const TRANSFORM_SUBTRACT_GREEN: u32 = 2;
const HUFFMAN_CODES_PER_META_CODE: usize = 5;
const GREEN_ALPHABET_SIZE: usize = 280;
const CHANNEL_ALPHABET_SIZE: usize = 256;
const DISTANCE_ALPHABET_SIZE: usize = 40;
#[derive(Debug)]
pub struct Vp8lBitWriter {
data: Vec<u8>,
bits: u64,
num_bits: u32,
}
impl Vp8lBitWriter {
pub fn new() -> Self {
Self {
data: Vec::with_capacity(4096),
bits: 0,
num_bits: 0,
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
data: Vec::with_capacity(capacity),
bits: 0,
num_bits: 0,
}
}
pub fn put_bits(&mut self, value: u32, n_bits: u32) {
debug_assert!(n_bits <= 32);
debug_assert!(n_bits == 32 || value < (1u32 << n_bits));
self.bits |= (value as u64) << self.num_bits;
self.num_bits += n_bits;
self.flush_partial();
}
pub fn put_bit(&mut self, bit: bool) {
self.put_bits(u32::from(bit), 1);
}
fn flush_partial(&mut self) {
while self.num_bits >= 8 {
self.data.push((self.bits & 0xff) as u8);
self.bits >>= 8;
self.num_bits -= 8;
}
}
pub fn finish(mut self) -> Vec<u8> {
if self.num_bits > 0 {
self.data.push((self.bits & 0xff) as u8);
}
self.data
}
pub fn byte_len(&self) -> usize {
self.data.len() + if self.num_bits > 0 { 1 } else { 0 }
}
}
#[derive(Debug, Clone, Copy, Default)]
struct HuffmanCode {
len: u32,
code: u32,
}
#[derive(Debug, Clone)]
struct Histogram {
counts: Vec<u32>,
}
impl Histogram {
fn new(size: usize) -> Self {
Self {
counts: vec![0; size],
}
}
fn add(&mut self, symbol: usize) {
if symbol < self.counts.len() {
self.counts[symbol] += 1;
}
}
fn alphabet_size(&self) -> usize {
self.counts.len()
}
fn num_used(&self) -> usize {
self.counts.iter().filter(|&&c| c > 0).count()
}
fn single_symbol(&self) -> Option<usize> {
if self.num_used() == 1 {
self.counts.iter().position(|&c| c > 0)
} else {
None
}
}
}
fn build_huffman_codes(hist: &Histogram) -> CodecResult<Vec<HuffmanCode>> {
let n = hist.alphabet_size();
let mut codes = vec![HuffmanCode::default(); n];
let used = hist.num_used();
if used == 0 {
return Ok(codes);
}
if used == 1 {
if let Some(sym) = hist.single_symbol() {
codes[sym] = HuffmanCode { len: 1, code: 0 };
}
return Ok(codes);
}
if used == 2 {
let mut iter = hist
.counts
.iter()
.enumerate()
.filter(|(_, &c)| c > 0)
.map(|(i, _)| i);
if let (Some(a), Some(b)) = (iter.next(), iter.next()) {
codes[a] = HuffmanCode { len: 1, code: 0 };
codes[b] = HuffmanCode { len: 1, code: 1 };
}
return Ok(codes);
}
let code_lengths = build_code_lengths(&hist.counts, MAX_HUFFMAN_CODE_LENGTH)?;
assign_canonical_codes(&code_lengths, &mut codes);
Ok(codes)
}
fn build_code_lengths(counts: &[u32], max_length: u32) -> CodecResult<Vec<u32>> {
let n = counts.len();
let mut lengths = vec![0u32; n];
let mut symbols: Vec<(u32, usize)> = counts
.iter()
.enumerate()
.filter(|(_, &c)| c > 0)
.map(|(i, &c)| (c, i))
.collect();
if symbols.len() < 2 {
for &(_, sym) in &symbols {
lengths[sym] = 1;
}
return Ok(lengths);
}
symbols.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
let tree_lengths = build_tree_lengths(&symbols, max_length);
for (i, &(_, sym)) in symbols.iter().enumerate() {
lengths[sym] = tree_lengths[i];
}
Ok(lengths)
}
fn build_tree_lengths(symbols: &[(u32, usize)], max_length: u32) -> Vec<u32> {
let n = symbols.len();
if n <= 1 {
return vec![1; n];
}
let mut nodes: Vec<(u64, Option<usize>, Option<usize>)> = symbols
.iter()
.map(|&(freq, _)| (freq.max(1) as u64, None, None))
.collect();
let mut active: Vec<usize> = (0..n).collect();
while active.len() > 1 {
active.sort_by(|&a, &b| nodes[a].0.cmp(&nodes[b].0));
let left = active[0];
let right = active[1];
let merged_freq = nodes[left].0 + nodes[right].0;
let new_idx = nodes.len();
nodes.push((merged_freq, Some(left), Some(right)));
active = active[2..].to_vec();
active.push(new_idx);
}
let mut depths = vec![0u32; n];
if let Some(&root) = active.first() {
compute_depths(&nodes, root, 0, n, &mut depths);
}
limit_code_lengths(&mut depths, max_length);
depths
}
fn compute_depths(
nodes: &[(u64, Option<usize>, Option<usize>)],
node_idx: usize,
depth: u32,
num_leaves: usize,
depths: &mut [u32],
) {
if node_idx < num_leaves {
depths[node_idx] = depth.max(1);
return;
}
let (_, left, right) = nodes[node_idx];
if let Some(l) = left {
compute_depths(nodes, l, depth + 1, num_leaves, depths);
}
if let Some(r) = right {
compute_depths(nodes, r, depth + 1, num_leaves, depths);
}
}
fn limit_code_lengths(depths: &mut [u32], max_length: u32) {
let mut any_over = false;
for d in depths.iter() {
if *d > max_length {
any_over = true;
break;
}
}
if !any_over {
return;
}
for d in depths.iter_mut() {
if *d > max_length {
*d = max_length;
}
}
for _ in 0..1000 {
let kraft_sum: u64 = depths
.iter()
.filter(|&&d| d > 0)
.map(|&d| 1u64 << (max_length - d))
.sum();
let target = 1u64 << max_length;
if kraft_sum == target {
break;
}
if kraft_sum > target {
if let Some(pos) = depths
.iter()
.enumerate()
.filter(|(_, &d)| d > 0 && d < max_length)
.min_by_key(|(_, &d)| d)
.map(|(i, _)| i)
{
depths[pos] += 1;
} else {
break;
}
} else {
if let Some(pos) = depths
.iter()
.enumerate()
.filter(|(_, &d)| d == max_length)
.max_by_key(|(i, _)| *i)
.map(|(i, _)| i)
{
depths[pos] -= 1;
} else {
break;
}
}
}
}
fn assign_canonical_codes(lengths: &[u32], codes: &mut [HuffmanCode]) {
let max_len = lengths.iter().copied().max().unwrap_or(0);
if max_len == 0 {
return;
}
let mut bl_count = vec![0u32; (max_len + 1) as usize];
for &l in lengths {
if l > 0 {
bl_count[l as usize] += 1;
}
}
let mut next_code = vec![0u32; (max_len + 1) as usize];
let mut code = 0u32;
for bits in 1..=max_len {
code = (code + bl_count[(bits - 1) as usize]) << 1;
next_code[bits as usize] = code;
}
for (sym, &len) in lengths.iter().enumerate() {
if len > 0 {
let c = next_code[len as usize];
next_code[len as usize] += 1;
codes[sym] = HuffmanCode {
len,
code: reverse_bits(c, len),
};
}
}
}
fn reverse_bits(value: u32, num_bits: u32) -> u32 {
let mut result = 0u32;
let mut v = value;
for _ in 0..num_bits {
result = (result << 1) | (v & 1);
v >>= 1;
}
result
}
fn apply_subtract_green(pixels: &mut [u32]) {
for px in pixels.iter_mut() {
let a = (*px >> 24) & 0xff;
let r = (*px >> 16) & 0xff;
let g = (*px >> 8) & 0xff;
let b = *px & 0xff;
let new_r = r.wrapping_sub(g) & 0xff;
let new_b = b.wrapping_sub(g) & 0xff;
*px = (a << 24) | (new_r << 16) | (g << 8) | new_b;
}
}
fn apply_predictor_transform(pixels: &mut [u32], width: u32, height: u32) -> CodecResult<Vec<u8>> {
let w = width as usize;
let h = height as usize;
if pixels.len() != w * h {
return Err(CodecError::InvalidParameter(format!(
"pixel count {} != width {} * height {}",
pixels.len(),
w,
h
)));
}
let tile_size = 1usize << 3;
let tiles_x = (w + tile_size - 1) / tile_size;
let tiles_y = (h + tile_size - 1) / tile_size;
let predictor_mode: u8 = 1;
let predictor_data = vec![predictor_mode; tiles_x * tiles_y];
let original = pixels.to_vec();
for y in 0..h {
for x in 0..w {
let idx = y * w + x;
let current = original[idx];
let predicted = if x == 0 && y == 0 {
0xff000000u32
} else if x == 0 {
original[(y - 1) * w + x]
} else {
original[y * w + (x - 1)]
};
let ca = (current >> 24) & 0xff;
let cr = (current >> 16) & 0xff;
let cg = (current >> 8) & 0xff;
let cb = current & 0xff;
let pa = (predicted >> 24) & 0xff;
let pr = (predicted >> 16) & 0xff;
let pg = (predicted >> 8) & 0xff;
let pb = predicted & 0xff;
let ra = ca.wrapping_sub(pa) & 0xff;
let rr = cr.wrapping_sub(pr) & 0xff;
let rg = cg.wrapping_sub(pg) & 0xff;
let rb = cb.wrapping_sub(pb) & 0xff;
pixels[idx] = (ra << 24) | (rr << 16) | (rg << 8) | rb;
}
}
Ok(predictor_data)
}
fn build_histograms(pixels: &[u32]) -> [Histogram; HUFFMAN_CODES_PER_META_CODE] {
let mut histograms = [
Histogram::new(GREEN_ALPHABET_SIZE),
Histogram::new(CHANNEL_ALPHABET_SIZE),
Histogram::new(CHANNEL_ALPHABET_SIZE),
Histogram::new(CHANNEL_ALPHABET_SIZE),
Histogram::new(DISTANCE_ALPHABET_SIZE),
];
for &px in pixels {
let g = ((px >> 8) & 0xff) as usize;
let r = ((px >> 16) & 0xff) as usize;
let b = (px & 0xff) as usize;
let a = ((px >> 24) & 0xff) as usize;
histograms[0].add(g);
histograms[1].add(r);
histograms[2].add(b);
histograms[3].add(a);
}
histograms
}
fn write_huffman_symbol(writer: &mut Vp8lBitWriter, codes: &[HuffmanCode], symbol: usize) {
let hc = if symbol < codes.len() {
codes[symbol]
} else {
HuffmanCode { len: 1, code: 0 }
};
if hc.len > 0 {
writer.put_bits(hc.code, hc.len);
}
}
fn write_huffman_codes(
writer: &mut Vp8lBitWriter,
codes: &[HuffmanCode],
alphabet_size: usize,
) -> CodecResult<()> {
let num_used = codes.iter().filter(|c| c.len > 0).count();
if num_used <= 2 {
return write_simple_code(writer, codes, num_used);
}
write_normal_code(writer, codes, alphabet_size)
}
fn write_simple_code(
writer: &mut Vp8lBitWriter,
codes: &[HuffmanCode],
num_used: usize,
) -> CodecResult<()> {
writer.put_bit(true);
let symbols: Vec<usize> = codes
.iter()
.enumerate()
.filter(|(_, c)| c.len > 0)
.map(|(i, _)| i)
.collect();
if num_used == 0 {
writer.put_bit(false); writer.put_bit(false); writer.put_bits(0, 1);
return Ok(());
}
if num_used == 1 {
let sym = symbols[0];
writer.put_bit(false); if sym < 2 {
writer.put_bit(false); writer.put_bits(sym as u32, 1);
} else {
writer.put_bit(true); writer.put_bits(sym as u32, 8);
}
} else {
let sym0 = symbols[0];
let sym1 = symbols[1];
writer.put_bit(true); writer.put_bits(sym0 as u32, 8);
writer.put_bits(sym1 as u32, 8);
}
Ok(())
}
fn write_normal_code(
writer: &mut Vp8lBitWriter,
codes: &[HuffmanCode],
alphabet_size: usize,
) -> CodecResult<()> {
writer.put_bit(false);
let mut code_lengths: Vec<u32> = codes.iter().take(alphabet_size).map(|c| c.len).collect();
code_lengths.resize(alphabet_size, 0);
let rle_symbols = rle_encode_code_lengths(&code_lengths);
let mut cl_hist = Histogram::new(NUM_CODE_LENGTH_CODES);
for &(sym, _) in &rle_symbols {
cl_hist.add(sym as usize);
}
let cl_codes = build_huffman_codes(&cl_hist)?;
let mut num_cl_codes = 4usize; for i in (0..NUM_CODE_LENGTH_CODES).rev() {
let sym = CODE_LENGTH_CODE_ORDER[i];
if sym < cl_codes.len() && cl_codes[sym].len > 0 {
num_cl_codes = i + 1;
break;
}
}
num_cl_codes = num_cl_codes.max(4);
writer.put_bits((num_cl_codes - 4) as u32, 4);
for i in 0..num_cl_codes {
let sym = CODE_LENGTH_CODE_ORDER[i];
let len = if sym < cl_codes.len() {
cl_codes[sym].len
} else {
0
};
writer.put_bits(len, 3);
}
for &(sym, extra) in &rle_symbols {
write_huffman_symbol(writer, &cl_codes, sym as usize);
match sym {
16 => {
writer.put_bits(extra, 2);
}
17 => {
writer.put_bits(extra, 3);
}
18 => {
writer.put_bits(extra, 7);
}
_ => {
}
}
}
Ok(())
}
fn rle_encode_code_lengths(lengths: &[u32]) -> Vec<(u32, u32)> {
let mut result = Vec::new();
let mut i = 0;
while i < lengths.len() {
let val = lengths[i];
if val == 0 {
let mut run = 1;
while i + run < lengths.len() && lengths[i + run] == 0 {
run += 1;
}
let mut remaining = run;
while remaining > 0 {
if remaining >= 11 {
let count = remaining.min(138);
result.push((18, (count - 11) as u32));
remaining -= count;
} else if remaining >= 3 {
let count = remaining.min(10);
result.push((17, (count - 3) as u32));
remaining -= count;
} else {
result.push((0, 0));
remaining -= 1;
}
}
i += run;
} else {
result.push((val, 0));
i += 1;
let mut run = 0;
while i + run < lengths.len() && lengths[i + run] == val {
run += 1;
}
let mut remaining = run;
while remaining >= 3 {
let count = remaining.min(6);
result.push((16, (count - 3) as u32));
remaining -= count;
}
while remaining > 0 {
result.push((val, 0));
remaining -= 1;
}
i += run;
}
}
result
}
pub struct Vp8lEncoder {
effort: u8,
}
impl Vp8lEncoder {
pub fn new(effort: u8) -> Self {
Self {
effort: effort.min(100),
}
}
pub fn encode(
&self,
pixels: &[u32],
width: u32,
height: u32,
has_alpha: bool,
) -> CodecResult<Vec<u8>> {
self.validate_inputs(pixels, width, height)?;
let mut transformed = pixels.to_vec();
let use_predictor = self.effort > 30;
let mut predictor_data: Option<(Vec<u8>, u32, u32, u32)> = None;
if use_predictor {
let size_bits: u32 = 3;
let tile_size = 1u32 << size_bits;
let tiles_x = (width + tile_size - 1) / tile_size;
let tiles_y = (height + tile_size - 1) / tile_size;
let pred_data = apply_predictor_transform(&mut transformed, width, height)?;
predictor_data = Some((pred_data, size_bits, tiles_x, tiles_y));
}
apply_subtract_green(&mut transformed);
let estimated_size = (pixels.len() * 4) + 256;
let mut writer = Vp8lBitWriter::with_capacity(estimated_size);
self.write_header(&mut writer, width, height, has_alpha)?;
self.write_transforms(&mut writer, use_predictor, &predictor_data)?;
writer.put_bit(false);
self.write_image_data(&mut writer, &transformed)?;
Ok(writer.finish())
}
fn validate_inputs(&self, pixels: &[u32], width: u32, height: u32) -> CodecResult<()> {
if width == 0 || height == 0 {
return Err(CodecError::InvalidParameter(
"width and height must be > 0".to_string(),
));
}
if width > VP8L_MAX_DIMENSION || height > VP8L_MAX_DIMENSION {
return Err(CodecError::InvalidParameter(format!(
"dimensions {}x{} exceed VP8L maximum {}",
width, height, VP8L_MAX_DIMENSION
)));
}
let expected = (width as usize) * (height as usize);
if pixels.len() != expected {
return Err(CodecError::InvalidParameter(format!(
"pixel count {} != expected {} ({}x{})",
pixels.len(),
expected,
width,
height
)));
}
Ok(())
}
fn write_header(
&self,
writer: &mut Vp8lBitWriter,
width: u32,
height: u32,
has_alpha: bool,
) -> CodecResult<()> {
writer.put_bits(u32::from(VP8L_SIGNATURE), 8);
writer.put_bits(width - 1, 14);
writer.put_bits(height - 1, 14);
writer.put_bit(has_alpha);
writer.put_bits(0, 3);
Ok(())
}
fn write_transforms(
&self,
writer: &mut Vp8lBitWriter,
use_predictor: bool,
predictor_data: &Option<(Vec<u8>, u32, u32, u32)>,
) -> CodecResult<()> {
writer.put_bit(true); writer.put_bits(TRANSFORM_SUBTRACT_GREEN, 2);
if use_predictor {
if let Some((ref pred_data, size_bits, _tiles_x, _tiles_y)) = *predictor_data {
writer.put_bit(true); writer.put_bits(TRANSFORM_PREDICTOR, 2);
writer.put_bits(size_bits, 3);
let pred_pixels: Vec<u32> =
pred_data.iter().map(|&mode| (mode as u32) << 8).collect();
self.write_image_data(writer, &pred_pixels)?;
}
}
Ok(())
}
fn write_image_data(&self, writer: &mut Vp8lBitWriter, pixels: &[u32]) -> CodecResult<()> {
writer.put_bit(false);
writer.put_bit(false);
let histograms = build_histograms(pixels);
let alphabet_sizes = [
GREEN_ALPHABET_SIZE,
CHANNEL_ALPHABET_SIZE,
CHANNEL_ALPHABET_SIZE,
CHANNEL_ALPHABET_SIZE,
DISTANCE_ALPHABET_SIZE,
];
let mut all_codes: Vec<Vec<HuffmanCode>> = Vec::with_capacity(HUFFMAN_CODES_PER_META_CODE);
for (i, hist) in histograms.iter().enumerate() {
let codes = build_huffman_codes(hist)?;
write_huffman_codes(writer, &codes, alphabet_sizes[i])?;
all_codes.push(codes);
}
for &px in pixels {
let g = ((px >> 8) & 0xff) as usize;
let r = ((px >> 16) & 0xff) as usize;
let b = (px & 0xff) as usize;
let a = ((px >> 24) & 0xff) as usize;
write_huffman_symbol(writer, &all_codes[0], g);
write_huffman_symbol(writer, &all_codes[1], r);
write_huffman_symbol(writer, &all_codes[2], b);
write_huffman_symbol(writer, &all_codes[3], a);
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bit_writer_basic() {
let mut w = Vp8lBitWriter::new();
w.put_bits(0b101, 3);
w.put_bits(0b1100, 4);
w.put_bit(true);
let data = w.finish();
assert_eq!(data.len(), 1);
assert_eq!(data[0], 0xe5);
}
#[test]
fn test_bit_writer_multi_byte() {
let mut w = Vp8lBitWriter::new();
w.put_bits(0xff, 8);
w.put_bits(0x00, 8);
w.put_bits(0xab, 8);
let data = w.finish();
assert_eq!(data, vec![0xff, 0x00, 0xab]);
}
#[test]
fn test_bit_writer_empty() {
let w = Vp8lBitWriter::new();
let data = w.finish();
assert!(data.is_empty());
}
#[test]
fn test_bit_writer_single_bit() {
let mut w = Vp8lBitWriter::new();
w.put_bit(true);
let data = w.finish();
assert_eq!(data, vec![0x01]);
}
#[test]
fn test_bit_writer_byte_len() {
let mut w = Vp8lBitWriter::new();
assert_eq!(w.byte_len(), 0);
w.put_bits(0xff, 8);
assert_eq!(w.byte_len(), 1);
w.put_bit(true);
assert_eq!(w.byte_len(), 2);
}
#[test]
fn test_reverse_bits() {
assert_eq!(reverse_bits(0b110, 3), 0b011);
assert_eq!(reverse_bits(0b1010, 4), 0b0101);
assert_eq!(reverse_bits(0b1, 1), 0b1);
assert_eq!(reverse_bits(0b0, 1), 0b0);
assert_eq!(reverse_bits(0b11001, 5), 0b10011);
}
#[test]
fn test_subtract_green() {
let mut pixels = vec![0xff_80_40_c0u32];
apply_subtract_green(&mut pixels);
let a = (pixels[0] >> 24) & 0xff;
let r = (pixels[0] >> 16) & 0xff;
let g = (pixels[0] >> 8) & 0xff;
let b = pixels[0] & 0xff;
assert_eq!(a, 0xff);
assert_eq!(g, 0x40);
assert_eq!(r, (0x80u32.wrapping_sub(0x40)) & 0xff);
assert_eq!(b, (0xc0u32.wrapping_sub(0x40)) & 0xff);
}
#[test]
fn test_subtract_green_wraparound() {
let mut pixels = vec![0xff_10_80_20u32];
apply_subtract_green(&mut pixels);
let r = (pixels[0] >> 16) & 0xff;
assert_eq!(r, 0x90);
}
#[test]
fn test_histogram_basic() {
let mut hist = Histogram::new(256);
hist.add(0);
hist.add(0);
hist.add(1);
hist.add(255);
assert_eq!(hist.num_used(), 3);
assert_eq!(hist.counts[0], 2);
assert_eq!(hist.counts[1], 1);
assert_eq!(hist.counts[255], 1);
}
#[test]
fn test_histogram_single_symbol() {
let mut hist = Histogram::new(256);
hist.add(42);
hist.add(42);
hist.add(42);
assert_eq!(hist.num_used(), 1);
assert_eq!(hist.single_symbol(), Some(42));
}
#[test]
fn test_histogram_out_of_range() {
let mut hist = Histogram::new(10);
hist.add(100); assert_eq!(hist.num_used(), 0);
}
#[test]
fn test_build_huffman_empty() {
let hist = Histogram::new(256);
let codes = build_huffman_codes(&hist).expect("should build");
assert!(codes.iter().all(|c| c.len == 0));
}
#[test]
fn test_build_huffman_single_symbol() {
let mut hist = Histogram::new(256);
hist.add(100);
hist.add(100);
let codes = build_huffman_codes(&hist).expect("should build");
assert_eq!(codes[100].len, 1);
assert_eq!(codes[100].code, 0);
}
#[test]
fn test_build_huffman_two_symbols() {
let mut hist = Histogram::new(256);
hist.add(10);
hist.add(20);
let codes = build_huffman_codes(&hist).expect("should build");
assert_eq!(codes[10].len, 1);
assert_eq!(codes[20].len, 1);
assert_ne!(codes[10].code, codes[20].code);
}
#[test]
fn test_build_huffman_multiple_symbols() {
let mut hist = Histogram::new(256);
for _ in 0..100 {
hist.add(0);
}
for _ in 0..50 {
hist.add(1);
}
for _ in 0..25 {
hist.add(2);
}
for _ in 0..10 {
hist.add(3);
}
let codes = build_huffman_codes(&hist).expect("should build");
assert!(codes[0].len <= codes[3].len);
for i in 0..4 {
assert!(codes[i].len > 0, "symbol {} should have nonzero length", i);
assert!(
codes[i].len <= MAX_HUFFMAN_CODE_LENGTH,
"symbol {} code length {} exceeds max",
i,
codes[i].len
);
}
}
#[test]
fn test_build_huffman_all_equal_frequencies() {
let mut hist = Histogram::new(4);
for i in 0..4 {
hist.add(i);
}
let codes = build_huffman_codes(&hist).expect("should build");
for i in 0..4 {
assert_eq!(codes[i].len, 2, "symbol {} should have length 2", i);
}
}
#[test]
fn test_canonical_codes_prefix_free() {
let mut hist = Histogram::new(8);
hist.add(0);
hist.add(0);
hist.add(0);
hist.add(0);
hist.add(1);
hist.add(1);
hist.add(2);
hist.add(3);
let codes = build_huffman_codes(&hist).expect("should build");
let used: Vec<(usize, &HuffmanCode)> = codes
.iter()
.enumerate()
.filter(|(_, c)| c.len > 0)
.collect();
for i in 0..used.len() {
for j in (i + 1)..used.len() {
let (_, ci) = used[i];
let (_, cj) = used[j];
let min_len = ci.len.min(cj.len);
let mask = (1u32 << min_len) - 1;
if ci.len != cj.len {
assert_ne!(ci.code & mask, cj.code & mask, "codes are not prefix-free");
} else {
assert_ne!(ci.code, cj.code, "duplicate codes");
}
}
}
}
#[test]
fn test_rle_encode_zeros() {
let lengths = vec![0; 20];
let rle = rle_encode_code_lengths(&lengths);
assert_eq!(rle.len(), 1);
assert_eq!(rle[0].0, 18); assert_eq!(rle[0].1, (20 - 11) as u32);
}
#[test]
fn test_rle_encode_small_zeros() {
let lengths = vec![0, 0, 0, 0, 0];
let rle = rle_encode_code_lengths(&lengths);
assert_eq!(rle.len(), 1);
assert_eq!(rle[0].0, 17); assert_eq!(rle[0].1, (5 - 3) as u32);
}
#[test]
fn test_rle_encode_mixed() {
let lengths = vec![3, 3, 3, 3, 0, 0, 0, 5];
let rle = rle_encode_code_lengths(&lengths);
assert!(rle.len() >= 3);
assert_eq!(rle[0].0, 3); assert_eq!(rle[1].0, 16); }
#[test]
fn test_rle_encode_single_values() {
let lengths = vec![1, 2, 3, 4, 5];
let rle = rle_encode_code_lengths(&lengths);
assert_eq!(rle.len(), 5);
for (i, &(sym, _)) in rle.iter().enumerate() {
assert_eq!(sym, (i + 1) as u32);
}
}
#[test]
fn test_limit_code_lengths() {
let mut depths = vec![1, 2, 16, 16, 16];
limit_code_lengths(&mut depths, 8);
for &d in &depths {
assert!(d <= 8, "depth {} exceeds max 8", d);
}
}
#[test]
fn test_limit_code_lengths_no_change() {
let original = vec![1, 2, 3, 4, 5];
let mut depths = original.clone();
limit_code_lengths(&mut depths, 15);
assert_eq!(depths, original);
}
#[test]
fn test_header_signature() {
let encoder = Vp8lEncoder::new(0);
let mut writer = Vp8lBitWriter::new();
encoder
.write_header(&mut writer, 100, 200, true)
.expect("header should write");
let data = writer.finish();
assert_eq!(data[0], VP8L_SIGNATURE);
}
#[test]
fn test_header_dimensions_encoded() {
let encoder = Vp8lEncoder::new(0);
let mut writer = Vp8lBitWriter::new();
encoder
.write_header(&mut writer, 100, 200, false)
.expect("write header");
let data = writer.finish();
assert_eq!(data[0], 0x2f);
let val = (data[1] as u32)
| ((data[2] as u32) << 8)
| ((data[3] as u32) << 16)
| ((data[4] as u32) << 24);
let w_minus_1 = val & 0x3fff;
let h_minus_1 = (val >> 14) & 0x3fff;
let alpha_used = (val >> 28) & 1;
let version = (val >> 29) & 0x7;
assert_eq!(w_minus_1, 99);
assert_eq!(h_minus_1, 199);
assert_eq!(alpha_used, 0);
assert_eq!(version, 0);
}
#[test]
fn test_header_with_alpha() {
let encoder = Vp8lEncoder::new(0);
let mut writer = Vp8lBitWriter::new();
encoder
.write_header(&mut writer, 50, 75, true)
.expect("write header");
let data = writer.finish();
let val = (data[1] as u32)
| ((data[2] as u32) << 8)
| ((data[3] as u32) << 16)
| ((data[4] as u32) << 24);
let alpha_used = (val >> 28) & 1;
assert_eq!(alpha_used, 1);
}
#[test]
fn test_encoder_1x1_image() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0xff_80_40_20u32];
let result = encoder.encode(&pixels, 1, 1, true);
assert!(result.is_ok());
let data = result.expect("encode should succeed");
assert_eq!(data[0], VP8L_SIGNATURE);
}
#[test]
fn test_encoder_small_image() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0xff_ff_00_00u32; 4]; let result = encoder.encode(&pixels, 2, 2, false);
assert!(result.is_ok());
let data = result.expect("encode should succeed");
assert_eq!(data[0], VP8L_SIGNATURE);
assert!(data.len() > 5);
}
#[test]
fn test_encoder_with_alpha() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0x80_00_ff_00u32; 16]; let result = encoder.encode(&pixels, 4, 4, true);
assert!(result.is_ok());
}
#[test]
fn test_encoder_all_black() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0xff_00_00_00u32; 64];
let result = encoder.encode(&pixels, 8, 8, false);
assert!(result.is_ok());
}
#[test]
fn test_encoder_gradient() {
let encoder = Vp8lEncoder::new(0);
let mut pixels = Vec::with_capacity(256);
for i in 0..256u32 {
pixels.push(0xff_00_00_00 | (i << 16) | (i << 8) | i);
}
let result = encoder.encode(&pixels, 16, 16, false);
assert!(result.is_ok());
}
#[test]
fn test_encoder_with_predictor() {
let encoder = Vp8lEncoder::new(50); let pixels = vec![0xff_ff_00_00u32; 64];
let result = encoder.encode(&pixels, 8, 8, false);
assert!(result.is_ok());
}
#[test]
fn test_encoder_invalid_zero_dimensions() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0u32; 4];
assert!(encoder.encode(&pixels, 0, 2, false).is_err());
assert!(encoder.encode(&pixels, 2, 0, false).is_err());
}
#[test]
fn test_encoder_dimension_overflow() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0u32; 1];
let result = encoder.encode(&pixels, VP8L_MAX_DIMENSION + 1, 1, false);
assert!(result.is_err());
}
#[test]
fn test_encoder_pixel_count_mismatch() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0u32; 10];
let result = encoder.encode(&pixels, 4, 4, false);
assert!(result.is_err());
}
#[test]
fn test_encoder_large_uniform_compresses() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0xff_00_80_ff_u32; 256 * 256];
let result = encoder.encode(&pixels, 256, 256, false);
assert!(result.is_ok());
let data = result.expect("encode should succeed");
assert!(
data.len() < 256 * 256 * 4,
"compressed size {} should be less than raw size {}",
data.len(),
256 * 256 * 4
);
}
#[test]
fn test_effort_affects_output() {
let pixels = vec![0xff_ff_00_00u32; 64];
let low = Vp8lEncoder::new(0);
let high = Vp8lEncoder::new(50);
let data_low = low.encode(&pixels, 8, 8, false).expect("low effort");
let data_high = high.encode(&pixels, 8, 8, false).expect("high effort");
assert_ne!(data_low, data_high);
}
#[test]
fn test_predictor_transform_uniform() {
let w = 4u32;
let h = 4u32;
let original = vec![0xff_80_40_20u32; (w * h) as usize];
let mut transformed = original.clone();
let result = apply_predictor_transform(&mut transformed, w, h);
assert!(result.is_ok());
for i in 1..(w * h) as usize {
let g = (transformed[i] >> 8) & 0xff;
let r = (transformed[i] >> 16) & 0xff;
let b = transformed[i] & 0xff;
assert_eq!(g, 0, "green residual should be 0 for uniform image");
assert_eq!(r, 0, "red residual should be 0 for uniform image");
assert_eq!(b, 0, "blue residual should be 0 for uniform image");
}
}
#[test]
fn test_predictor_transform_dimension_mismatch() {
let mut pixels = vec![0u32; 10];
let result = apply_predictor_transform(&mut pixels, 4, 4);
assert!(result.is_err());
}
#[test]
fn test_encoder_max_valid_dimension() {
let encoder = Vp8lEncoder::new(0);
let pixels = vec![0xff_00_00_00u32; 1];
let result = encoder.encode(&pixels, 1, 1, false);
assert!(result.is_ok());
}
#[test]
fn test_encoder_effort_clamped() {
let encoder = Vp8lEncoder::new(255);
let pixels = vec![0xff_ff_ff_ffu32; 4];
let result = encoder.encode(&pixels, 2, 2, true);
assert!(result.is_ok());
}
}