#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_wrap)]
use crate::error::{CodecError, CodecResult};
#[rustfmt::skip]
static DEFAULT_COEFF_PROBS: [[[[u8; 11]; 3]; 8]; 4] = [
[
[[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128],
[128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
[[253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128],
[189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128],
[106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128]],
[[ 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128],
[181, 133, 238, 254, 211, 236, 255, 255, 128, 128, 128],
[ 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128]],
[[ 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128],
[184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128],
[ 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128]],
[[ 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128],
[170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128],
[ 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128]],
[[ 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128],
[207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128],
[102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128]],
[[ 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128],
[177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128],
[ 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128]],
[[ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
],
[
[[198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62],
[131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1],
[ 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128]],
[[ 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128],
[184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128],
[ 81, 99, 181, 242, 195, 203, 255, 219, 128, 128, 128]],
[[ 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128],
[132, 109, 223, 253, 214, 175, 255, 236, 128, 128, 128],
[ 68, 104, 184, 246, 171, 175, 255, 236, 128, 128, 128]],
[[ 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128],
[195, 148, 244, 255, 236, 203, 128, 128, 128, 128, 128],
[ 39, 130, 228, 255, 223, 255, 128, 128, 128, 128, 128]],
[[ 1, 107, 238, 254, 198, 218, 255, 191, 128, 128, 128],
[188, 133, 238, 253, 233, 181, 128, 128, 128, 128, 128],
[ 36, 142, 199, 247, 175, 230, 255, 255, 128, 128, 128]],
[[ 1, 238, 251, 255, 210, 128, 128, 128, 128, 128, 128],
[190, 171, 253, 255, 249, 128, 128, 128, 128, 128, 128],
[ 61, 104, 231, 255, 235, 128, 128, 128, 128, 128, 128]],
[[ 1, 210, 247, 255, 255, 128, 128, 128, 128, 128, 128],
[164, 154, 246, 255, 249, 128, 128, 128, 128, 128, 128],
[ 29, 145, 228, 255, 220, 128, 128, 128, 128, 128, 128]],
[[ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[218, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
],
[
[[ 1, 108, 226, 255, 227, 187, 128, 128, 128, 128, 128],
[117, 109, 203, 246, 197, 174, 255, 255, 128, 128, 128],
[ 15, 66, 128, 224, 149, 147, 255, 255, 128, 128, 128]],
[[ 1, 59, 220, 255, 205, 206, 128, 128, 128, 128, 128],
[138, 40, 218, 255, 237, 219, 255, 255, 128, 128, 128],
[ 31, 27, 156, 248, 188, 175, 255, 255, 128, 128, 128]],
[[ 1, 112, 230, 250, 199, 191, 255, 255, 128, 128, 128],
[116, 109, 225, 252, 198, 190, 255, 255, 128, 128, 128],
[ 41, 82, 163, 237, 156, 172, 255, 255, 128, 128, 128]],
[[ 1, 74, 254, 255, 227, 128, 128, 128, 128, 128, 128],
[150, 101, 247, 255, 222, 128, 128, 128, 128, 128, 128],
[ 57, 56, 231, 255, 243, 128, 128, 128, 128, 128, 128]],
[[ 1, 179, 255, 255, 128, 128, 128, 128, 128, 128, 128],
[176, 134, 243, 255, 228, 128, 128, 128, 128, 128, 128],
[ 80, 84, 234, 255, 210, 128, 128, 128, 128, 128, 128]],
[[ 1, 253, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[185, 205, 255, 255, 128, 128, 128, 128, 128, 128, 128],
[141, 124, 248, 255, 128, 128, 128, 128, 128, 128, 128]],
[[ 1, 254, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[187, 252, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[175, 138, 254, 254, 128, 128, 128, 128, 128, 128, 128]],
[[ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[239, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
],
[
[[ 1, 202, 254, 255, 245, 255, 128, 128, 128, 128, 128],
[248, 136, 248, 254, 227, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
[[ 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128],
[184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128],
[ 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128]],
[[ 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128],
[170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128],
[ 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128]],
[[ 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128],
[207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128],
[102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128]],
[[ 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128],
[177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128],
[ 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128]],
[[ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
[[ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
[[ 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128],
[255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128]],
],
];
#[rustfmt::skip]
static DC_QUANT_TABLE: [i32; 128] = [
4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17,
18, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28,
29, 30, 31, 32, 33, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 43,
44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58,
59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89,
91, 93, 95, 96, 98, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118,
122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 143, 145, 148, 151, 154, 157,
];
#[rustfmt::skip]
static AC_QUANT_TABLE: [i32; 128] = [
4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76,
78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108,
110, 112, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152,
155, 158, 161, 164, 167, 170, 173, 177, 181, 185, 189, 193, 197, 201, 205, 209,
213, 217, 221, 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284,
];
static ZIGZAG_ORDER: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15];
static COEFF_BANDS: [usize; 16] = [0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7];
struct BoolEncoder {
output: Vec<u8>,
range: u32,
bottom: u64,
bits_left: i32,
}
impl BoolEncoder {
fn new() -> Self {
Self {
output: Vec::new(),
range: 255,
bottom: 0,
bits_left: 24,
}
}
fn encode_bool(&mut self, value: bool, prob: u8) {
let split = 1 + (((self.range - 1) * u32::from(prob)) >> 8);
if value {
self.bottom += u64::from(split);
self.range -= split;
} else {
self.range = split;
}
let mut shift = 0u32;
while self.range < 128 {
self.range <<= 1;
shift += 1;
}
self.bottom <<= shift;
self.bits_left -= shift as i32;
if self.bits_left <= 0 {
self.flush_bits();
}
}
fn encode_bit(&mut self, value: bool) {
self.encode_bool(value, 128);
}
fn encode_literal(&mut self, value: u32, n: u8) {
for i in (0..n).rev() {
let bit = (value >> i) & 1 != 0;
self.encode_bit(bit);
}
}
fn encode_literal_with_prob(&mut self, value: u32, n: u8, prob: u8) {
for i in (0..n).rev() {
let bit = (value >> i) & 1 != 0;
self.encode_bool(bit, prob);
}
}
fn flush_bits(&mut self) {
while self.bits_left <= 0 {
let byte = (self.bottom >> 24) as u8;
self.output.push(byte);
self.bottom = (self.bottom & 0x00FF_FFFF) << 8;
self.bits_left += 8;
}
}
fn flush(mut self) -> Vec<u8> {
for _ in 0..4 {
let byte = (self.bottom >> 24) as u8;
self.output.push(byte);
self.bottom <<= 8;
}
self.output
}
}
fn fdct4_1d(input: &[i32; 4], output: &mut [i32; 4]) {
let a0 = input[0] + input[3];
let a1 = input[1] + input[2];
let a2 = input[1] - input[2];
let a3 = input[0] - input[3];
output[0] = a0 + a1;
output[2] = a0 - a1;
output[1] = (a2 * 5352 + a3 * 2217 + 14500) >> 12;
output[3] = (a3 * 5352 - a2 * 2217 + 7500) >> 12;
}
fn fdct4x4(residual: &[i32; 16], coeffs: &mut [i32; 16]) {
let mut temp = [0i32; 16];
for row in 0..4 {
let base = row * 4;
let input = [
residual[base],
residual[base + 1],
residual[base + 2],
residual[base + 3],
];
let mut out = [0i32; 4];
fdct4_1d(&input, &mut out);
temp[base] = out[0];
temp[base + 1] = out[1];
temp[base + 2] = out[2];
temp[base + 3] = out[3];
}
for col in 0..4 {
let input = [temp[col], temp[col + 4], temp[col + 8], temp[col + 12]];
let mut out = [0i32; 4];
fdct4_1d(&input, &mut out);
coeffs[col] = (out[0] + 1) >> 1;
coeffs[col + 4] = (out[1] + 1) >> 1;
coeffs[col + 8] = (out[2] + 1) >> 1;
coeffs[col + 12] = (out[3] + 1) >> 1;
}
}
fn fwht4x4(dc_values: &[i32; 16], coeffs: &mut [i32; 16]) {
let mut temp = [0i32; 16];
for row in 0..4 {
let base = row * 4;
let a = dc_values[base] + dc_values[base + 3];
let b = dc_values[base + 1] + dc_values[base + 2];
let c = dc_values[base + 1] - dc_values[base + 2];
let d = dc_values[base] - dc_values[base + 3];
temp[base] = a + b;
temp[base + 1] = d + c;
temp[base + 2] = a - b;
temp[base + 3] = d - c;
}
for col in 0..4 {
let a = temp[col] + temp[col + 12];
let b = temp[col + 4] + temp[col + 8];
let c = temp[col + 4] - temp[col + 8];
let d = temp[col] - temp[col + 12];
coeffs[col] = a + b;
coeffs[col + 4] = d + c;
coeffs[col + 8] = a - b;
coeffs[col + 12] = d - c;
}
}
struct YuvPlanes {
y: Vec<u8>,
u: Vec<u8>,
v: Vec<u8>,
y_stride: usize,
uv_stride: usize,
width: u32,
height: u32,
}
fn rgb_to_yuv420(data: &[u8], width: u32, height: u32) -> CodecResult<YuvPlanes> {
let w = width as usize;
let h = height as usize;
if data.len() < w * h * 3 {
return Err(CodecError::InvalidParameter(format!(
"RGB data too short: need {}, have {}",
w * h * 3,
data.len()
)));
}
let mb_w = ((w + 15) / 16) * 16;
let mb_h = ((h + 15) / 16) * 16;
let y_stride = mb_w;
let uv_stride = mb_w / 2;
let mut y_plane = vec![0u8; y_stride * mb_h];
let mut u_plane = vec![128u8; uv_stride * (mb_h / 2)];
let mut v_plane = vec![128u8; uv_stride * (mb_h / 2)];
for row in 0..h {
for col in 0..w {
let idx = (row * w + col) * 3;
let r = f64::from(data[idx]);
let g = f64::from(data[idx + 1]);
let b = f64::from(data[idx + 2]);
let y_val = 0.299 * r + 0.587 * g + 0.114 * b;
y_plane[row * y_stride + col] = y_val.clamp(0.0, 255.0) as u8;
}
}
let ch_w = (w + 1) / 2;
let ch_h = (h + 1) / 2;
for row in 0..ch_h {
for col in 0..ch_w {
let mut sum_u = 0.0f64;
let mut sum_v = 0.0f64;
let mut count = 0.0f64;
for dy in 0..2 {
for dx in 0..2 {
let sy = row * 2 + dy;
let sx = col * 2 + dx;
if sy < h && sx < w {
let idx = (sy * w + sx) * 3;
let r = f64::from(data[idx]);
let g = f64::from(data[idx + 1]);
let b = f64::from(data[idx + 2]);
sum_u += -0.169 * r - 0.331 * g + 0.500 * b + 128.0;
sum_v += 0.500 * r - 0.419 * g - 0.081 * b + 128.0;
count += 1.0;
}
}
}
let u_val = (sum_u / count).clamp(0.0, 255.0) as u8;
let v_val = (sum_v / count).clamp(0.0, 255.0) as u8;
u_plane[row * uv_stride + col] = u_val;
v_plane[row * uv_stride + col] = v_val;
}
}
for row in 0..h {
for col in w..mb_w {
y_plane[row * y_stride + col] = y_plane[row * y_stride + w.saturating_sub(1)];
}
}
for row in h..mb_h {
let src_row = h.saturating_sub(1);
for col in 0..mb_w {
y_plane[row * y_stride + col] = y_plane[src_row * y_stride + col.min(mb_w - 1)];
}
}
for row in 0..ch_h {
for col in ch_w..(mb_w / 2) {
u_plane[row * uv_stride + col] = u_plane[row * uv_stride + ch_w.saturating_sub(1)];
v_plane[row * uv_stride + col] = v_plane[row * uv_stride + ch_w.saturating_sub(1)];
}
}
for row in ch_h..(mb_h / 2) {
let src_row = ch_h.saturating_sub(1);
for col in 0..(mb_w / 2) {
u_plane[row * uv_stride + col] = u_plane[src_row * uv_stride + col];
v_plane[row * uv_stride + col] = v_plane[src_row * uv_stride + col];
}
}
Ok(YuvPlanes {
y: y_plane,
u: u_plane,
v: v_plane,
y_stride,
uv_stride,
width,
height,
})
}
static CAT1_PROB: [u8; 1] = [159];
static CAT2_PROB: [u8; 2] = [165, 145];
static CAT3_PROB: [u8; 3] = [173, 148, 140];
static CAT4_PROB: [u8; 4] = [176, 155, 140, 135];
static CAT5_PROB: [u8; 5] = [180, 157, 141, 134, 130];
static CAT6_PROB: [u8; 11] = [254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129];
fn encode_token(
enc: &mut BoolEncoder,
coeff: i32,
block_type: usize,
band: usize,
ctx: usize,
is_first_after_dc: bool,
) -> usize {
let probs = &DEFAULT_COEFF_PROBS[block_type][band][ctx];
let abs_val = coeff.unsigned_abs();
if !is_first_after_dc {
}
if abs_val == 0 {
enc.encode_bool(false, probs[0]); return 0;
}
enc.encode_bool(true, probs[0]);
if abs_val == 1 {
enc.encode_bool(false, probs[1]);
enc.encode_bit(coeff < 0);
return 1;
}
enc.encode_bool(true, probs[1]);
if abs_val <= 4 {
enc.encode_bool(false, probs[2]);
if abs_val == 2 {
enc.encode_bool(false, probs[3]);
} else {
enc.encode_bool(true, probs[3]);
enc.encode_bool(abs_val == 4, probs[4]);
}
enc.encode_bit(coeff < 0);
return 2;
}
enc.encode_bool(true, probs[2]);
if abs_val <= 10 {
enc.encode_bool(false, probs[5]);
if abs_val <= 6 {
enc.encode_bool(false, probs[6]);
let extra = abs_val - 5;
enc.encode_bool(extra != 0, CAT1_PROB[0]);
} else {
enc.encode_bool(true, probs[6]);
let extra = abs_val - 7;
for (i, &p) in CAT2_PROB.iter().enumerate() {
let bit = (extra >> (CAT2_PROB.len() - 1 - i)) & 1 != 0;
enc.encode_bool(bit, p);
}
}
} else {
enc.encode_bool(true, probs[5]);
if abs_val <= 34 {
enc.encode_bool(false, probs[7]);
if abs_val <= 18 {
enc.encode_bool(false, probs[8]);
let extra = abs_val - 11;
for (i, &p) in CAT3_PROB.iter().enumerate() {
let bit = (extra >> (CAT3_PROB.len() - 1 - i)) & 1 != 0;
enc.encode_bool(bit, p);
}
} else {
enc.encode_bool(true, probs[8]);
let extra = abs_val - 19;
for (i, &p) in CAT4_PROB.iter().enumerate() {
let bit = (extra >> (CAT4_PROB.len() - 1 - i)) & 1 != 0;
enc.encode_bool(bit, p);
}
}
} else {
enc.encode_bool(true, probs[7]);
if abs_val <= 66 {
enc.encode_bool(false, probs[9]);
let extra = abs_val - 35;
for (i, &p) in CAT5_PROB.iter().enumerate() {
let bit = (extra >> (CAT5_PROB.len() - 1 - i)) & 1 != 0;
enc.encode_bool(bit, p);
}
} else {
enc.encode_bool(true, probs[9]);
let extra = abs_val - 67;
for (i, &p) in CAT6_PROB.iter().enumerate() {
let bit = (extra >> (CAT6_PROB.len() - 1 - i)) & 1 != 0;
enc.encode_bool(bit, p);
}
}
}
}
enc.encode_bit(coeff < 0);
2
}
fn encode_block(
enc: &mut BoolEncoder,
quantized: &[i32; 16],
block_type: usize,
first_coeff_idx: usize,
) {
let mut last_nonzero: Option<usize> = None;
for i in (first_coeff_idx..16).rev() {
let zigzag_pos = ZIGZAG_ORDER[i];
if quantized[zigzag_pos] != 0 {
last_nonzero = Some(i);
break;
}
}
let last_nz = match last_nonzero {
Some(idx) => idx,
None => {
let band = COEFF_BANDS[first_coeff_idx];
let probs = &DEFAULT_COEFF_PROBS[block_type][band][0];
enc.encode_bool(false, probs[0]); return;
}
};
let mut ctx: usize = 0;
for i in first_coeff_idx..=last_nz {
let zigzag_pos = ZIGZAG_ORDER[i];
let coeff = quantized[zigzag_pos];
let band = COEFF_BANDS[i];
ctx = encode_token(enc, coeff, block_type, band, ctx, i > first_coeff_idx);
}
if last_nz + 1 < 16 {
let eob_band = COEFF_BANDS[(last_nz + 1).min(15)];
let eob_probs = &DEFAULT_COEFF_PROBS[block_type][eob_band][ctx];
enc.encode_bool(false, eob_probs[0]);
}
}
fn quantize(coeff: i32, step: i32) -> i32 {
if step == 0 {
return coeff;
}
let sign = if coeff < 0 { -1 } else { 1 };
let abs_c = coeff.abs();
sign * ((abs_c + step / 2) / step)
}
struct MacroblockCoeffs {
y_blocks: [[i32; 16]; 16],
y2_block: [i32; 16],
u_blocks: [[i32; 16]; 4],
v_blocks: [[i32; 16]; 4],
}
fn encode_macroblock(
yuv: &YuvPlanes,
mb_x: usize,
mb_y: usize,
dc_quant: i32,
ac_quant: i32,
y2_dc_quant: i32,
y2_ac_quant: i32,
uv_dc_quant: i32,
uv_ac_quant: i32,
reconstructed_y: &[u8],
recon_y_stride: usize,
reconstructed_u: &[u8],
recon_uv_stride: usize,
reconstructed_v: &[u8],
) -> MacroblockCoeffs {
let mut mb = MacroblockCoeffs {
y_blocks: [[0i32; 16]; 16],
y2_block: [0i32; 16],
u_blocks: [[0i32; 16]; 4],
v_blocks: [[0i32; 16]; 4],
};
let pred_y = compute_dc_pred_16x16(reconstructed_y, recon_y_stride, mb_x, mb_y);
let mut dc_values = [0i32; 16];
for sb in 0..16 {
let sb_row = sb / 4;
let sb_col = sb % 4;
let mut residual = [0i32; 16];
for r in 0..4 {
for c in 0..4 {
let py = mb_y * 16 + sb_row * 4 + r;
let px = mb_x * 16 + sb_col * 4 + c;
let orig = i32::from(yuv.y[py * yuv.y_stride + px]);
let pred = i32::from(pred_y);
residual[r * 4 + c] = orig - pred;
}
}
let mut coeffs = [0i32; 16];
fdct4x4(&residual, &mut coeffs);
dc_values[sb] = coeffs[0];
for i in 1..16 {
coeffs[i] = quantize(coeffs[i], ac_quant);
}
coeffs[0] = 0;
mb.y_blocks[sb] = coeffs;
}
let mut y2_coeffs = [0i32; 16];
fwht4x4(&dc_values, &mut y2_coeffs);
mb.y2_block[0] = quantize(y2_coeffs[0], y2_dc_quant);
for i in 1..16 {
mb.y2_block[i] = quantize(y2_coeffs[i], y2_ac_quant);
}
let pred_u = compute_dc_pred_8x8(reconstructed_u, recon_uv_stride, mb_x, mb_y);
let pred_v = compute_dc_pred_8x8(reconstructed_v, recon_uv_stride, mb_x, mb_y);
for sb in 0..4 {
let sb_row = sb / 2;
let sb_col = sb % 2;
let mut u_residual = [0i32; 16];
for r in 0..4 {
for c in 0..4 {
let py = mb_y * 8 + sb_row * 4 + r;
let px = mb_x * 8 + sb_col * 4 + c;
let orig = i32::from(yuv.u[py * yuv.uv_stride + px]);
u_residual[r * 4 + c] = orig - i32::from(pred_u);
}
}
let mut u_coeffs = [0i32; 16];
fdct4x4(&u_residual, &mut u_coeffs);
u_coeffs[0] = quantize(u_coeffs[0], uv_dc_quant);
for i in 1..16 {
u_coeffs[i] = quantize(u_coeffs[i], uv_ac_quant);
}
mb.u_blocks[sb] = u_coeffs;
let mut v_residual = [0i32; 16];
for r in 0..4 {
for c in 0..4 {
let py = mb_y * 8 + sb_row * 4 + r;
let px = mb_x * 8 + sb_col * 4 + c;
let orig = i32::from(yuv.v[py * yuv.uv_stride + px]);
v_residual[r * 4 + c] = orig - i32::from(pred_v);
}
}
let mut v_coeffs = [0i32; 16];
fdct4x4(&v_residual, &mut v_coeffs);
v_coeffs[0] = quantize(v_coeffs[0], uv_dc_quant);
for i in 1..16 {
v_coeffs[i] = quantize(v_coeffs[i], uv_ac_quant);
}
mb.v_blocks[sb] = v_coeffs;
}
mb
}
fn compute_dc_pred_16x16(recon: &[u8], stride: usize, mb_x: usize, mb_y: usize) -> u8 {
let mut sum: u32 = 0;
let mut count: u32 = 0;
if mb_y > 0 {
let top_row = (mb_y * 16 - 1) * stride + mb_x * 16;
for col in 0..16 {
if top_row + col < recon.len() {
sum += u32::from(recon[top_row + col]);
count += 1;
}
}
}
if mb_x > 0 {
let left_col = mb_x * 16 - 1;
for row in 0..16 {
let idx = (mb_y * 16 + row) * stride + left_col;
if idx < recon.len() {
sum += u32::from(recon[idx]);
count += 1;
}
}
}
(sum + count / 2).checked_div(count).unwrap_or(128) as u8
}
fn compute_dc_pred_8x8(recon: &[u8], stride: usize, mb_x: usize, mb_y: usize) -> u8 {
let mut sum: u32 = 0;
let mut count: u32 = 0;
if mb_y > 0 {
let top_row = (mb_y * 8 - 1) * stride + mb_x * 8;
for col in 0..8 {
if top_row + col < recon.len() {
sum += u32::from(recon[top_row + col]);
count += 1;
}
}
}
if mb_x > 0 {
let left_col = mb_x * 8 - 1;
for row in 0..8 {
let idx = (mb_y * 8 + row) * stride + left_col;
if idx < recon.len() {
sum += u32::from(recon[idx]);
count += 1;
}
}
}
(sum + count / 2).checked_div(count).unwrap_or(128) as u8
}
fn reconstruct_macroblock(
mb: &MacroblockCoeffs,
dc_quant: i32,
ac_quant: i32,
y2_dc_quant: i32,
y2_ac_quant: i32,
uv_dc_quant: i32,
uv_ac_quant: i32,
pred_y: u8,
pred_u: u8,
pred_v: u8,
recon_y: &mut [u8],
recon_y_stride: usize,
mb_x: usize,
mb_y: usize,
recon_u: &mut [u8],
recon_uv_stride: usize,
recon_v: &mut [u8],
) {
let mut y2_dequant = [0i32; 16];
y2_dequant[0] = mb.y2_block[0] * y2_dc_quant;
for i in 1..16 {
y2_dequant[i] = mb.y2_block[i] * y2_ac_quant;
}
let mut dc_values = [0i32; 16];
{
let mut temp = [0i32; 16];
for row in 0..4 {
let b = row * 4;
let a = y2_dequant[b] + y2_dequant[b + 2];
let bv = y2_dequant[b + 1] + y2_dequant[b + 3];
let c = y2_dequant[b + 1] - y2_dequant[b + 3];
let d = y2_dequant[b] - y2_dequant[b + 2];
temp[b] = a + bv;
temp[b + 1] = d + c;
temp[b + 2] = a - bv;
temp[b + 3] = d - c;
}
for col in 0..4 {
let a = temp[col] + temp[col + 8];
let bv = temp[col + 4] + temp[col + 12];
let c = temp[col + 4] - temp[col + 12];
let d = temp[col] - temp[col + 8];
dc_values[col] = (a + bv + 1) >> 1;
dc_values[col + 4] = (d + c + 1) >> 1;
dc_values[col + 8] = (a - bv + 1) >> 1;
dc_values[col + 12] = (d - c + 1) >> 1;
}
}
for sb in 0..16 {
let sb_row = sb / 4;
let sb_col = sb % 4;
let mut dequant = [0i32; 16];
dequant[0] = dc_values[sb]; for i in 1..16 {
dequant[i] = mb.y_blocks[sb][i] * ac_quant;
}
let reconstructed = idct4x4_simple(&dequant);
for r in 0..4 {
for c in 0..4 {
let py = mb_y * 16 + sb_row * 4 + r;
let px = mb_x * 16 + sb_col * 4 + c;
let val = reconstructed[r * 4 + c] + i32::from(pred_y);
recon_y[py * recon_y_stride + px] = val.clamp(0, 255) as u8;
}
}
}
for sb in 0..4 {
let sb_row = sb / 2;
let sb_col = sb % 2;
let mut u_dequant = [0i32; 16];
u_dequant[0] = mb.u_blocks[sb][0] * uv_dc_quant;
for i in 1..16 {
u_dequant[i] = mb.u_blocks[sb][i] * uv_ac_quant;
}
let u_recon = idct4x4_simple(&u_dequant);
for r in 0..4 {
for c in 0..4 {
let py = mb_y * 8 + sb_row * 4 + r;
let px = mb_x * 8 + sb_col * 4 + c;
let val = u_recon[r * 4 + c] + i32::from(pred_u);
recon_u[py * recon_uv_stride + px] = val.clamp(0, 255) as u8;
}
}
let mut v_dequant = [0i32; 16];
v_dequant[0] = mb.v_blocks[sb][0] * uv_dc_quant;
for i in 1..16 {
v_dequant[i] = mb.v_blocks[sb][i] * uv_ac_quant;
}
let v_recon = idct4x4_simple(&v_dequant);
for r in 0..4 {
for c in 0..4 {
let py = mb_y * 8 + sb_row * 4 + r;
let px = mb_x * 8 + sb_col * 4 + c;
let val = v_recon[r * 4 + c] + i32::from(pred_v);
recon_v[py * recon_uv_stride + px] = val.clamp(0, 255) as u8;
}
}
}
}
fn idct4x4_simple(coeffs: &[i32; 16]) -> [i32; 16] {
let mut temp = [0i32; 16];
let mut output = [0i32; 16];
for row in 0..4 {
let b = row * 4;
let c0 = coeffs[b];
let c1 = coeffs[b + 1];
let c2 = coeffs[b + 2];
let c3 = coeffs[b + 3];
let a1 = c0 + c2;
let b1 = c0 - c2;
let t1 = (c1 * 35468 + c3 * 85627 + 32768) >> 16;
let t2 = (c1 * 85627 - c3 * 35468 + 32768) >> 16;
temp[b] = a1 + t2;
temp[b + 1] = b1 + t1;
temp[b + 2] = b1 - t1;
temp[b + 3] = a1 - t2;
}
for col in 0..4 {
let c0 = temp[col];
let c1 = temp[col + 4];
let c2 = temp[col + 8];
let c3 = temp[col + 12];
let a1 = c0 + c2;
let b1 = c0 - c2;
let t1 = (c1 * 35468 + c3 * 85627 + 32768) >> 16;
let t2 = (c1 * 85627 - c3 * 35468 + 32768) >> 16;
output[col] = (a1 + t2 + 4) >> 3;
output[col + 4] = (b1 + t1 + 4) >> 3;
output[col + 8] = (b1 - t1 + 4) >> 3;
output[col + 12] = (a1 - t2 + 4) >> 3;
}
output
}
fn write_frame_header(enc: &mut BoolEncoder, mb_width: u32, mb_height: u32, quant_index: u8) {
enc.encode_bit(false);
enc.encode_bit(false);
enc.encode_bit(false);
enc.encode_bit(false);
enc.encode_literal(0, 6);
enc.encode_literal(0, 3);
enc.encode_bit(false);
enc.encode_literal(0, 2);
enc.encode_literal(u32::from(quant_index), 7);
enc.encode_bit(false);
enc.encode_bit(false);
enc.encode_bit(false);
enc.encode_bit(false);
enc.encode_bit(false);
for _block_type in 0..4 {
for _band in 0..8 {
for _ctx in 0..3 {
for _node in 0..11 {
enc.encode_bit(false); }
}
}
}
enc.encode_bit(false);
let total_mbs = mb_width * mb_height;
for _ in 0..total_mbs {
enc.encode_bool(false, 145);
enc.encode_bool(false, 142); }
}
pub struct WebPLossyEncoder {
quality: u8,
}
impl WebPLossyEncoder {
#[must_use]
pub fn new(quality: u8) -> Self {
Self {
quality: quality.min(100),
}
}
fn quality_to_qindex(&self) -> u8 {
let qindex = 127 - (u32::from(self.quality) * 127 / 100);
(qindex as u8).min(127)
}
pub fn encode_rgb(&self, data: &[u8], width: u32, height: u32) -> CodecResult<Vec<u8>> {
self.validate_dimensions(width, height)?;
let expected_len = (width as usize) * (height as usize) * 3;
if data.len() < expected_len {
return Err(CodecError::InvalidParameter(format!(
"RGB data too short: expected {expected_len}, got {}",
data.len()
)));
}
let yuv = rgb_to_yuv420(data, width, height)?;
self.encode_yuv(&yuv)
}
pub fn encode_rgba(
&self,
data: &[u8],
width: u32,
height: u32,
) -> CodecResult<(Vec<u8>, Vec<u8>)> {
self.validate_dimensions(width, height)?;
let w = width as usize;
let h = height as usize;
let expected_len = w * h * 4;
if data.len() < expected_len {
return Err(CodecError::InvalidParameter(format!(
"RGBA data too short: expected {expected_len}, got {}",
data.len()
)));
}
let pixel_count = w * h;
let mut rgb = Vec::with_capacity(pixel_count * 3);
let mut alpha = Vec::with_capacity(pixel_count);
for i in 0..pixel_count {
let base = i * 4;
rgb.push(data[base]);
rgb.push(data[base + 1]);
rgb.push(data[base + 2]);
alpha.push(data[base + 3]);
}
let vp8_data = self.encode_rgb(&rgb, width, height)?;
Ok((vp8_data, alpha))
}
fn validate_dimensions(&self, width: u32, height: u32) -> CodecResult<()> {
if width == 0 || height == 0 {
return Err(CodecError::InvalidParameter(
"Width and height must be non-zero".to_string(),
));
}
if width > 16383 || height > 16383 {
return Err(CodecError::InvalidParameter(format!(
"Dimensions {}x{} exceed VP8 maximum of 16383",
width, height
)));
}
Ok(())
}
fn encode_yuv(&self, yuv: &YuvPlanes) -> CodecResult<Vec<u8>> {
let width = yuv.width;
let height = yuv.height;
let mb_width = ((width + 15) / 16) as usize;
let mb_height = ((height + 15) / 16) as usize;
let qindex = self.quality_to_qindex();
let qi = qindex as usize;
let dc_quant = DC_QUANT_TABLE[qi.min(127)];
let ac_quant = AC_QUANT_TABLE[qi.min(127)];
let y2_dc_quant = DC_QUANT_TABLE[qi.min(127)] * 2;
let y2_ac_quant = AC_QUANT_TABLE[qi.min(127)].max(8) * 155 / 100;
let uv_dc_quant = DC_QUANT_TABLE[qi.min(127)];
let uv_ac_quant = AC_QUANT_TABLE[qi.min(127)];
let recon_y_stride = mb_width * 16;
let recon_uv_stride = mb_width * 8;
let mut recon_y = vec![128u8; recon_y_stride * mb_height * 16];
let mut recon_u = vec![128u8; recon_uv_stride * mb_height * 8];
let mut recon_v = vec![128u8; recon_uv_stride * mb_height * 8];
let mut header_enc = BoolEncoder::new();
write_frame_header(&mut header_enc, mb_width as u32, mb_height as u32, qindex);
let mut token_enc = BoolEncoder::new();
for mby in 0..mb_height {
for mbx in 0..mb_width {
let mb = encode_macroblock(
yuv,
mbx,
mby,
dc_quant,
ac_quant,
y2_dc_quant,
y2_ac_quant,
uv_dc_quant,
uv_ac_quant,
&recon_y,
recon_y_stride,
&recon_u,
recon_uv_stride,
&recon_v,
);
encode_block(&mut token_enc, &mb.y2_block, 3, 0);
for sb in 0..16 {
encode_block(&mut token_enc, &mb.y_blocks[sb], 0, 1);
}
for sb in 0..4 {
encode_block(&mut token_enc, &mb.u_blocks[sb], 2, 0);
}
for sb in 0..4 {
encode_block(&mut token_enc, &mb.v_blocks[sb], 2, 0);
}
let pred_y = compute_dc_pred_16x16(&recon_y, recon_y_stride, mbx, mby);
let pred_u = compute_dc_pred_8x8(&recon_u, recon_uv_stride, mbx, mby);
let pred_v = compute_dc_pred_8x8(&recon_v, recon_uv_stride, mbx, mby);
reconstruct_macroblock(
&mb,
dc_quant,
ac_quant,
y2_dc_quant,
y2_ac_quant,
uv_dc_quant,
uv_ac_quant,
pred_y,
pred_u,
pred_v,
&mut recon_y,
recon_y_stride,
mbx,
mby,
&mut recon_u,
recon_uv_stride,
&mut recon_v,
);
}
}
let header_data = header_enc.flush();
let token_data = token_enc.flush();
self.assemble_bitstream(width, height, &header_data, &token_data)
}
fn assemble_bitstream(
&self,
width: u32,
height: u32,
header_data: &[u8],
token_data: &[u8],
) -> CodecResult<Vec<u8>> {
let first_partition_size = header_data.len() as u32;
let total_size = 3 + 3 + 4 + header_data.len() + token_data.len();
let mut output = Vec::with_capacity(total_size);
let b0: u8 = 0x00 | 0x00 | 0x10 | ((first_partition_size << 5) as u8 & 0xE0);
let b1: u8 = (first_partition_size >> 3) as u8;
let b2: u8 = (first_partition_size >> 11) as u8;
output.push(b0);
output.push(b1);
output.push(b2);
output.push(0x9D);
output.push(0x01);
output.push(0x2A);
let w_le = (width & 0x3FFF) as u16;
output.push(w_le as u8);
output.push((w_le >> 8) as u8);
let h_le = (height & 0x3FFF) as u16;
output.push(h_le as u8);
output.push((h_le >> 8) as u8);
output.extend_from_slice(header_data);
output.extend_from_slice(token_data);
Ok(output)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bool_encoder_basic() {
let mut enc = BoolEncoder::new();
enc.encode_bit(false);
enc.encode_bit(true);
enc.encode_bit(false);
let data = enc.flush();
assert!(!data.is_empty());
}
#[test]
fn test_bool_encoder_literal() {
let mut enc = BoolEncoder::new();
enc.encode_literal(42, 8);
let data = enc.flush();
assert!(!data.is_empty());
}
#[test]
fn test_bool_encoder_with_prob() {
let mut enc = BoolEncoder::new();
for prob in [1, 50, 128, 200, 255] {
enc.encode_bool(true, prob);
enc.encode_bool(false, prob);
}
let data = enc.flush();
assert!(!data.is_empty());
}
#[test]
fn test_fdct4_1d() {
let input = [100, 100, 100, 100]; let mut output = [0i32; 4];
fdct4_1d(&input, &mut output);
assert!(output[0] > 0);
assert!(output[0].abs() > output[1].abs());
assert!(output[0].abs() > output[2].abs());
assert!(output[0].abs() > output[3].abs());
}
#[test]
fn test_fdct4x4_dc_only() {
let residual = [10i32; 16]; let mut coeffs = [0i32; 16];
fdct4x4(&residual, &mut coeffs);
assert!(coeffs[0].abs() > 0);
let dc_abs = coeffs[0].abs();
for i in 1..16 {
assert!(
coeffs[i].abs() < dc_abs / 2,
"AC coeff[{i}] = {} should be much smaller than DC = {}",
coeffs[i],
coeffs[0]
);
}
}
#[test]
fn test_fwht4x4_dc_only() {
let dc_values = [100i32; 16]; let mut coeffs = [0i32; 16];
fwht4x4(&dc_values, &mut coeffs);
assert_eq!(coeffs[0], 1600);
for i in 1..16 {
assert_eq!(coeffs[i], 0);
}
}
#[test]
fn test_quantize() {
assert_eq!(quantize(100, 10), 10);
assert_eq!(quantize(-100, 10), -10);
assert_eq!(quantize(0, 10), 0);
assert_eq!(quantize(4, 10), 0); assert_eq!(quantize(15, 10), 2); }
#[test]
fn test_quality_to_qindex() {
let enc_low = WebPLossyEncoder::new(0);
let enc_mid = WebPLossyEncoder::new(50);
let enc_high = WebPLossyEncoder::new(100);
assert_eq!(enc_high.quality_to_qindex(), 0);
assert_eq!(enc_low.quality_to_qindex(), 127);
assert!(enc_mid.quality_to_qindex() > 0);
assert!(enc_mid.quality_to_qindex() < 127);
}
#[test]
fn test_rgb_to_yuv420_basic() {
let data = vec![255u8; 4 * 4 * 3];
let yuv = rgb_to_yuv420(&data, 4, 4).expect("conversion should succeed");
for &y in &yuv.y[..16] {
assert!(y >= 250, "Y should be near 255 for white, got {y}");
}
}
#[test]
fn test_rgb_to_yuv420_black() {
let data = vec![0u8; 4 * 4 * 3];
let yuv = rgb_to_yuv420(&data, 4, 4).expect("conversion should succeed");
for &y in &yuv.y[..16] {
assert!(y <= 5, "Y should be near 0 for black, got {y}");
}
for &u in &yuv.u[..4] {
assert!(
(120..=136).contains(&u),
"U should be near 128 for black, got {u}"
);
}
}
#[test]
fn test_rgb_to_yuv420_short_data() {
let data = vec![0u8; 10]; assert!(rgb_to_yuv420(&data, 4, 4).is_err());
}
#[test]
fn test_encode_rgb_produces_valid_frame_tag() {
let encoder = WebPLossyEncoder::new(50);
let data = vec![128u8; 16 * 16 * 3];
let vp8 = encoder
.encode_rgb(&data, 16, 16)
.expect("encode should succeed");
assert!(vp8.len() >= 10);
assert_eq!(vp8[3], 0x9D);
assert_eq!(vp8[4], 0x01);
assert_eq!(vp8[5], 0x2A);
assert_eq!(vp8[0] & 0x01, 0, "Should be keyframe");
assert_ne!(vp8[0] & 0x10, 0, "show_frame should be set");
let w = u16::from(vp8[6]) | (u16::from(vp8[7]) << 8);
let h = u16::from(vp8[8]) | (u16::from(vp8[9]) << 8);
assert_eq!(w & 0x3FFF, 16);
assert_eq!(h & 0x3FFF, 16);
}
#[test]
fn test_encode_rgb_different_qualities() {
let data = vec![100u8; 32 * 32 * 3];
let low = WebPLossyEncoder::new(10);
let high = WebPLossyEncoder::new(90);
let low_data = low.encode_rgb(&data, 32, 32).expect("low quality encode");
let high_data = high.encode_rgb(&data, 32, 32).expect("high quality encode");
assert!(!low_data.is_empty());
assert!(!high_data.is_empty());
}
#[test]
fn test_encode_rgb_non_mb_aligned() {
let encoder = WebPLossyEncoder::new(75);
let data = vec![200u8; 7 * 5 * 3];
let vp8 = encoder
.encode_rgb(&data, 7, 5)
.expect("non-aligned encode should succeed");
assert!(!vp8.is_empty());
let w = u16::from(vp8[6]) | (u16::from(vp8[7]) << 8);
let h = u16::from(vp8[8]) | (u16::from(vp8[9]) << 8);
assert_eq!(w & 0x3FFF, 7);
assert_eq!(h & 0x3FFF, 5);
}
#[test]
fn test_encode_rgba_basic() {
let encoder = WebPLossyEncoder::new(75);
let mut rgba = Vec::with_capacity(4 * 4 * 4);
for _ in 0..16 {
rgba.extend_from_slice(&[255, 0, 0, 128]);
}
let (vp8_data, alpha_data) = encoder
.encode_rgba(&rgba, 4, 4)
.expect("RGBA encode should succeed");
assert!(!vp8_data.is_empty());
assert_eq!(alpha_data.len(), 16);
assert!(alpha_data.iter().all(|&a| a == 128));
}
#[test]
fn test_encode_zero_dimensions() {
let encoder = WebPLossyEncoder::new(50);
assert!(encoder.encode_rgb(&[], 0, 10).is_err());
assert!(encoder.encode_rgb(&[], 10, 0).is_err());
}
#[test]
fn test_encode_too_short_data() {
let encoder = WebPLossyEncoder::new(50);
let data = vec![0u8; 10];
assert!(encoder.encode_rgb(&data, 16, 16).is_err());
}
#[test]
fn test_encode_oversized_dimensions() {
let encoder = WebPLossyEncoder::new(50);
assert!(encoder.encode_rgb(&[], 20000, 100).is_err());
}
#[test]
fn test_idct4x4_simple_dc() {
let mut coeffs = [0i32; 16];
coeffs[0] = 400;
let output = idct4x4_simple(&coeffs);
let avg = output.iter().sum::<i32>() / 16;
for &v in &output {
assert!(
(v - avg).abs() <= 2,
"DC-only IDCT should produce roughly uniform output"
);
}
}
#[test]
fn test_encode_rgb_1x1() {
let encoder = WebPLossyEncoder::new(75);
let data = [128, 128, 128]; let vp8 = encoder
.encode_rgb(&data, 1, 1)
.expect("1x1 encode should succeed");
assert!(!vp8.is_empty());
assert_eq!(vp8[0] & 0x01, 0);
}
#[test]
fn test_encode_quality_extremes() {
let data = vec![128u8; 16 * 16 * 3];
let enc0 = WebPLossyEncoder::new(0);
let out0 = enc0.encode_rgb(&data, 16, 16).expect("q0");
assert!(!out0.is_empty());
let enc100 = WebPLossyEncoder::new(100);
let out100 = enc100.encode_rgb(&data, 16, 16).expect("q100");
assert!(!out100.is_empty());
let enc200 = WebPLossyEncoder::new(200);
assert_eq!(enc200.quality, 100);
}
#[test]
fn test_encode_rgb_colored_image() {
let width = 32u32;
let height = 32u32;
let mut data = Vec::with_capacity((width * height * 3) as usize);
for y in 0..height {
for x in 0..width {
data.push((x * 8) as u8); data.push((y * 8) as u8); data.push(128); }
}
let encoder = WebPLossyEncoder::new(80);
let vp8 = encoder
.encode_rgb(&data, width, height)
.expect("gradient encode should succeed");
assert!(vp8.len() > 10);
assert_eq!(vp8[3], 0x9D);
assert_eq!(vp8[4], 0x01);
assert_eq!(vp8[5], 0x2A);
}
#[test]
fn test_first_partition_size_encoding() {
let encoder = WebPLossyEncoder::new(50);
let data = vec![128u8; 16 * 16 * 3];
let vp8 = encoder.encode_rgb(&data, 16, 16).expect("encode");
let b0 = vp8[0];
let b1 = vp8[1];
let b2 = vp8[2];
let fps = (u32::from(b0 >> 5) & 0x07) | (u32::from(b1) << 3) | (u32::from(b2) << 11);
assert!(fps > 0, "first_partition_size should be non-zero");
assert!(
(fps as usize) < vp8.len(),
"first_partition_size ({fps}) should be less than total ({}) ",
vp8.len()
);
}
#[test]
fn test_compute_dc_pred_16x16_no_neighbors() {
let recon = vec![0u8; 16 * 16];
let pred = compute_dc_pred_16x16(&recon, 16, 0, 0);
assert_eq!(pred, 128); }
#[test]
fn test_compute_dc_pred_16x16_with_top() {
let stride = 32;
let mut recon = vec![0u8; stride * 32];
for col in 0..16 {
recon[15 * stride + col] = 200;
}
let pred = compute_dc_pred_16x16(&recon, stride, 0, 1);
assert_eq!(pred, 200);
}
#[test]
fn test_fwht_iwht_roundtrip() {
let uniform = [50i32; 16];
let mut wht_coeffs = [0i32; 16];
fwht4x4(&uniform, &mut wht_coeffs);
assert_eq!(wht_coeffs[0], 800);
for i in 1..16 {
assert_eq!(wht_coeffs[i], 0, "AC coeff at index {i} should be 0");
}
let varied = [
10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160,
];
fwht4x4(&varied, &mut wht_coeffs);
let total: i32 = varied.iter().sum();
assert_eq!(wht_coeffs[0], total);
let nonzero_ac = wht_coeffs[1..].iter().filter(|&&c| c != 0).count();
assert!(nonzero_ac > 0, "Non-uniform input should have non-zero AC");
}
}