pub(crate) fn read_exif_orientation(exif_data: &[u8]) -> (u16, bool) {
let tiff_data = if exif_data.starts_with(b"Exif\0\0") {
&exif_data[6..]
} else {
exif_data
};
let reader = exif::Reader::new();
let Ok(parsed) = reader.read_raw(tiff_data.to_vec()) else {
return (0, false);
};
let Some(orient) = parsed.get_field(exif::Tag::Orientation, exif::In::PRIMARY) else {
return (0, false);
};
match orient.value.get_uint(0).unwrap_or(1) {
1 => (0, false),
2 => (0, true),
3 => (180, false),
4 => (180, true),
5 => (270, true),
6 => (90, false),
7 => (90, true),
8 => (270, false),
_ => (0, false),
}
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn apply_exif_u8(
data: &mut [u8],
stride: usize,
w: &mut usize,
h: &mut usize,
bytes_per_pixel: usize,
rotation_deg: u16,
flip_h: bool,
scratch: &mut Vec<u8>,
) {
let img_w = *w;
let img_h = *h;
if flip_h {
for y in 0..img_h {
let row_start = y * stride;
for x in 0..img_w / 2 {
let left = row_start + x * bytes_per_pixel;
let right = row_start + (img_w - 1 - x) * bytes_per_pixel;
for c in 0..bytes_per_pixel {
data.swap(left + c, right + c);
}
}
}
}
match rotation_deg {
90 => {
let new_w = img_h;
let new_h = img_w;
scratch.resize(new_w * new_h * bytes_per_pixel, 0);
for y in 0..img_h {
for x in 0..img_w {
let src_off = y * stride + x * bytes_per_pixel;
let dst_x = img_h - 1 - y;
let dst_y = x;
let dst_off = dst_y * new_w * bytes_per_pixel + dst_x * bytes_per_pixel;
scratch[dst_off..dst_off + bytes_per_pixel]
.copy_from_slice(&data[src_off..src_off + bytes_per_pixel]);
}
}
data[..scratch.len()].copy_from_slice(scratch);
*w = new_w;
*h = new_h;
}
180 => {
let total = img_w * img_h;
for i in 0..total / 2 {
let j = total - 1 - i;
let a_y = i / img_w;
let a_x = i % img_w;
let b_y = j / img_w;
let b_x = j % img_w;
let a = a_y * stride + a_x * bytes_per_pixel;
let b = b_y * stride + b_x * bytes_per_pixel;
for c in 0..bytes_per_pixel {
data.swap(a + c, b + c);
}
}
}
270 => {
let new_w = img_h;
let new_h = img_w;
scratch.resize(new_w * new_h * bytes_per_pixel, 0);
for y in 0..img_h {
for x in 0..img_w {
let src_off = y * stride + x * bytes_per_pixel;
let dst_x = y;
let dst_y = img_w - 1 - x;
let dst_off = dst_y * new_w * bytes_per_pixel + dst_x * bytes_per_pixel;
scratch[dst_off..dst_off + bytes_per_pixel]
.copy_from_slice(&data[src_off..src_off + bytes_per_pixel]);
}
}
data[..scratch.len()].copy_from_slice(scratch);
*w = new_w;
*h = new_h;
}
_ => {}
}
}
pub(crate) fn rotated_dims(width: usize, height: usize, rotation_deg: u16) -> (usize, usize) {
match rotation_deg {
90 | 270 => (height, width),
_ => (width, height),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn exif_orientation_default() {
assert_eq!(read_exif_orientation(&[]), (0, false));
}
#[test]
fn rotated_dims_swap() {
assert_eq!(rotated_dims(10, 20, 0), (10, 20));
assert_eq!(rotated_dims(10, 20, 90), (20, 10));
assert_eq!(rotated_dims(10, 20, 180), (10, 20));
assert_eq!(rotated_dims(10, 20, 270), (20, 10));
}
fn make_strided_rgb(stride: usize) -> Vec<u8> {
assert!(stride >= 9);
let mut buf = vec![0xFF; stride * 2];
for y in 0..2usize {
for x in 0..3usize {
let off = y * stride + x * 3;
let base = ((y * 3 + x) * 10) as u8;
buf[off] = base;
buf[off + 1] = base + 1;
buf[off + 2] = base + 2;
}
}
buf
}
fn rgb_at(buf: &[u8], stride: usize, x: usize, y: usize) -> [u8; 3] {
let off = y * stride + x * 3;
[buf[off], buf[off + 1], buf[off + 2]]
}
#[test]
fn rotate_180_respects_strided_input() {
let stride = 12;
let mut buf = make_strided_rgb(stride);
let mut scratch = Vec::new();
let mut w = 3usize;
let mut h = 2usize;
apply_exif_u8(
&mut buf,
stride,
&mut w,
&mut h,
3,
180,
false,
&mut scratch,
);
assert_eq!((w, h), (3, 2));
for y in 0..2 {
for x in 0..3 {
let src_base = (((1 - y) * 3 + (2 - x)) * 10) as u8;
assert_eq!(
rgb_at(&buf, stride, x, y),
[src_base, src_base + 1, src_base + 2],
"180° mismatch at ({x},{y}) with strided input"
);
}
}
}
#[test]
fn rotate_90_respects_strided_input() {
let stride = 12;
let mut buf = make_strided_rgb(stride);
let mut scratch = Vec::new();
let mut w = 3usize;
let mut h = 2usize;
apply_exif_u8(&mut buf, stride, &mut w, &mut h, 3, 90, false, &mut scratch);
assert_eq!((w, h), (2, 3));
let new_stride = 6;
for src_y in 0..2 {
for src_x in 0..3 {
let dst_x = 1 - src_y;
let dst_y = src_x;
let base = ((src_y * 3 + src_x) * 10) as u8;
assert_eq!(
rgb_at(&buf, new_stride, dst_x, dst_y),
[base, base + 1, base + 2],
"90° mismatch: src ({src_x},{src_y}) -> dst ({dst_x},{dst_y})"
);
}
}
}
}