use jpegli::{
decoder::Decoder,
encoder::{ChromaSubsampling, EncoderConfig, PixelLayout},
};
fn generate_gradient(width: usize, height: usize) -> Vec<u8> {
let mut rgb = vec![0u8; width * height * 3];
for y in 0..height {
for x in 0..width {
let idx = (y * width + x) * 3;
rgb[idx] = ((x * 255) / width.max(1)) as u8;
rgb[idx + 1] = ((y * 255) / height.max(1)) as u8;
rgb[idx + 2] = 128;
}
}
rgb
}
fn max_pixel_diff(a: &[u8], b: &[u8]) -> u8 {
a.iter()
.zip(b.iter())
.map(|(&x, &y)| x.abs_diff(y))
.max()
.unwrap_or(0)
}
fn encode_rgb(width: u32, height: u32, data: &[u8], config: &EncoderConfig) -> Vec<u8> {
let mut enc = config
.encode_from_bytes(width, height, PixelLayout::Rgb8Srgb)
.expect("encoder creation failed");
enc.push_packed(data, enough::Unstoppable)
.expect("push failed");
enc.finish().expect("finish failed")
}
#[test]
fn test_baseline_roundtrip_consistency() {
let width = 128u32;
let height = 128u32;
let rgb = generate_gradient(width as usize, height as usize);
let config = EncoderConfig::ycbcr(90.0, ChromaSubsampling::Quarter);
let jpeg1 = encode_rgb(width, height, &rgb, &config);
let decoded1 = Decoder::new().decode(&jpeg1).expect("decode 1 failed");
let jpeg2 = encode_rgb(width, height, &decoded1.data, &config);
let decoded2 = Decoder::new().decode(&jpeg2).expect("decode 2 failed");
let decode_same_1 = Decoder::new().decode(&jpeg1).expect("decode same 1 failed");
let decode_same_2 = Decoder::new().decode(&jpeg1).expect("decode same 2 failed");
assert_eq!(
decode_same_1.data, decode_same_2.data,
"Decoding same JPEG twice should produce identical pixels"
);
let max_diff = max_pixel_diff(&decoded1.data, &decoded2.data);
assert!(
max_diff <= 10,
"Roundtrip stability: max diff {} too high (expected <= 10)",
max_diff
);
}
#[test]
fn test_progressive_roundtrip_consistency() {
let width = 128u32;
let height = 128u32;
let rgb = generate_gradient(width as usize, height as usize);
let config = EncoderConfig::ycbcr(90.0, ChromaSubsampling::Quarter).progressive(true);
let jpeg1 = encode_rgb(width, height, &rgb, &config);
let decoded1 = Decoder::new().decode(&jpeg1).expect("decode 1 failed");
let jpeg2 = encode_rgb(width, height, &decoded1.data, &config);
let decoded2 = Decoder::new().decode(&jpeg2).expect("decode 2 failed");
let max_diff = max_pixel_diff(&decoded1.data, &decoded2.data);
assert!(
max_diff <= 10,
"Progressive roundtrip stability: max diff {} too high",
max_diff
);
}
#[test]
fn test_decoder_produces_correct_dimensions() {
let config = EncoderConfig::ycbcr(85.0, ChromaSubsampling::Quarter);
for (width, height) in [(64, 64), (100, 75), (256, 128), (17, 33)] {
let rgb = generate_gradient(width, height);
let jpeg = encode_rgb(width as u32, height as u32, &rgb, &config);
let decoded = Decoder::new().decode(&jpeg).expect("decode failed");
assert_eq!(
decoded.width as usize, width,
"Decoded width mismatch for {}x{}",
width, height
);
assert_eq!(
decoded.height as usize, height,
"Decoded height mismatch for {}x{}",
width, height
);
assert_eq!(
decoded.data.len(),
width * height * 3,
"Decoded data size mismatch"
);
}
}
#[test]
fn test_multiple_roundtrips_converge() {
let width = 64u32;
let height = 64u32;
let rgb = generate_gradient(width as usize, height as usize);
let config = EncoderConfig::ycbcr(90.0, ChromaSubsampling::Quarter);
let mut current_pixels = rgb.clone();
let mut previous_diff = u8::MAX;
for i in 0..5 {
let jpeg = encode_rgb(width, height, ¤t_pixels, &config);
let decoded = Decoder::new().decode(&jpeg).expect("decode failed");
let max_diff = max_pixel_diff(¤t_pixels, &decoded.data);
if i > 0 {
assert!(
max_diff <= previous_diff + 2,
"Roundtrip {} introduced unexpected error: {} vs previous {}",
i,
max_diff,
previous_diff
);
}
previous_diff = max_diff;
current_pixels = decoded.data;
}
}
#[test]
fn test_high_quality_roundtrip_low_error() {
let width = 128u32;
let height = 128u32;
let rgb = generate_gradient(width as usize, height as usize);
let config = EncoderConfig::ycbcr(100.0, ChromaSubsampling::Quarter);
let jpeg = encode_rgb(width, height, &rgb, &config);
let decoded = Decoder::new().decode(&jpeg).expect("decode failed");
let max_diff = max_pixel_diff(&rgb, &decoded.data);
assert!(
max_diff <= 5,
"Q100 max diff {} too high (expected <= 5)",
max_diff
);
}