use crate::picture::SourceFormat;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SubImageIndex {
Zero,
One,
Two,
Three,
}
impl SubImageIndex {
pub fn as_u8(self) -> u8 {
match self {
SubImageIndex::Zero => 0,
SubImageIndex::One => 1,
SubImageIndex::Two => 2,
SubImageIndex::Three => 3,
}
}
pub fn from_u8(n: u8) -> Self {
Self::try_from_u8(n).expect("SubImageIndex::from_u8 requires 0..=3")
}
pub fn try_from_u8(n: u8) -> Option<Self> {
match n {
0 => Some(SubImageIndex::Zero),
1 => Some(SubImageIndex::One),
2 => Some(SubImageIndex::Two),
3 => Some(SubImageIndex::Three),
_ => None,
}
}
pub fn next(self) -> Option<Self> {
match self {
SubImageIndex::Zero => Some(SubImageIndex::One),
SubImageIndex::One => Some(SubImageIndex::Two),
SubImageIndex::Two => Some(SubImageIndex::Three),
SubImageIndex::Three => None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AnnexDTrError {
HighBitsNonZero { tr: u8 },
OutOfRange { tr: u8 },
}
pub fn still_image_tr(idx: SubImageIndex) -> u8 {
idx.as_u8() & 0b11
}
pub fn parse_still_image_tr(tr: u8) -> Result<SubImageIndex, AnnexDTrError> {
if tr > 0b11111 {
return Err(AnnexDTrError::OutOfRange { tr });
}
if tr & 0b11100 != 0 {
return Err(AnnexDTrError::HighBitsNonZero { tr });
}
Ok(SubImageIndex::from_u8(tr & 0b11))
}
pub fn still_image_dimensions(fmt: SourceFormat) -> (u32, u32) {
let (w, h) = fmt.dimensions();
(w * 2, h * 2)
}
pub fn still_image_chroma_dimensions(fmt: SourceFormat) -> (u32, u32) {
let (w, h) = still_image_dimensions(fmt);
(w / 2, h / 2)
}
pub fn subimage_origin(idx: SubImageIndex) -> (u32, u32) {
match idx {
SubImageIndex::Zero => (0, 0),
SubImageIndex::One => (0, 1),
SubImageIndex::Two => (1, 1),
SubImageIndex::Three => (1, 0),
}
}
pub fn subsample_plane(still_plane: &[u8], still_w: u32, still_h: u32) -> [Vec<u8>; 4] {
assert!(still_w % 2 == 0, "Annex D still-image width must be even");
assert!(still_h % 2 == 0, "Annex D still-image height must be even");
let expected = (still_w as usize) * (still_h as usize);
assert_eq!(
still_plane.len(),
expected,
"still_plane length {} doesn't match {} x {}",
still_plane.len(),
still_w,
still_h
);
let sub_w = (still_w / 2) as usize;
let sub_h = (still_h / 2) as usize;
let mut out: [Vec<u8>; 4] = [
vec![0u8; sub_w * sub_h],
vec![0u8; sub_w * sub_h],
vec![0u8; sub_w * sub_h],
vec![0u8; sub_w * sub_h],
];
for idx_u8 in 0u8..4 {
let idx = SubImageIndex::from_u8(idx_u8);
let (dx, dy) = subimage_origin(idx);
let dst = &mut out[idx_u8 as usize];
for y in 0..sub_h {
let src_y = y * 2 + (dy as usize);
let src_row = &still_plane[src_y * (still_w as usize)..][..(still_w as usize)];
let dst_row = &mut dst[y * sub_w..][..sub_w];
for x in 0..sub_w {
let src_x = x * 2 + (dx as usize);
dst_row[x] = src_row[src_x];
}
}
}
out
}
pub fn subsample_still_image(
fmt: SourceFormat,
still_y: &[u8],
still_cb: &[u8],
still_cr: &[u8],
) -> [SubImagePlanes; 4] {
let (sw, sh) = still_image_dimensions(fmt);
let (cw, ch) = still_image_chroma_dimensions(fmt);
let y_planes = subsample_plane(still_y, sw, sh);
let cb_planes = subsample_plane(still_cb, cw, ch);
let cr_planes = subsample_plane(still_cr, cw, ch);
let [y0, y1, y2, y3] = y_planes;
let [cb0, cb1, cb2, cb3] = cb_planes;
let [cr0, cr1, cr2, cr3] = cr_planes;
[
SubImagePlanes {
y: y0,
cb: cb0,
cr: cr0,
},
SubImagePlanes {
y: y1,
cb: cb1,
cr: cr1,
},
SubImagePlanes {
y: y2,
cb: cb2,
cr: cr2,
},
SubImagePlanes {
y: y3,
cb: cb3,
cr: cr3,
},
]
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SubImagePlanes {
pub y: Vec<u8>,
pub cb: Vec<u8>,
pub cr: Vec<u8>,
}
pub fn reassemble_plane(subs: &[Vec<u8>; 4], still_w: u32, still_h: u32) -> Vec<u8> {
assert!(still_w % 2 == 0, "Annex D still-image width must be even");
assert!(still_h % 2 == 0, "Annex D still-image height must be even");
let sub_w = (still_w / 2) as usize;
let sub_h = (still_h / 2) as usize;
for (i, p) in subs.iter().enumerate() {
assert_eq!(
p.len(),
sub_w * sub_h,
"sub-image {i} length {} doesn't match {} x {}",
p.len(),
sub_w,
sub_h
);
}
let mut out = vec![0u8; (still_w as usize) * (still_h as usize)];
for idx_u8 in 0u8..4 {
let idx = SubImageIndex::from_u8(idx_u8);
let (dx, dy) = subimage_origin(idx);
let src = &subs[idx_u8 as usize];
for y in 0..sub_h {
let dst_y = y * 2 + (dy as usize);
let dst_row = &mut out[dst_y * (still_w as usize)..][..(still_w as usize)];
let src_row = &src[y * sub_w..][..sub_w];
for x in 0..sub_w {
let dst_x = x * 2 + (dx as usize);
dst_row[dst_x] = src_row[x];
}
}
}
out
}
pub fn reassemble_still_image(
fmt: SourceFormat,
subs: &[SubImagePlanes; 4],
) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let (sw, sh) = still_image_dimensions(fmt);
let (cw, ch) = still_image_chroma_dimensions(fmt);
let y_subs = [
subs[0].y.clone(),
subs[1].y.clone(),
subs[2].y.clone(),
subs[3].y.clone(),
];
let cb_subs = [
subs[0].cb.clone(),
subs[1].cb.clone(),
subs[2].cb.clone(),
subs[3].cb.clone(),
];
let cr_subs = [
subs[0].cr.clone(),
subs[1].cr.clone(),
subs[2].cr.clone(),
subs[3].cr.clone(),
];
let y = reassemble_plane(&y_subs, sw, sh);
let cb = reassemble_plane(&cb_subs, cw, ch);
let cr = reassemble_plane(&cr_subs, cw, ch);
(y, cb, cr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sub_image_index_round_trip_u8() {
for n in 0u8..4 {
let idx = SubImageIndex::from_u8(n);
assert_eq!(idx.as_u8(), n);
assert_eq!(SubImageIndex::try_from_u8(n), Some(idx));
}
assert_eq!(SubImageIndex::try_from_u8(4), None);
assert_eq!(SubImageIndex::try_from_u8(255), None);
}
#[test]
fn sub_image_next_is_sequential() {
assert_eq!(SubImageIndex::Zero.next(), Some(SubImageIndex::One));
assert_eq!(SubImageIndex::One.next(), Some(SubImageIndex::Two));
assert_eq!(SubImageIndex::Two.next(), Some(SubImageIndex::Three));
assert_eq!(SubImageIndex::Three.next(), None);
}
#[test]
fn still_image_tr_low_bits_only() {
for n in 0u8..4 {
let idx = SubImageIndex::from_u8(n);
let tr = still_image_tr(idx);
assert!(tr <= 3, "tr {tr} should fit in 2 bits");
assert_eq!(tr & 0b11100, 0, "high 3 bits of TR must be 0");
assert_eq!(parse_still_image_tr(tr), Ok(idx));
}
}
#[test]
fn parse_still_image_tr_rejects_high_bits() {
for tr in [0b00100, 0b01000, 0b10000, 0b11111, 0b11100] {
assert_eq!(
parse_still_image_tr(tr),
Err(AnnexDTrError::HighBitsNonZero { tr })
);
}
}
#[test]
fn parse_still_image_tr_rejects_out_of_range() {
for tr in [32u8, 33, 64, 128, 255] {
assert_eq!(
parse_still_image_tr(tr),
Err(AnnexDTrError::OutOfRange { tr })
);
}
}
#[test]
fn still_image_dimensions_are_4x_video_format() {
assert_eq!(still_image_dimensions(SourceFormat::Qcif), (352, 288));
assert_eq!(still_image_dimensions(SourceFormat::Cif), (704, 576));
assert_eq!(
still_image_chroma_dimensions(SourceFormat::Qcif),
(176, 144)
);
assert_eq!(still_image_chroma_dimensions(SourceFormat::Cif), (352, 288));
}
#[test]
fn subimage_origin_matches_figure_d1() {
assert_eq!(subimage_origin(SubImageIndex::Zero), (0, 0));
assert_eq!(subimage_origin(SubImageIndex::One), (0, 1));
assert_eq!(subimage_origin(SubImageIndex::Two), (1, 1));
assert_eq!(subimage_origin(SubImageIndex::Three), (1, 0));
}
#[test]
fn subsample_then_reassemble_round_trip_small() {
let w = 4u32;
let h = 4u32;
let mut plane = vec![0u8; (w * h) as usize];
for y in 0..h {
for x in 0..w {
plane[(y * w + x) as usize] = ((y * 16) + x) as u8;
}
}
let subs = subsample_plane(&plane, w, h);
for p in &subs {
assert_eq!(p.len(), 4);
}
assert_eq!(subs[0], vec![0, 2, 32, 34]);
assert_eq!(subs[1], vec![16, 18, 48, 50]);
assert_eq!(subs[2], vec![17, 19, 49, 51]);
assert_eq!(subs[3], vec![1, 3, 33, 35]);
let recon = reassemble_plane(&subs, w, h);
assert_eq!(recon, plane);
}
#[test]
fn still_image_round_trip_qcif() {
let fmt = SourceFormat::Qcif;
let (sw, sh) = still_image_dimensions(fmt);
let (cw, ch) = still_image_chroma_dimensions(fmt);
assert_eq!((sw, sh), (352, 288));
assert_eq!((cw, ch), (176, 144));
let mut y = vec![0u8; (sw * sh) as usize];
let mut cb = vec![0u8; (cw * ch) as usize];
let mut cr = vec![0u8; (cw * ch) as usize];
let mut s: u32 = 0x1234_5678;
for b in y.iter_mut().chain(cb.iter_mut()).chain(cr.iter_mut()) {
s = s.wrapping_mul(1664525).wrapping_add(1013904223);
*b = (s >> 16) as u8;
}
let subs = subsample_still_image(fmt, &y, &cb, &cr);
let (vw, vh) = fmt.dimensions();
let (cvw, cvh) = (vw / 2, vh / 2);
for s in &subs {
assert_eq!(s.y.len(), (vw * vh) as usize);
assert_eq!(s.cb.len(), (cvw * cvh) as usize);
assert_eq!(s.cr.len(), (cvw * cvh) as usize);
}
let (y2, cb2, cr2) = reassemble_still_image(fmt, &subs);
assert_eq!(y2, y);
assert_eq!(cb2, cb);
assert_eq!(cr2, cr);
}
#[test]
fn still_image_round_trip_cif() {
let fmt = SourceFormat::Cif;
let (sw, sh) = still_image_dimensions(fmt);
let (cw, ch) = still_image_chroma_dimensions(fmt);
assert_eq!((sw, sh), (704, 576));
assert_eq!((cw, ch), (352, 288));
let mut y = vec![0u8; (sw * sh) as usize];
let mut cb = vec![0u8; (cw * ch) as usize];
let mut cr = vec![0u8; (cw * ch) as usize];
let mut s: u32 = 0xCAFE_BABE;
for b in y.iter_mut().chain(cb.iter_mut()).chain(cr.iter_mut()) {
s = s.wrapping_mul(48271);
*b = (s >> 16) as u8;
}
let subs = subsample_still_image(fmt, &y, &cb, &cr);
let (y2, cb2, cr2) = reassemble_still_image(fmt, &subs);
assert_eq!(y2, y);
assert_eq!(cb2, cb);
assert_eq!(cr2, cr);
}
#[test]
fn sub_image_origins_partition_the_2x2_tile() {
let mut hits = [[0u8; 2]; 2];
for n in 0u8..4 {
let (dx, dy) = subimage_origin(SubImageIndex::from_u8(n));
hits[dy as usize][dx as usize] += 1;
}
assert_eq!(hits, [[1u8; 2]; 2]);
}
}