use rusty_jpeg::decode::Decoder;
use rusty_jpeg::encode::{ColorType, Encoder, SamplingFactor};
use std::io::Cursor;
fn source(w: usize, h: usize) -> Vec<u8> {
let mut rgb = vec![0u8; w * h * 3];
for y in 0..h {
for x in 0..w {
let o = (y * w + x) * 3;
rgb[o] = ((x * 7 + y * 3) % 256) as u8;
rgb[o + 1] = ((x * 3 + y * 11) % 256) as u8;
rgb[o + 2] = ((x * 13 + y * 5) % 256) as u8;
}
}
rgb
}
fn encode(w: usize, h: usize, sampling: SamplingFactor) -> Vec<u8> {
let mut out = Vec::new();
let mut enc = Encoder::new(&mut out, 95);
enc.set_sampling_factor(sampling);
enc.encode(&source(w, h), w as u16, h as u16, ColorType::Rgb)
.expect("encode");
out
}
#[test]
fn plane_geometry_matches_the_declared_subsampling() {
for (sampling, want) in [
(SamplingFactor::R_4_4_4, (1, 1)),
(SamplingFactor::R_4_2_2, (2, 1)),
(SamplingFactor::R_4_2_0, (2, 2)),
] {
let jpeg = encode(64, 48, sampling);
let planar = Decoder::new(Cursor::new(&jpeg))
.decode_planar()
.expect("decode_planar");
assert_eq!(planar.components.len(), 3, "{sampling:?}");
assert_eq!((planar.width, planar.height), (64, 48));
assert_eq!(planar.chroma_subsampling(), Some(want), "{sampling:?}");
let (sh, sv) = want;
assert_eq!(planar.components[0].width, 64, "{sampling:?} luma width");
assert_eq!(planar.components[0].height, 48, "{sampling:?} luma height");
for c in &planar.components[1..] {
assert_eq!(c.width, 64usize.div_ceil(sh), "{sampling:?} chroma width");
assert_eq!(c.height, 48usize.div_ceil(sv), "{sampling:?} chroma height");
assert!(c.stride >= c.width);
assert!(c.data.len() >= c.stride * (c.height - 1) + c.width);
}
}
}
fn reconstruction_error(jpeg: &[u8], w: usize, h: usize, swap_chroma: bool) -> f64 {
let rgb = Decoder::new(Cursor::new(jpeg)).decode().expect("decode");
let planar = Decoder::new(Cursor::new(jpeg))
.decode_planar()
.expect("decode_planar");
let (sh, sv) = planar.chroma_subsampling().unwrap();
let yp = &planar.components[0];
let (cb, cr) = if swap_chroma {
(&planar.components[2], &planar.components[1])
} else {
(&planar.components[1], &planar.components[2])
};
let mut total = 0f64;
for y in 0..h {
for x in 0..w {
let yy = yp.data[y * yp.stride + x] as f32;
let b = cb.data[(y / sv) * cb.stride + (x / sh)] as f32 - 128.0;
let r = cr.data[(y / sv) * cr.stride + (x / sh)] as f32 - 128.0;
let want = [
yy + 1.402 * r,
yy - 0.344136 * b - 0.714136 * r,
yy + 1.772 * b,
];
for (c, wv) in want.iter().enumerate() {
let got = rgb[(y * w + x) * 3 + c] as i32;
total += (got - wv.round().clamp(0.0, 255.0) as i32).abs() as f64;
}
}
}
total / (w * h * 3) as f64
}
#[test]
fn planes_reconstruct_the_interleaved_decode() {
for (sampling, tol) in [
(SamplingFactor::R_4_4_4, 0.5),
(SamplingFactor::R_4_2_2, 8.0),
(SamplingFactor::R_4_2_0, 12.0),
] {
let (w, h) = (64usize, 48usize);
let jpeg = encode(w, h, sampling);
let err = reconstruction_error(&jpeg, w, h, false);
assert!(
err <= tol,
"{sampling:?}: mean error {err:.2} exceeds {tol}"
);
let swapped = reconstruction_error(&jpeg, w, h, true);
assert!(
swapped > tol * 2.0,
"{sampling:?}: swapping Cb/Cr only moved mean error to {swapped:.2}, \
so this test cannot detect a wrong plane"
);
}
}
#[test]
fn grayscale_yields_a_single_plane() {
let mut out = Vec::new();
let gray: Vec<u8> = (0..(32 * 32)).map(|i| (i % 256) as u8).collect();
Encoder::new(&mut out, 90)
.encode(&gray, 32, 32, ColorType::Luma)
.expect("encode");
let planar = Decoder::new(Cursor::new(&out))
.decode_planar()
.expect("decode_planar");
assert_eq!(planar.components.len(), 1);
assert_eq!(planar.chroma_subsampling(), None);
}
#[test]
fn planar_request_does_not_leak_into_a_later_decode() {
let jpeg = encode(32, 32, SamplingFactor::R_4_2_0);
let mut d = Decoder::new(Cursor::new(&jpeg));
assert!(d.decode_planar().is_ok());
let mut d2 = Decoder::new(Cursor::new(&jpeg));
assert!(!d2.decode().unwrap().is_empty());
}
#[test]
fn single_threaded_flag_selects_the_immediate_worker_on_baseline() {
let jpeg = encode(64, 64, SamplingFactor::R_4_2_0);
let mut d = Decoder::new(Cursor::new(&jpeg));
d.set_single_threaded(true);
let st = d.decode().expect("decode st");
assert!(
rusty_jpeg::decode::last_worker_was_immediate(),
"set_single_threaded(true) did not select the immediate worker"
);
let mut d = Decoder::new(Cursor::new(&jpeg));
d.set_single_threaded(false);
let mt = d.decode().expect("decode mt");
assert_eq!(st, mt, "single-threaded decode diverged from multithreaded");
}