use super::bitwriter::BitWriter;
use super::entropy_encode::encode_block;
use super::fdct::fdct_8x8;
use super::quantize::quantize_block;
use super::zigzag::PROGRESSIVE_ZIGZAG;
pub fn encode_slice(
luma: &[u16],
cb: &[u16],
cr: &[u16],
luma_stride: usize,
chroma_stride: usize,
mb_width: usize,
qscale: u8,
luma_matrix: &[u8; 64],
chroma_matrix: &[u8; 64],
) -> Vec<u8> {
let luma_bits = encode_plane(
luma,
luma_stride,
mb_width,
PlaneKind::Luma,
qscale,
luma_matrix,
);
let cb_bits = encode_plane(
cb,
chroma_stride,
mb_width,
PlaneKind::Chroma,
qscale,
chroma_matrix,
);
let cr_bits = encode_plane(
cr,
chroma_stride,
mb_width,
PlaneKind::Chroma,
qscale,
chroma_matrix,
);
let luma_size = luma_bits.len() as u16;
let cb_size = cb_bits.len() as u16;
let cr_size = cr_bits.len() as u16;
let mut out = Vec::with_capacity(8 + luma_bits.len() + cb_bits.len() + cr_bits.len());
out.push(8u8 << 4); out.push(qscale);
out.extend_from_slice(&luma_size.to_be_bytes());
out.extend_from_slice(&cb_size.to_be_bytes());
out.extend_from_slice(&cr_size.to_be_bytes());
out.extend_from_slice(&luma_bits);
out.extend_from_slice(&cb_bits);
out.extend_from_slice(&cr_bits);
out
}
#[allow(clippy::too_many_arguments)]
pub fn encode_slice_444(
luma: &[u16],
cb: &[u16],
cr: &[u16],
luma_stride: usize,
chroma_stride: usize,
mb_width: usize,
qscale: u8,
luma_matrix: &[u8; 64],
chroma_matrix: &[u8; 64],
) -> Vec<u8> {
let luma_bits = encode_plane(
luma,
luma_stride,
mb_width,
PlaneKind::Luma,
qscale,
luma_matrix,
);
let cb_bits = encode_plane(
cb,
chroma_stride,
mb_width,
PlaneKind::Chroma444,
qscale,
chroma_matrix,
);
let cr_bits = encode_plane(
cr,
chroma_stride,
mb_width,
PlaneKind::Chroma444,
qscale,
chroma_matrix,
);
let luma_size = luma_bits.len() as u16;
let cb_size = cb_bits.len() as u16;
let cr_size = cr_bits.len() as u16;
let mut out = Vec::with_capacity(8 + luma_bits.len() + cb_bits.len() + cr_bits.len());
out.push(8u8 << 4); out.push(qscale);
out.extend_from_slice(&luma_size.to_be_bytes());
out.extend_from_slice(&cb_size.to_be_bytes());
out.extend_from_slice(&cr_size.to_be_bytes());
out.extend_from_slice(&luma_bits);
out.extend_from_slice(&cb_bits);
out.extend_from_slice(&cr_bits);
out
}
#[derive(Clone, Copy)]
enum PlaneKind {
Luma,
Chroma,
Chroma444,
}
impl PlaneKind {
fn blocks_per_mb(self) -> usize {
match self {
Self::Luma | Self::Chroma444 => 4,
Self::Chroma => 2,
}
}
fn col_offset(self, mb_x: usize, block_in_mb: usize) -> usize {
match self {
Self::Luma | Self::Chroma444 => mb_x * 16 + (block_in_mb & 1) * 8,
Self::Chroma => mb_x * 8,
}
}
fn row_offset(self, block_in_mb: usize) -> usize {
match self {
Self::Luma | Self::Chroma444 => (block_in_mb / 2) * 8,
Self::Chroma => block_in_mb * 8,
}
}
}
fn encode_plane(
plane: &[u16],
stride: usize,
mb_width: usize,
kind: PlaneKind,
qscale: u8,
matrix: &[u8; 64],
) -> Vec<u8> {
let mut writer = BitWriter::new();
let mut running_dc: i16 = 0;
let blocks_per_mb = kind.blocks_per_mb();
for mb_x in 0..mb_width {
for b in 0..blocks_per_mb {
let col = kind.col_offset(mb_x, b);
let row = kind.row_offset(b);
let mut block = [0i32; 64];
for r in 0..8 {
for c in 0..8 {
let idx = (row + r) * stride + (col + c);
let sample = if idx < plane.len() { plane[idx] } else { 512 };
block[r * 8 + c] = i32::from(sample) - 512;
}
}
let freq = fdct_8x8(&block);
let quantized_raster = quantize_block(&freq, matrix, qscale);
let mut quantized_scan = [0i16; 64];
for (scan_idx, &raster_idx) in PROGRESSIVE_ZIGZAG.iter().enumerate() {
quantized_scan[scan_idx] = quantized_raster[raster_idx as usize];
}
running_dc = encode_block(&mut writer, &quantized_scan, running_dc);
}
}
writer.into_bytes()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prores::decode::{
decode_slice_to_yuv422, decode_slice_to_yuv444, split_slice_planes,
};
use crate::prores::picture::parse_slice_header;
use crate::prores::quant::{DEFAULT_CHROMA_QUANT_MATRIX, DEFAULT_LUMA_QUANT_MATRIX};
fn flat_plane(val: u16, width: usize, height: usize) -> Vec<u16> {
vec![val; width * height]
}
#[test]
fn encode_slice_header_format_is_correct() {
let mb_width = 8usize;
let luma_w = mb_width * 16;
let chroma_w = mb_width * 8;
let height = 16;
let luma = flat_plane(512, luma_w, height);
let cb = flat_plane(512, chroma_w, height);
let cr = flat_plane(512, chroma_w, height);
let slice_bytes = encode_slice(
&luma,
&cb,
&cr,
luma_w,
chroma_w,
mb_width,
6,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
);
let (hdr, payload) = parse_slice_header(&slice_bytes, false).expect("parse_slice_header");
assert_eq!(hdr.header_size, 8, "header_size should be 8");
assert_eq!(hdr.quant_scale, 6, "quant_scale should match");
assert_eq!(
hdr.data_size(),
payload.len(),
"sum of plane sizes should match payload"
);
}
#[test]
fn encode_decode_slice_flat_color_roundtrip() {
let mb_width = 2usize;
let luma_w = mb_width * 16;
let chroma_w = mb_width * 8;
let height = 16;
let luma = flat_plane(700, luma_w, height);
let cb = flat_plane(512, chroma_w, height);
let cr = flat_plane(300, chroma_w, height);
let slice_bytes = encode_slice(
&luma,
&cb,
&cr,
luma_w,
chroma_w,
mb_width,
6,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
);
let (hdr, payload) = parse_slice_header(&slice_bytes, false).expect("parse_slice_header");
let sd = split_slice_planes(
payload,
hdr.luma_data_size,
hdr.cb_data_size,
hdr.cr_data_size,
None,
)
.expect("split");
let mut dst_luma = vec![0u16; luma_w * height];
let mut dst_cb = vec![0u16; chroma_w * height];
let mut dst_cr = vec![0u16; chroma_w * height];
decode_slice_to_yuv422(
sd,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
hdr.quant_scale,
mb_width,
&mut dst_luma,
luma_w,
&mut dst_cb,
chroma_w,
&mut dst_cr,
chroma_w,
)
.expect("decode");
for &v in &dst_luma {
assert!(
(v as i32 - 700).abs() <= 16,
"luma error too large: got {v}"
);
}
for &v in &dst_cb {
assert!((v as i32 - 512).abs() <= 16, "Cb error too large: got {v}");
}
for &v in &dst_cr {
assert!((v as i32 - 300).abs() <= 16, "Cr error too large: got {v}");
}
}
#[test]
fn encode_decode_slice_444_flat_color_roundtrip() {
let mb_width = 2usize;
let plane_w = mb_width * 16; let height = 16;
let luma = flat_plane(620, plane_w, height);
let cb = flat_plane(480, plane_w, height);
let cr = flat_plane(350, plane_w, height);
let slice_bytes = encode_slice_444(
&luma,
&cb,
&cr,
plane_w,
plane_w,
mb_width,
6,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
);
let (hdr, payload) = parse_slice_header(&slice_bytes, false).expect("parse_slice_header");
let sd = split_slice_planes(
payload,
hdr.luma_data_size,
hdr.cb_data_size,
hdr.cr_data_size,
None,
)
.expect("split");
let mut dst_luma = vec![0u16; plane_w * height];
let mut dst_cb = vec![0u16; plane_w * height];
let mut dst_cr = vec![0u16; plane_w * height];
decode_slice_to_yuv444(
sd,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
hdr.quant_scale,
mb_width,
&mut dst_luma,
plane_w,
&mut dst_cb,
plane_w,
&mut dst_cr,
plane_w,
)
.expect("decode 4:4:4");
for &v in &dst_luma {
assert!(
(v as i32 - 620).abs() <= 16,
"luma error too large: got {v}"
);
}
for &v in &dst_cb {
assert!((v as i32 - 480).abs() <= 16, "Cb error too large: got {v}");
}
for &v in &dst_cr {
assert!((v as i32 - 350).abs() <= 16, "Cr error too large: got {v}");
}
}
#[test]
fn encode_slice_444_chroma_payload_larger_than_422() {
let mb_width = 4usize;
let luma_w = mb_width * 16;
let chroma_w_422 = mb_width * 8;
let chroma_w_444 = mb_width * 16;
let height = 16;
let ramp = |w: usize, h: usize| -> Vec<u16> {
(0..w * h)
.map(|i| 200u16 + ((i as u16 * 7) % 600))
.collect()
};
let luma = ramp(luma_w, height);
let s422 = encode_slice(
&luma,
&ramp(chroma_w_422, height),
&ramp(chroma_w_422, height),
luma_w,
chroma_w_422,
mb_width,
6,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
);
let s444 = encode_slice_444(
&luma,
&ramp(chroma_w_444, height),
&ramp(chroma_w_444, height),
luma_w,
chroma_w_444,
mb_width,
6,
&DEFAULT_LUMA_QUANT_MATRIX,
&DEFAULT_CHROMA_QUANT_MATRIX,
);
let (h422, _) = parse_slice_header(&s422, false).expect("422 header");
let (h444, _) = parse_slice_header(&s444, false).expect("444 header");
assert!(
h444.cb_data_size > h422.cb_data_size,
"4:4:4 Cb ({}) should exceed 4:2:2 Cb ({})",
h444.cb_data_size,
h422.cb_data_size
);
assert!(h444.cr_data_size > h422.cr_data_size);
}
}