#![allow(dead_code)]
use super::coeffs::{decode_coeffs, encode_coeffs, CoeffCdfContext};
use super::ec::{BoolDecoder, BoolEncoder};
use super::partition::{extract_block, place_block};
use super::predict::{predict_4x4, IntraMode};
use super::quant::{ac_q, dc_q, dequantize_coeff, quantize_coeff};
use super::transform::{fdct4x4, fwht4x4, idct4x4, iwht4x4};
pub fn encode_block(
enc: &mut BoolEncoder,
ctx: &mut CoeffCdfContext,
plane: &mut [u8],
stride: usize,
bx: usize,
by: usize,
w: usize,
h: usize,
base_q_idx: u8,
) {
let block = extract_block(plane, stride, bx, by, w, h);
let (top, left) = context_refs(plane, stride, bx, by, w, h);
let pred = predict_4x4(
IntraMode::DcPred,
top.as_deref(),
left.as_deref(),
);
let mut residual = [0i32; 16];
for i in 0..16 {
residual[i] = block[i] as i32 - pred[i] as i32;
}
let coeffs = if base_q_idx == 0 {
fwht4x4(&residual)
} else {
fdct4x4(&residual)
};
let q_dc = dc_q(base_q_idx);
let q_ac = ac_q(base_q_idx);
let mut qcoeffs = [0i32; 16];
qcoeffs[0] = quantize_coeff(coeffs[0], q_dc);
for i in 1..16 {
qcoeffs[i] = quantize_coeff(coeffs[i], q_ac);
}
encode_coeffs(enc, ctx, &qcoeffs);
let recon = reconstruct_block(&qcoeffs, &pred, base_q_idx);
place_block(plane, stride, bx, by, w, h, &recon);
}
pub fn decode_block(
dec: &mut BoolDecoder<'_>,
ctx: &mut CoeffCdfContext,
plane: &mut [u8],
stride: usize,
bx: usize,
by: usize,
w: usize,
h: usize,
base_q_idx: u8,
) {
let (top, left) = context_refs(plane, stride, bx, by, w, h);
let qcoeffs = decode_coeffs(dec, ctx);
let pred = predict_4x4(
IntraMode::DcPred,
top.as_deref(),
left.as_deref(),
);
let recon = reconstruct_block(&qcoeffs, &pred, base_q_idx);
place_block(plane, stride, bx, by, w, h, &recon);
}
fn reconstruct_block(qcoeffs: &[i32; 16], pred: &[u8; 16], base_q_idx: u8) -> [u8; 16] {
let q_dc = dc_q(base_q_idx);
let q_ac = ac_q(base_q_idx);
let mut coeffs = [0i32; 16];
coeffs[0] = dequantize_coeff(qcoeffs[0], q_dc);
for i in 1..16 {
coeffs[i] = dequantize_coeff(qcoeffs[i], q_ac);
}
let residual = if base_q_idx == 0 {
iwht4x4(&coeffs)
} else {
idct4x4(&coeffs)
};
let mut block = [0u8; 16];
for i in 0..16 {
block[i] = (pred[i] as i32 + residual[i]).clamp(0, 255) as u8;
}
block
}
fn context_refs(
plane: &[u8],
stride: usize,
bx: usize,
by: usize,
w: usize,
h: usize,
) -> (Option<Vec<u8>>, Option<Vec<u8>>) {
let top: Option<Vec<u8>> = if by > 0 {
Some(
(0..4)
.map(|c| {
let px = bx + c;
if px < w {
plane[(by - 1) * stride + px]
} else {
0
}
})
.collect(),
)
} else {
None
};
let left: Option<Vec<u8>> = if bx > 0 {
Some(
(0..4)
.map(|r| {
let py = by + r;
if py < h {
plane[py * stride + (bx - 1)]
} else {
0
}
})
.collect(),
)
} else {
None
};
(top, left)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::av1::ec::{BoolDecoder, BoolEncoder};
fn encode_decode_plane_block(plane: &[u8], w: usize, h: usize, base_q_idx: u8) -> Vec<u8> {
let mut plane_enc = plane.to_vec();
let mut enc = BoolEncoder::new();
let mut ectx = CoeffCdfContext::new();
encode_block(&mut enc, &mut ectx, &mut plane_enc, w, 0, 0, w, h, base_q_idx);
let bytes = enc.finish();
let mut plane_dec = vec![0u8; w * h];
let mut dec = BoolDecoder::new(&bytes);
let mut dctx = CoeffCdfContext::new();
decode_block(&mut dec, &mut dctx, &mut plane_dec, w, 0, 0, w, h, base_q_idx);
plane_dec
}
#[test]
fn test_constant_block_lossless() {
let plane = vec![100u8; 16];
let decoded = encode_decode_plane_block(&plane, 4, 4, 0);
assert_eq!(decoded, plane, "constant block must round-trip exactly");
}
#[test]
fn test_gradient_block_lossless() {
let plane: Vec<u8> = (0u8..16).collect();
let decoded = encode_decode_plane_block(&plane, 4, 4, 0);
assert_eq!(decoded, plane, "gradient block must round-trip exactly");
}
#[test]
fn test_zero_block_lossless() {
let plane = vec![0u8; 16];
let decoded = encode_decode_plane_block(&plane, 4, 4, 0);
assert_eq!(decoded, plane);
}
#[test]
fn test_max_value_block_lossless() {
let plane = vec![255u8; 16];
let decoded = encode_decode_plane_block(&plane, 4, 4, 0);
assert_eq!(decoded, plane);
}
#[test]
fn test_mixed_values_block_lossless() {
let plane: Vec<u8> = [10, 200, 30, 150, 5, 250, 80, 175, 0, 255, 128, 64, 100, 50, 200, 0]
.to_vec();
let decoded = encode_decode_plane_block(&plane, 4, 4, 0);
assert_eq!(decoded, plane, "mixed values must round-trip exactly in lossless mode");
}
#[test]
fn test_8x8_lossless_round_trip() {
let w = 8usize;
let h = 8usize;
let original: Vec<u8> = (0u8..64).map(|i| i.wrapping_mul(3)).collect();
let mut plane_enc = original.clone();
let mut enc = BoolEncoder::new();
let mut ectx = CoeffCdfContext::new();
for (bx, by) in super::super::partition::block_grid(w, h) {
encode_block(&mut enc, &mut ectx, &mut plane_enc, w, bx, by, w, h, 0);
}
let bytes = enc.finish();
let mut plane_dec = vec![0u8; w * h];
let mut dec = BoolDecoder::new(&bytes);
let mut dctx = CoeffCdfContext::new();
for (bx, by) in super::super::partition::block_grid(w, h) {
decode_block(&mut dec, &mut dctx, &mut plane_dec, w, bx, by, w, h, 0);
}
for (i, (&orig, &got)) in original.iter().zip(plane_dec.iter()).enumerate() {
assert_eq!(
orig, got,
"8×8 lossless mismatch at pixel {i}: orig={orig} got={got}"
);
}
}
}