1use crate::core::errors::ImageProcessError;
4use crate::processors::types::CropMode;
5use crate::utils::image;
6use ::image::RgbImage;
7
8#[derive(Debug)]
13pub struct Crop {
14 crop_size: [u32; 2],
16 crop_mode: CropMode,
18}
19
20impl Crop {
21 pub fn new(crop_size: [u32; 2], crop_mode: CropMode) -> Result<Self, ImageProcessError> {
42 image::check_image_size(&crop_size)?;
43 Ok(Self {
44 crop_size,
45 crop_mode,
46 })
47 }
48
49 pub fn crop_size(&self) -> [u32; 2] {
55 self.crop_size
56 }
57
58 pub fn crop_mode(&self) -> &CropMode {
64 &self.crop_mode
65 }
66
67 pub fn process(&self, img: &RgbImage) -> Result<RgbImage, ImageProcessError> {
91 let (img_width, img_height) = img.dimensions();
92 let [crop_width, crop_height] = self.crop_size;
93
94 if crop_width > img_width || crop_height > img_height {
96 return Err(ImageProcessError::CropSizeTooLarge);
97 }
98
99 if crop_width == img_width && crop_height == img_height {
101 return Ok(img.clone());
102 }
103
104 let (x, y) = self.calculate_crop_position(img_width, img_height)?;
105
106 image::validate_crop_bounds(img_width, img_height, x, y, crop_width, crop_height)?;
108
109 let coords = (x, y, x + crop_width, y + crop_height);
111 image::slice_image(img, coords)
112 }
113
114 fn calculate_crop_position(
126 &self,
127 img_width: u32,
128 img_height: u32,
129 ) -> Result<(u32, u32), ImageProcessError> {
130 let [crop_width, crop_height] = self.crop_size;
131
132 match self.crop_mode {
133 CropMode::Center => {
134 image::calculate_center_crop_coords(img_width, img_height, crop_width, crop_height)
135 }
136 CropMode::TopLeft => Ok((0, 0)),
137 CropMode::TopRight => {
138 if crop_width > img_width {
139 return Err(ImageProcessError::CropSizeTooLarge);
140 }
141 Ok((img_width - crop_width, 0))
142 }
143 CropMode::BottomLeft => {
144 if crop_height > img_height {
145 return Err(ImageProcessError::CropSizeTooLarge);
146 }
147 Ok((0, img_height - crop_height))
148 }
149 CropMode::BottomRight => {
150 if crop_width > img_width || crop_height > img_height {
151 return Err(ImageProcessError::CropSizeTooLarge);
152 }
153 Ok((img_width - crop_width, img_height - crop_height))
154 }
155 CropMode::Custom { x, y } => {
156 if x + crop_width > img_width || y + crop_height > img_height {
158 return Err(ImageProcessError::CropOutOfBounds);
159 }
160 Ok((x, y))
161 }
162 }
163 }
164
165 pub fn process_batch(&self, images: &[RgbImage]) -> Result<Vec<RgbImage>, ImageProcessError> {
176 images.iter().map(|img| self.process(img)).collect()
177 }
178
179 pub fn set_crop_size(&mut self, crop_size: [u32; 2]) -> Result<(), ImageProcessError> {
190 image::check_image_size(&crop_size)?;
191 self.crop_size = crop_size;
192 Ok(())
193 }
194
195 pub fn set_crop_mode(&mut self, crop_mode: CropMode) {
201 self.crop_mode = crop_mode;
202 }
203
204 pub fn can_crop(&self, img_width: u32, img_height: u32) -> bool {
216 let [crop_width, crop_height] = self.crop_size;
217 crop_width <= img_width && crop_height <= img_height
218 }
219
220 pub fn aspect_ratio(&self) -> f32 {
226 let [crop_width, crop_height] = self.crop_size;
227 crop_width as f32 / crop_height as f32
228 }
229}
230
231impl Default for Crop {
232 fn default() -> Self {
234 Self {
235 crop_size: [224, 224],
236 crop_mode: CropMode::Center,
237 }
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use ::image::{Rgb, RgbImage};
245
246 fn create_test_image(width: u32, height: u32) -> RgbImage {
247 RgbImage::from_pixel(width, height, Rgb([255, 0, 0]))
248 }
249
250 #[test]
251 fn test_crop_center() {
252 let crop = Crop::new([100, 100], CropMode::Center).unwrap();
253 let img = create_test_image(200, 200);
254 let cropped = crop.process(&img).unwrap();
255 assert_eq!(cropped.dimensions(), (100, 100));
256 }
257
258 #[test]
259 fn test_crop_top_left() {
260 let crop = Crop::new([100, 100], CropMode::TopLeft).unwrap();
261 let img = create_test_image(200, 200);
262 let cropped = crop.process(&img).unwrap();
263 assert_eq!(cropped.dimensions(), (100, 100));
264 }
265
266 #[test]
267 fn test_crop_custom() {
268 let crop = Crop::new([100, 100], CropMode::Custom { x: 50, y: 50 }).unwrap();
269 let img = create_test_image(200, 200);
270 let cropped = crop.process(&img).unwrap();
271 assert_eq!(cropped.dimensions(), (100, 100));
272
273 let crop_oob = Crop::new([100, 100], CropMode::Custom { x: 150, y: 150 }).unwrap();
275 assert!(crop_oob.process(&img).is_err());
276 }
277
278 #[test]
279 fn test_crop_size_too_large() {
280 let crop = Crop::new([300, 300], CropMode::Center).unwrap();
281 let img = create_test_image(200, 200);
282 assert!(crop.process(&img).is_err());
283 }
284
285 #[test]
286 fn test_crop_same_size() {
287 let crop = Crop::new([200, 200], CropMode::Center).unwrap();
288 let img = create_test_image(200, 200);
289 let cropped = crop.process(&img).unwrap();
290 assert_eq!(cropped.dimensions(), (200, 200));
291 }
292
293 #[test]
294 fn test_can_crop() {
295 let crop = Crop::new([100, 100], CropMode::Center).unwrap();
296 assert!(crop.can_crop(200, 200));
297 assert!(crop.can_crop(100, 100));
298 assert!(!crop.can_crop(50, 50));
299 }
300
301 #[test]
302 fn test_aspect_ratio() {
303 let crop = Crop::new([200, 100], CropMode::Center).unwrap();
304 assert_eq!(crop.aspect_ratio(), 2.0);
305 }
306
307 #[test]
308 fn test_process_batch() {
309 let crop = Crop::new([100, 100], CropMode::Center).unwrap();
310 let images = vec![create_test_image(200, 200), create_test_image(300, 300)];
311 let cropped = crop.process_batch(&images).unwrap();
312 assert_eq!(cropped.len(), 2);
313 assert_eq!(cropped[0].dimensions(), (100, 100));
314 assert_eq!(cropped[1].dimensions(), (100, 100));
315 }
316}