use sentryshot_util::{image_fill_max_pixsteps, pixfmt::PIX_FMT_FLAG_PAL, Frame};
use std::num::NonZeroU16;
use thiserror::Error;
#[derive(Debug, Error, PartialEq, Eq)]
pub enum CropError {
#[error("size out of bounds: {0}+{1} > {2}")]
OutOfBounds(u16, u16, u16),
#[error("add")]
Add,
}
#[allow(clippy::similar_names)]
pub fn crop(
src_frame: &Frame,
dst_frame: &mut Frame,
x: u16,
y: u16,
w: NonZeroU16,
h: NonZeroU16,
) -> Result<(), CropError> {
use CropError::*;
let src_width = src_frame.width().get();
if x.checked_add(w.get()).ok_or(Add)? > src_width {
return Err(OutOfBounds(x, w.get(), src_width));
}
let src_height = src_frame.height().get();
if y.checked_add(h.get()).ok_or(Add)? > src_height {
return Err(OutOfBounds(y, h.get(), src_height));
}
let pix_fmt = src_frame.pix_fmt();
dst_frame.set_width(w);
dst_frame.set_height(h);
dst_frame.set_pix_fmt(pix_fmt);
*dst_frame.linesize_mut() = *src_frame.linesize();
let mut max_step = [0, 0, 0, 0];
image_fill_max_pixsteps(&mut max_step, &mut [0, 0, 0, 0], Some(pix_fmt));
let hsub = pix_fmt.log2_chroma_w();
let vsub = pix_fmt.log2_chroma_h();
let mut offsets = [0, 0, 0, 0];
offsets[0] = usize::from(y) * dst_frame.linesize()[0];
offsets[0] += usize::from(x) * max_step[0];
if pix_fmt.flags() & PIX_FMT_FLAG_PAL == 0 {
for i in 1..3 {
if !dst_frame.data()[i].is_empty() {
offsets[i] += (usize::from(y) >> vsub) * dst_frame.linesize()[i];
offsets[i] += (usize::from(x) * max_step[i]) >> hsub;
}
}
}
if !dst_frame.data()[3].is_empty() {
offsets[3] += usize::from(y) * dst_frame.linesize()[3];
offsets[3] += usize::from(x) * max_step[3];
}
#[allow(clippy::needless_range_loop)]
for i in 0..4 {
dst_frame.data_mut()[i] = src_frame.data()[i][offsets[i]..].to_owned();
}
Ok(())
}
#[allow(clippy::too_many_arguments, clippy::unwrap_used)]
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use pretty_hex::pretty_hex;
use sentryshot_util::pixfmt::PixelFormat;
use std::num::NonZeroU16;
use test_case::test_case;
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
0, 0, 2, 2;
"rgb24 minimal"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[0,0,0,
2,2,2],
0, 0, 1, 2;
"rgb24 right"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[0,0,0, 1,1,1],
0, 0, 2, 1;
"rgb24 bottom"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[0,0,0],
0, 0, 1, 1;
"rgb24 right and bottom"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[1,1,1,
3,3,3],
1, 0, 1, 2;
"rgb24 left"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[2,2,2, 3,3,3],
0, 1, 2, 1;
"rgb24 top"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
&[3,3,3],
1, 1, 1, 1;
"rgb24 left and top"
)]
#[test_case(
3, 3, PixelFormat::RGB24,
&[0,0,0, 1,1,1, 2,2,2,
3,3,3, 4,4,4, 5,5,5,
6,6,6, 7,7,7, 8,8,8],
&[0,0,0, 1,1,1, 2,2,2,
3,3,3, 4,4,4, 5,5,5,
6,6,6, 7,7,7, 8,8,8],
0, 0, 3, 3;
"rgb24 3x3 minimal"
)]
#[test_case(
3, 3, PixelFormat::RGB24,
&[0,0,0, 1,1,1, 2,2,2,
3,3,3, 4,4,4, 5,5,5,
6,6,6, 7,7,7, 8,8,8],
&[4,4,4, 5,5,5,
7,7,7, 8,8,8],
1, 1, 2, 2;
"rgb24 3x3 to 2x2"
)]
#[test_case(
4, 4, PixelFormat::RGB24,
&[0,0,0, 1,1,1, 2,2,2, 3,3,3,
4,4,4, 5,5,5, 6,6,6, 7,7,7,
8,8,8, 9,9,9, 10,10,10, 11,11,11,
12,12,12, 13,13,13, 14,14,14, 15,15,15,
],
&[0,0,0, 1,1,1, 2,2,2, 3,3,3,
4,4,4, 5,5,5, 6,6,6, 7,7,7,
8,8,8, 9,9,9, 10,10,10, 11,11,11,
12,12,12, 13,13,13, 14,14,14, 15,15,15],
0, 0, 4, 4;
"rgb24 4x4 minimal"
)]
#[test_case(
4, 4, PixelFormat::RGB24,
&[0,0,0, 1,1,1, 2,2,2, 3,3,3,
4,4,4, 5,5,5, 6,6,6, 7,7,7,
8,8,8, 9,9,9, 10,10,10, 11,11,11,
12,12,12, 13,13,13, 14,14,14, 15,15,15],
&[5,5,5, 6,6,6,
9,9,9, 10,10,10],
1, 1, 2, 2;
"rgb24 4x4 border"
)]
fn test_crop(
src_width: u16,
src_height: u16,
pix_fmt: PixelFormat,
input: &[u8],
want: &[u8],
x: u16,
y: u16,
w: u16,
h: u16,
) {
let src_frame = Frame::from_raw(
input,
pix_fmt,
NonZeroU16::new(src_width).unwrap(),
NonZeroU16::new(src_height).unwrap(),
1,
)
.unwrap();
let mut dst_frame = Frame::new();
crop(
&src_frame,
&mut dst_frame,
x,
y,
NonZeroU16::new(w).unwrap(),
NonZeroU16::new(h).unwrap(),
)
.unwrap();
let mut raw = Vec::new();
dst_frame.copy_to_buffer(&mut raw, 1).unwrap();
assert_eq!(w, dst_frame.width().get());
assert_eq!(h, dst_frame.height().get());
assert_eq!(pix_fmt, dst_frame.pix_fmt());
assert_eq!(pretty_hex(&want), pretty_hex(&raw));
}
#[test]
fn test_crop_twice() {
#[rustfmt::skip]
let frame1 = Frame::from_raw(
&[0,0,0, 1,1,1, 2,2,2, 3,3,3, 4,4,4,
5,5,5, 6,6,6, 7,7,7, 8,8,8, 9,9,9,
10,10,10, 11,11,11, 12,12,12, 13,13,13, 14,14,14,
15,15,15, 16,16,16, 17,17,17, 18,18,18, 19,19,19,
20,20,20, 21,21,21, 22,22,22, 23,23,23, 24,24,24],
PixelFormat::RGB24,
NonZeroU16::new(5).unwrap(),
NonZeroU16::new(5).unwrap(),
1,
)
.unwrap();
let mut frame2 = Frame::new();
crop(
&frame1,
&mut frame2,
1,
1,
NonZeroU16::new(3).unwrap(),
NonZeroU16::new(3).unwrap(),
)
.unwrap();
let mut raw = Vec::new();
frame2.copy_to_buffer(&mut raw, 1).unwrap();
assert_eq!(3, frame2.width().get());
assert_eq!(3, frame2.height().get());
#[rustfmt::skip]
let want = &[6,6,6, 7,7,7, 8,8,8,
11,11,11, 12,12,12, 13,13,13,
16,16,16, 17,17,17, 18,18,18];
assert_eq!(pretty_hex(&want), pretty_hex(&raw));
let mut frame3 = Frame::new();
crop(
&frame2,
&mut frame3,
1,
1,
NonZeroU16::new(1).unwrap(),
NonZeroU16::new(1).unwrap(),
)
.unwrap();
let mut raw = Vec::new();
frame3.copy_to_buffer(&mut raw, 1).unwrap();
assert_eq!(1, frame3.width().get());
assert_eq!(1, frame3.height().get());
assert_eq!(pretty_hex(&[12, 12, 12]), pretty_hex(&raw));
}
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
1, 0, 2, 2,
CropError::OutOfBounds(1, 2, 2);
"width out of bounds"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
0, 1, 2, 2,
CropError::OutOfBounds(1, 2, 2);
"height out of bounds"
)]
#[test_case(
2, 2, PixelFormat::RGB24,
&[0,0,0, 1,1,1,
2,2,2, 3,3,3],
0, 65535, 2, 2,
CropError::Add;
"add"
)]
fn test_crop_error(
src_width: u16,
src_height: u16,
pix_fmt: PixelFormat,
input: &[u8],
x: u16,
y: u16,
w: u16,
h: u16,
want: CropError,
) {
let src_frame = Frame::from_raw(
input,
pix_fmt,
NonZeroU16::new(src_width).unwrap(),
NonZeroU16::new(src_height).unwrap(),
1,
)
.unwrap();
let result = crop(
&src_frame,
&mut Frame::new(),
x,
y,
NonZeroU16::new(w).unwrap(),
NonZeroU16::new(h).unwrap(),
);
assert_eq!(result, Err(want));
}
}