use edgefirst_codec::{peek_info, DecodeOptions, ImageDecoder, ImageLoad};
use edgefirst_tensor::{PixelFormat, Tensor, TensorMemory, TensorTrait};
use std::path::PathBuf;
const W: usize = 1280;
const H: usize = 720;
const CH: usize = 3;
fn testdata(name: &str) -> Vec<u8> {
let root = std::env::var("EDGEFIRST_TESTDATA_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.unwrap()
.parent()
.unwrap()
.join("testdata")
});
let path = root.join(name);
std::fs::read(&path).unwrap_or_else(|e| panic!("read testdata {}: {e}", path.display()))
}
fn decode_to_rgb(name: &str, apply: bool, max_w: usize, max_h: usize) -> (usize, usize, Vec<u8>) {
let data = testdata(name);
let opts = DecodeOptions::default()
.with_format(PixelFormat::Rgb)
.with_exif(apply);
let mut tensor =
Tensor::<u8>::image(max_w, max_h, PixelFormat::Rgb, Some(TensorMemory::Mem)).unwrap();
let mut decoder = ImageDecoder::new();
let info = tensor
.load_image(&mut decoder, &data, &opts)
.unwrap_or_else(|e| panic!("decode {name}: {e}"));
let stride = tensor.effective_row_stride().unwrap_or(max_w * CH);
let map = tensor.map().unwrap();
let bytes: &[u8] = ↦
let mut packed = vec![0u8; info.width * info.height * CH];
let row_bytes = info.width * CH;
for y in 0..info.height {
let src = y * stride;
let dst = y * row_bytes;
packed[dst..dst + row_bytes].copy_from_slice(&bytes[src..src + row_bytes]);
}
(info.width, info.height, packed)
}
fn apply_exif_oracle(w: usize, h: usize, src: &[u8], o: u32) -> (usize, usize, Vec<u8>) {
assert_eq!(src.len(), w * h * CH);
let (new_w, new_h) = match o {
5..=8 => (h, w),
_ => (w, h),
};
let mut dst = vec![0u8; new_w * new_h * CH];
for y in 0..h {
for x in 0..w {
let src_off = (y * w + x) * CH;
let (nx, ny) = match o {
1 => (x, y),
2 => (w - 1 - x, y),
3 => (w - 1 - x, h - 1 - y),
4 => (x, h - 1 - y),
5 => (y, x),
6 => (h - 1 - y, x),
7 => (h - 1 - y, w - 1 - x),
8 => (y, w - 1 - x),
_ => unreachable!("orientation {o} out of range"),
};
let dst_off = (ny * new_w + nx) * CH;
dst[dst_off..dst_off + CH].copy_from_slice(&src[src_off..src_off + CH]);
}
}
(new_w, new_h, dst)
}
fn sad(a: &[u8], b: &[u8]) -> u64 {
assert_eq!(a.len(), b.len(), "buffer length mismatch");
a.iter()
.zip(b.iter())
.map(|(&x, &y)| x.abs_diff(y) as u64)
.sum()
}
#[test]
fn jpeg_exif_apply_false_all_identical() {
let (_, _, reference) = decode_to_rgb("zidane_exif_1.jpg", false, W, H);
for o in 2..=8 {
let (w, h, pixels) = decode_to_rgb(&format!("zidane_exif_{o}.jpg"), false, W, H);
assert_eq!((w, h), (W, H), "orientation {o} dims differ from baseline");
assert_eq!(
pixels, reference,
"orientation {o} pixels differ from orientation=1 with apply_exif=false"
);
}
}
#[test]
fn jpeg_exif_peek_swaps_dims_for_5_6_7_8() {
for o in 1..=8u32 {
let data = testdata(&format!("zidane_exif_{o}.jpg"));
let opts = DecodeOptions::default()
.with_format(PixelFormat::Rgb)
.with_exif(true);
let info = peek_info(&data, &opts).unwrap();
match o {
5..=8 => assert_eq!(
(info.width, info.height),
(H, W),
"orientation {o} should peek post-rotation (H, W)"
),
_ => assert_eq!(
(info.width, info.height),
(W, H),
"orientation {o} should peek pre-rotation (W, H)"
),
}
}
}
#[test]
fn jpeg_exif_apply_true_matches_oracle_orientation_1() {
let (w, h, decoded) = decode_to_rgb("zidane_exif_1.jpg", true, W, H);
let (_, _, reference) = decode_to_rgb("zidane_exif_1.jpg", false, W, H);
assert_eq!((w, h), (W, H));
assert_eq!(decoded, reference);
}
fn check_orientation(o: u32) {
let (ref_w, ref_h, reference) = decode_to_rgb("zidane_exif_1.jpg", false, W, H);
assert_eq!((ref_w, ref_h), (W, H));
let (exp_w, exp_h, expected) = apply_exif_oracle(ref_w, ref_h, &reference, o);
let (got_w, got_h, decoded) =
decode_to_rgb(&format!("zidane_exif_{o}.jpg"), true, W.max(H), W.max(H));
assert_eq!(
(got_w, got_h),
(exp_w, exp_h),
"orientation {o}: decoded dims differ from oracle"
);
let pixel_count = exp_w * exp_h * CH;
let s = sad(&decoded[..pixel_count], &expected[..pixel_count]);
assert_eq!(
s, 0,
"orientation {o}: SAD={s} (expected 0 — apply_exif_u8 is a pure byte rearrangement)"
);
}
#[test]
fn jpeg_exif_orientation_1() {
check_orientation(1);
}
#[test]
fn jpeg_exif_orientation_2() {
check_orientation(2);
}
#[test]
fn jpeg_exif_orientation_3() {
check_orientation(3);
}
#[test]
fn jpeg_exif_orientation_4() {
check_orientation(4);
}
#[test]
fn jpeg_exif_orientation_5() {
check_orientation(5);
}
#[test]
fn jpeg_exif_orientation_6() {
check_orientation(6);
}
#[test]
fn jpeg_exif_orientation_7() {
check_orientation(7);
}
#[test]
fn jpeg_exif_orientation_8() {
check_orientation(8);
}
#[test]
fn png_exif_apply_false_all_identical() {
let (_, _, reference) = decode_to_rgb("zidane_exif_1.png", false, W, H);
for o in 2..=8 {
let (w, h, pixels) = decode_to_rgb(&format!("zidane_exif_{o}.png"), false, W, H);
assert_eq!((w, h), (W, H));
assert_eq!(
pixels, reference,
"PNG orientation {o} pixels differ with apply_exif=false"
);
}
}
#[test]
fn png_exif_peek_swaps_dims_for_5_6_7_8() {
for o in 1..=8u32 {
let data = testdata(&format!("zidane_exif_{o}.png"));
let opts = DecodeOptions::default()
.with_format(PixelFormat::Rgb)
.with_exif(true);
let info = peek_info(&data, &opts).unwrap();
match o {
5..=8 => assert_eq!(
(info.width, info.height),
(H, W),
"PNG orientation {o} should peek post-rotation"
),
_ => assert_eq!(
(info.width, info.height),
(W, H),
"PNG orientation {o} should peek pre-rotation"
),
}
}
}
fn check_png_orientation(o: u32) {
let (ref_w, ref_h, reference) = decode_to_rgb("zidane_exif_1.png", false, W, H);
let (exp_w, exp_h, expected) = apply_exif_oracle(ref_w, ref_h, &reference, o);
let (got_w, got_h, decoded) =
decode_to_rgb(&format!("zidane_exif_{o}.png"), true, W.max(H), W.max(H));
assert_eq!(
(got_w, got_h),
(exp_w, exp_h),
"PNG orientation {o}: decoded dims differ from oracle"
);
let pixel_count = exp_w * exp_h * CH;
let s = sad(&decoded[..pixel_count], &expected[..pixel_count]);
assert_eq!(s, 0, "PNG orientation {o}: SAD={s} (expected 0)");
}
#[test]
fn png_exif_orientation_1() {
check_png_orientation(1);
}
#[test]
fn png_exif_orientation_2() {
check_png_orientation(2);
}
#[test]
fn png_exif_orientation_3() {
check_png_orientation(3);
}
#[test]
fn png_exif_orientation_4() {
check_png_orientation(4);
}
#[test]
fn png_exif_orientation_5() {
check_png_orientation(5);
}
#[test]
fn png_exif_orientation_6() {
check_png_orientation(6);
}
#[test]
fn png_exif_orientation_7() {
check_png_orientation(7);
}
#[test]
fn png_exif_orientation_8() {
check_png_orientation(8);
}