fn main() {
let input = std::env::args().nth(1).expect("input HEIC file path");
let ref_path = std::env::args().nth(2).expect("reference YUV file path");
let data = std::fs::read(&input).expect("read input");
let decoder = heic::DecoderConfig::new();
let frame = decoder.decode_to_frame(&data).expect("decode HEIC");
let w = frame.cropped_width();
let h = frame.cropped_height();
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
eprintln!(
"Decoded: {}x{} (cropped from {}x{}, chroma {}x{})",
w, h, frame.width, frame.height, cw, ch
);
let ref_data = std::fs::read(&ref_path).expect("read reference");
let expected_size = (w * h + cw * ch * 2) as usize;
eprintln!(
"Reference YUV: {} bytes (expected {})",
ref_data.len(),
expected_size
);
assert_eq!(ref_data.len(), expected_size, "YUV size mismatch");
let ref_y = &ref_data[..(w * h) as usize];
let ref_cb = &ref_data[(w * h) as usize..(w * h + cw * ch) as usize];
let ref_cr = &ref_data[(w * h + cw * ch) as usize..];
let our_y = extract_cropped_plane(
&frame.y_plane,
frame.width,
frame.crop_left,
frame.crop_top,
w,
h,
);
let chroma_crop_left = frame.crop_left / 2;
let chroma_crop_top = frame.crop_top / 2;
let chroma_stride = frame.width.div_ceil(2);
let our_cb = extract_cropped_plane(
&frame.cb_plane,
chroma_stride,
chroma_crop_left,
chroma_crop_top,
cw,
ch,
);
let our_cr = extract_cropped_plane(
&frame.cr_plane,
chroma_stride,
chroma_crop_left,
chroma_crop_top,
cw,
ch,
);
let (y_psnr, y_first_diff) = compare_plane(&our_y, ref_y, w, h, "Y");
let (cb_psnr, cb_first_diff) = compare_plane(&our_cb, ref_cb, cw, ch, "Cb");
let (cr_psnr, cr_first_diff) = compare_plane(&our_cr, ref_cr, cw, ch, "Cr");
eprintln!(
"\nPSNR: Y={:.2} dB, Cb={:.2} dB, Cr={:.2} dB",
y_psnr, cb_psnr, cr_psnr
);
if let Some((x, y, ours, ref_val)) = y_first_diff {
eprintln!(
"First Y diff at ({}, {}): ours={} ref={}",
x, y, ours, ref_val
);
}
if let Some((x, y, ours, ref_val)) = cb_first_diff {
eprintln!(
"First Cb diff at ({}, {}): ours={} ref={}",
x, y, ours, ref_val
);
}
if let Some((x, y, ours, ref_val)) = cr_first_diff {
eprintln!(
"First Cr diff at ({}, {}): ours={} ref={}",
x, y, ours, ref_val
);
}
}
fn extract_cropped_plane(
plane: &[u16],
stride: u32,
crop_left: u32,
crop_top: u32,
width: u32,
height: u32,
) -> Vec<u16> {
let mut out = Vec::with_capacity((width * height) as usize);
for y in 0..height {
let src_y = crop_top + y;
let src_start = (src_y * stride + crop_left) as usize;
out.extend_from_slice(&plane[src_start..src_start + width as usize]);
}
out
}
fn compare_plane(
ours: &[u16],
reference: &[u8],
w: u32,
h: u32,
name: &str,
) -> (f64, Option<(u32, u32, u16, u8)>) {
let size = (w * h) as usize;
assert_eq!(
ours.len(),
size,
"{} plane size mismatch: {} vs {}",
name,
ours.len(),
size
);
let mut mse_sum = 0u64;
let mut diff_count = 0u32;
let mut max_diff = 0u32;
let mut first_diff = None;
for y in 0..h {
for x in 0..w {
let idx = (y * w + x) as usize;
let our_val = ours[idx];
let ref_val = reference[idx];
let d = (our_val as i32 - ref_val as i32).unsigned_abs();
if d > 0 {
diff_count += 1;
if first_diff.is_none() {
first_diff = Some((x, y, our_val, ref_val));
}
}
max_diff = max_diff.max(d);
mse_sum += (d * d) as u64;
}
}
let mse = mse_sum as f64 / size as f64;
let psnr = if mse == 0.0 {
f64::INFINITY
} else {
10.0 * (255.0f64 * 255.0 / mse).log10()
};
eprintln!(
"{}: PSNR={:.2} dB, diff_pixels={}/{}, max_diff={}",
name, psnr, diff_count, size, max_diff
);
(psnr, first_diff)
}