1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use crate::errors::SicImageEngineError;
use crate::operations::ImageOperation;
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use sic_core::image::{DynamicImage, GenericImageView};
use sic_core::{image, SicImage};

pub struct Crop {
    // top left anchor
    anchor_left: (u32, u32),
    // bottom right anchor
    anchor_right: (u32, u32),
}

impl Crop {
    pub fn new(anchor_left: (u32, u32), anchor_right: (u32, u32)) -> Self {
        Self {
            anchor_left,
            anchor_right,
        }
    }
}

impl ImageOperation for Crop {
    fn apply_operation(&self, image: &mut SicImage) -> Result<(), SicImageEngineError> {
        match image {
            SicImage::Static(image) => crop_impl(image, self),
            SicImage::Animated(image) => crop_animated_image(image.frames_mut(), self),
        }
    }
}

fn crop_animated_image(frames: &mut [image::Frame], cfg: &Crop) -> Result<(), SicImageEngineError> {
    // Do the in-bounds check on the first frame only: setup
    let index = 0;
    let frame = frames
        .get(index)
        .ok_or(SicImageEngineError::AnimatedFrameUnobtainable(index))?;

    let selection = CropSelection::new(
        cfg.anchor_left.0,
        cfg.anchor_left.1,
        cfg.anchor_right.0,
        cfg.anchor_right.1,
    );
    let (x, y, width, height) = selection.box_dimensions();

    // The in-bounds check
    let _ = selection
        .dimensions_are_ok()
        .and_then(|selection| selection.fits_within(frame.buffer()))?;

    // Crop the frames
    frames.par_iter_mut().for_each(|frame| {
        *frame.buffer_mut() =
            image::imageops::crop(frame.buffer_mut(), x, y, width, height).to_image();
    });

    Ok(())
}

fn crop_impl(image: &mut DynamicImage, cfg: &Crop) -> Result<(), SicImageEngineError> {
    let lx = cfg.anchor_left.0;
    let ly = cfg.anchor_left.1;
    let rx = cfg.anchor_right.0;
    let ry = cfg.anchor_right.1;

    let selection = CropSelection::new(lx, ly, rx, ry);
    // 1. verify that the top left anchor is smaller than the bottom right anchor
    // 2. verify that the selection is within the bounds of the image
    selection
        .dimensions_are_ok()
        .and_then(|selection| selection.fits_within(image))
        .map(|_| *image = image.crop(lx, ly, rx - lx, ry - ly))
}

struct CropSelection {
    lx: u32,
    ly: u32,
    rx: u32,
    ry: u32,
}

impl CropSelection {
    pub(crate) fn new(lx: u32, ly: u32, rx: u32, ry: u32) -> Self {
        Self { lx, ly, rx, ry }
    }

    pub(crate) fn dimensions_are_ok(&self) -> Result<&Self, SicImageEngineError> {
        if self.are_dimensions_incorrect() {
            Err(SicImageEngineError::CropInvalidSelection(
                self.lx, self.ly, self.rx, self.ry,
            ))
        } else {
            Ok(self)
        }
    }

    // returns a tuple containing the x, y, width and height
    fn box_dimensions(&self) -> (u32, u32, u32, u32) {
        (self.lx, self.ly, self.rx - self.lx, self.ry - self.ly)
    }

    fn fits_within<I: GenericImageView>(
        &self,
        bounded_image: &I,
    ) -> Result<&Self, SicImageEngineError> {
        let (dim_x, dim_y) = bounded_image.dimensions();

        match (
            self.lx <= dim_x,
            self.ly <= dim_y,
            self.rx <= dim_x,
            self.ry <= dim_y,
        ) {
            (true, true, true, true) => Ok(self),
            _ => Err(SicImageEngineError::CropCoordinateOutOfBounds(
                dim_x, dim_y, self.lx, self.ly, self.rx, self.ry,
            )),
        }
    }

    fn are_dimensions_incorrect(&self) -> bool {
        (self.rx <= self.lx) || (self.ry <= self.ly)
    }
}