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> {
46 image::check_image_size(&crop_size)?;
47 Ok(Self {
48 crop_size,
49 crop_mode,
50 })
51 }
52
53 pub fn crop_size(&self) -> [u32; 2] {
59 self.crop_size
60 }
61
62 pub fn crop_mode(&self) -> &CropMode {
68 &self.crop_mode
69 }
70
71 pub fn process(&self, img: &RgbImage) -> Result<RgbImage, ImageProcessError> {
98 let (img_width, img_height) = img.dimensions();
99 let [crop_width, crop_height] = self.crop_size;
100
101 if crop_width > img_width || crop_height > img_height {
103 return Err(ImageProcessError::CropSizeTooLarge);
104 }
105
106 if crop_width == img_width && crop_height == img_height {
108 return Ok(img.clone());
109 }
110
111 let (x, y) = self.calculate_crop_position(img_width, img_height)?;
112
113 image::validate_crop_bounds(img_width, img_height, x, y, crop_width, crop_height)?;
115
116 let coords = (x, y, x + crop_width, y + crop_height);
118 image::slice_image(img, coords)
119 }
120
121 fn calculate_crop_position(
133 &self,
134 img_width: u32,
135 img_height: u32,
136 ) -> Result<(u32, u32), ImageProcessError> {
137 let [crop_width, crop_height] = self.crop_size;
138
139 match self.crop_mode {
140 CropMode::Center => {
141 image::calculate_center_crop_coords(img_width, img_height, crop_width, crop_height)
142 }
143 CropMode::TopLeft => Ok((0, 0)),
144 CropMode::TopRight => {
145 if crop_width > img_width {
146 return Err(ImageProcessError::CropSizeTooLarge);
147 }
148 Ok((img_width - crop_width, 0))
149 }
150 CropMode::BottomLeft => {
151 if crop_height > img_height {
152 return Err(ImageProcessError::CropSizeTooLarge);
153 }
154 Ok((0, img_height - crop_height))
155 }
156 CropMode::BottomRight => {
157 if crop_width > img_width || crop_height > img_height {
158 return Err(ImageProcessError::CropSizeTooLarge);
159 }
160 Ok((img_width - crop_width, img_height - crop_height))
161 }
162 CropMode::Custom { x, y } => {
163 if x + crop_width > img_width || y + crop_height > img_height {
165 return Err(ImageProcessError::CropOutOfBounds);
166 }
167 Ok((x, y))
168 }
169 }
170 }
171
172 pub fn process_batch(&self, images: &[RgbImage]) -> Result<Vec<RgbImage>, ImageProcessError> {
183 images.iter().map(|img| self.process(img)).collect()
184 }
185
186 pub fn set_crop_size(&mut self, crop_size: [u32; 2]) -> Result<(), ImageProcessError> {
197 image::check_image_size(&crop_size)?;
198 self.crop_size = crop_size;
199 Ok(())
200 }
201
202 pub fn set_crop_mode(&mut self, crop_mode: CropMode) {
208 self.crop_mode = crop_mode;
209 }
210
211 pub fn can_crop(&self, img_width: u32, img_height: u32) -> bool {
223 let [crop_width, crop_height] = self.crop_size;
224 crop_width <= img_width && crop_height <= img_height
225 }
226
227 pub fn aspect_ratio(&self) -> f32 {
233 let [crop_width, crop_height] = self.crop_size;
234 crop_width as f32 / crop_height as f32
235 }
236}
237
238impl Default for Crop {
239 fn default() -> Self {
241 Self {
242 crop_size: [224, 224],
243 crop_mode: CropMode::Center,
244 }
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use ::image::{Rgb, RgbImage};
252
253 fn create_test_image(width: u32, height: u32) -> RgbImage {
254 RgbImage::from_pixel(width, height, Rgb([255, 0, 0]))
255 }
256
257 #[test]
258 fn test_crop_center() -> Result<(), ImageProcessError> {
259 let crop = Crop::new([100, 100], CropMode::Center)?;
260 let img = create_test_image(200, 200);
261 let cropped = crop.process(&img)?;
262 assert_eq!(cropped.dimensions(), (100, 100));
263 Ok(())
264 }
265
266 #[test]
267 fn test_crop_top_left() -> Result<(), ImageProcessError> {
268 let crop = Crop::new([100, 100], CropMode::TopLeft)?;
269 let img = create_test_image(200, 200);
270 let cropped = crop.process(&img)?;
271 assert_eq!(cropped.dimensions(), (100, 100));
272 Ok(())
273 }
274
275 #[test]
276 fn test_crop_custom() -> Result<(), ImageProcessError> {
277 let crop = Crop::new([100, 100], CropMode::Custom { x: 50, y: 50 })?;
278 let img = create_test_image(200, 200);
279 let cropped = crop.process(&img)?;
280 assert_eq!(cropped.dimensions(), (100, 100));
281
282 let crop_oob = Crop::new([100, 100], CropMode::Custom { x: 150, y: 150 })?;
284 assert!(crop_oob.process(&img).is_err());
285 Ok(())
286 }
287
288 #[test]
289 fn test_crop_size_too_large() -> Result<(), ImageProcessError> {
290 let crop = Crop::new([300, 300], CropMode::Center)?;
291 let img = create_test_image(200, 200);
292 assert!(crop.process(&img).is_err());
293 Ok(())
294 }
295
296 #[test]
297 fn test_crop_same_size() -> Result<(), ImageProcessError> {
298 let crop = Crop::new([200, 200], CropMode::Center)?;
299 let img = create_test_image(200, 200);
300 let cropped = crop.process(&img)?;
301 assert_eq!(cropped.dimensions(), (200, 200));
302 Ok(())
303 }
304
305 #[test]
306 fn test_can_crop() -> Result<(), ImageProcessError> {
307 let crop = Crop::new([100, 100], CropMode::Center)?;
308 assert!(crop.can_crop(200, 200));
309 assert!(crop.can_crop(100, 100));
310 assert!(!crop.can_crop(50, 50));
311 Ok(())
312 }
313
314 #[test]
315 fn test_aspect_ratio() -> Result<(), ImageProcessError> {
316 let crop = Crop::new([200, 100], CropMode::Center)?;
317 assert_eq!(crop.aspect_ratio(), 2.0);
318 Ok(())
319 }
320
321 #[test]
322 fn test_process_batch() -> Result<(), ImageProcessError> {
323 let crop = Crop::new([100, 100], CropMode::Center)?;
324 let images = vec![create_test_image(200, 200), create_test_image(300, 300)];
325 let cropped = crop.process_batch(&images)?;
326 assert_eq!(cropped.len(), 2);
327 assert_eq!(cropped[0].dimensions(), (100, 100));
328 assert_eq!(cropped[1].dimensions(), (100, 100));
329 Ok(())
330 }
331}