image/imageops/
affine.rs

1//! Functions for performing affine transformations.
2
3use crate::error::{ImageError, ParameterError, ParameterErrorKind};
4use crate::image::{GenericImage, GenericImageView};
5use crate::traits::Pixel;
6use crate::ImageBuffer;
7
8/// Rotate an image 90 degrees clockwise.
9pub fn rotate90<I: GenericImageView>(
10    image: &I,
11) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
12where
13    I::Pixel: 'static,
14{
15    let (width, height) = image.dimensions();
16    let mut out = ImageBuffer::new(height, width);
17    let _ = rotate90_in(image, &mut out);
18    out
19}
20
21/// Rotate an image 180 degrees clockwise.
22pub fn rotate180<I: GenericImageView>(
23    image: &I,
24) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
25where
26    I::Pixel: 'static,
27{
28    let (width, height) = image.dimensions();
29    let mut out = ImageBuffer::new(width, height);
30    let _ = rotate180_in(image, &mut out);
31    out
32}
33
34/// Rotate an image 270 degrees clockwise.
35pub fn rotate270<I: GenericImageView>(
36    image: &I,
37) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
38where
39    I::Pixel: 'static,
40{
41    let (width, height) = image.dimensions();
42    let mut out = ImageBuffer::new(height, width);
43    let _ = rotate270_in(image, &mut out);
44    out
45}
46
47/// Rotate an image 90 degrees clockwise and put the result into the destination [`ImageBuffer`].
48pub fn rotate90_in<I, Container>(
49    image: &I,
50    destination: &mut ImageBuffer<I::Pixel, Container>,
51) -> crate::ImageResult<()>
52where
53    I: GenericImageView,
54    I::Pixel: 'static,
55    Container: std::ops::DerefMut<Target = [<I::Pixel as Pixel>::Subpixel]>,
56{
57    let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions());
58    if w0 != h1 || h0 != w1 {
59        return Err(ImageError::Parameter(ParameterError::from_kind(
60            ParameterErrorKind::DimensionMismatch,
61        )));
62    }
63
64    for y in 0..h0 {
65        for x in 0..w0 {
66            let p = image.get_pixel(x, y);
67            destination.put_pixel(h0 - y - 1, x, p);
68        }
69    }
70    Ok(())
71}
72
73/// Rotate an image 180 degrees clockwise and put the result into the destination [`ImageBuffer`].
74pub fn rotate180_in<I, Container>(
75    image: &I,
76    destination: &mut ImageBuffer<I::Pixel, Container>,
77) -> crate::ImageResult<()>
78where
79    I: GenericImageView,
80    I::Pixel: 'static,
81    Container: std::ops::DerefMut<Target = [<I::Pixel as Pixel>::Subpixel]>,
82{
83    let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions());
84    if w0 != w1 || h0 != h1 {
85        return Err(ImageError::Parameter(ParameterError::from_kind(
86            ParameterErrorKind::DimensionMismatch,
87        )));
88    }
89
90    for y in 0..h0 {
91        for x in 0..w0 {
92            let p = image.get_pixel(x, y);
93            destination.put_pixel(w0 - x - 1, h0 - y - 1, p);
94        }
95    }
96    Ok(())
97}
98
99/// Rotate an image 270 degrees clockwise and put the result into the destination [`ImageBuffer`].
100pub fn rotate270_in<I, Container>(
101    image: &I,
102    destination: &mut ImageBuffer<I::Pixel, Container>,
103) -> crate::ImageResult<()>
104where
105    I: GenericImageView,
106    I::Pixel: 'static,
107    Container: std::ops::DerefMut<Target = [<I::Pixel as Pixel>::Subpixel]>,
108{
109    let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions());
110    if w0 != h1 || h0 != w1 {
111        return Err(ImageError::Parameter(ParameterError::from_kind(
112            ParameterErrorKind::DimensionMismatch,
113        )));
114    }
115
116    for y in 0..h0 {
117        for x in 0..w0 {
118            let p = image.get_pixel(x, y);
119            destination.put_pixel(y, w0 - x - 1, p);
120        }
121    }
122    Ok(())
123}
124
125/// Flip an image horizontally
126pub fn flip_horizontal<I: GenericImageView>(
127    image: &I,
128) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
129where
130    I::Pixel: 'static,
131{
132    let (width, height) = image.dimensions();
133    let mut out = ImageBuffer::new(width, height);
134    let _ = flip_horizontal_in(image, &mut out);
135    out
136}
137
138/// Flip an image vertically
139pub fn flip_vertical<I: GenericImageView>(
140    image: &I,
141) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>>
142where
143    I::Pixel: 'static,
144{
145    let (width, height) = image.dimensions();
146    let mut out = ImageBuffer::new(width, height);
147    let _ = flip_vertical_in(image, &mut out);
148    out
149}
150
151/// Flip an image horizontally and put the result into the destination [`ImageBuffer`].
152pub fn flip_horizontal_in<I, Container>(
153    image: &I,
154    destination: &mut ImageBuffer<I::Pixel, Container>,
155) -> crate::ImageResult<()>
156where
157    I: GenericImageView,
158    I::Pixel: 'static,
159    Container: std::ops::DerefMut<Target = [<I::Pixel as Pixel>::Subpixel]>,
160{
161    let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions());
162    if w0 != w1 || h0 != h1 {
163        return Err(ImageError::Parameter(ParameterError::from_kind(
164            ParameterErrorKind::DimensionMismatch,
165        )));
166    }
167
168    for y in 0..h0 {
169        for x in 0..w0 {
170            let p = image.get_pixel(x, y);
171            destination.put_pixel(w0 - x - 1, y, p);
172        }
173    }
174    Ok(())
175}
176
177/// Flip an image vertically and put the result into the destination [`ImageBuffer`].
178pub fn flip_vertical_in<I, Container>(
179    image: &I,
180    destination: &mut ImageBuffer<I::Pixel, Container>,
181) -> crate::ImageResult<()>
182where
183    I: GenericImageView,
184    I::Pixel: 'static,
185    Container: std::ops::DerefMut<Target = [<I::Pixel as Pixel>::Subpixel]>,
186{
187    let ((w0, h0), (w1, h1)) = (image.dimensions(), destination.dimensions());
188    if w0 != w1 || h0 != h1 {
189        return Err(ImageError::Parameter(ParameterError::from_kind(
190            ParameterErrorKind::DimensionMismatch,
191        )));
192    }
193
194    for y in 0..h0 {
195        for x in 0..w0 {
196            let p = image.get_pixel(x, y);
197            destination.put_pixel(x, h0 - 1 - y, p);
198        }
199    }
200    Ok(())
201}
202
203/// Rotate an image 180 degrees clockwise in place.
204pub fn rotate180_in_place<I: GenericImage>(image: &mut I) {
205    let (width, height) = image.dimensions();
206
207    for y in 0..height / 2 {
208        for x in 0..width {
209            let p = image.get_pixel(x, y);
210
211            let x2 = width - x - 1;
212            let y2 = height - y - 1;
213
214            let p2 = image.get_pixel(x2, y2);
215            image.put_pixel(x, y, p2);
216            image.put_pixel(x2, y2, p);
217        }
218    }
219
220    if height % 2 != 0 {
221        let middle = height / 2;
222
223        for x in 0..width / 2 {
224            let p = image.get_pixel(x, middle);
225            let x2 = width - x - 1;
226
227            let p2 = image.get_pixel(x2, middle);
228            image.put_pixel(x, middle, p2);
229            image.put_pixel(x2, middle, p);
230        }
231    }
232}
233
234/// Flip an image horizontally in place.
235pub fn flip_horizontal_in_place<I: GenericImage>(image: &mut I) {
236    let (width, height) = image.dimensions();
237
238    for y in 0..height {
239        for x in 0..width / 2 {
240            let x2 = width - x - 1;
241            let p2 = image.get_pixel(x2, y);
242            let p = image.get_pixel(x, y);
243            image.put_pixel(x2, y, p);
244            image.put_pixel(x, y, p2);
245        }
246    }
247}
248
249/// Flip an image vertically in place.
250pub fn flip_vertical_in_place<I: GenericImage>(image: &mut I) {
251    let (width, height) = image.dimensions();
252
253    for y in 0..height / 2 {
254        for x in 0..width {
255            let y2 = height - y - 1;
256            let p2 = image.get_pixel(x, y2);
257            let p = image.get_pixel(x, y);
258            image.put_pixel(x, y2, p);
259            image.put_pixel(x, y, p2);
260        }
261    }
262}
263
264#[cfg(test)]
265mod test {
266    use super::{
267        flip_horizontal, flip_horizontal_in_place, flip_vertical, flip_vertical_in_place,
268        rotate180, rotate180_in_place, rotate270, rotate90,
269    };
270    use crate::image::GenericImage;
271    use crate::traits::Pixel;
272    use crate::{GrayImage, ImageBuffer};
273
274    macro_rules! assert_pixels_eq {
275        ($actual:expr, $expected:expr) => {{
276            let actual_dim = $actual.dimensions();
277            let expected_dim = $expected.dimensions();
278
279            if actual_dim != expected_dim {
280                panic!(
281                    "dimensions do not match. \
282                     actual: {:?}, expected: {:?}",
283                    actual_dim, expected_dim
284                )
285            }
286
287            let diffs = pixel_diffs($actual, $expected);
288
289            if !diffs.is_empty() {
290                let mut err = "".to_string();
291
292                let diff_messages = diffs
293                    .iter()
294                    .take(5)
295                    .map(|d| format!("\nactual: {:?}, expected {:?} ", d.0, d.1))
296                    .collect::<Vec<_>>()
297                    .join("");
298
299                err.push_str(&diff_messages);
300                panic!("pixels do not match. {:?}", err)
301            }
302        }};
303    }
304
305    #[test]
306    fn test_rotate90() {
307        let image: GrayImage =
308            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
309
310        let expected: GrayImage =
311            ImageBuffer::from_raw(2, 3, vec![10u8, 0u8, 11u8, 1u8, 12u8, 2u8]).unwrap();
312
313        assert_pixels_eq!(&rotate90(&image), &expected);
314    }
315
316    #[test]
317    fn test_rotate180() {
318        let image: GrayImage =
319            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
320
321        let expected: GrayImage =
322            ImageBuffer::from_raw(3, 2, vec![12u8, 11u8, 10u8, 2u8, 1u8, 0u8]).unwrap();
323
324        assert_pixels_eq!(&rotate180(&image), &expected);
325    }
326
327    #[test]
328    fn test_rotate270() {
329        let image: GrayImage =
330            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
331
332        let expected: GrayImage =
333            ImageBuffer::from_raw(2, 3, vec![2u8, 12u8, 1u8, 11u8, 0u8, 10u8]).unwrap();
334
335        assert_pixels_eq!(&rotate270(&image), &expected);
336    }
337
338    #[test]
339    fn test_rotate180_in_place() {
340        let mut image: GrayImage =
341            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
342
343        let expected: GrayImage =
344            ImageBuffer::from_raw(3, 2, vec![12u8, 11u8, 10u8, 2u8, 1u8, 0u8]).unwrap();
345
346        rotate180_in_place(&mut image);
347
348        assert_pixels_eq!(&image, &expected);
349    }
350
351    #[test]
352    fn test_flip_horizontal() {
353        let image: GrayImage =
354            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
355
356        let expected: GrayImage =
357            ImageBuffer::from_raw(3, 2, vec![2u8, 1u8, 0u8, 12u8, 11u8, 10u8]).unwrap();
358
359        assert_pixels_eq!(&flip_horizontal(&image), &expected);
360    }
361
362    #[test]
363    fn test_flip_vertical() {
364        let image: GrayImage =
365            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
366
367        let expected: GrayImage =
368            ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 0u8, 1u8, 2u8]).unwrap();
369
370        assert_pixels_eq!(&flip_vertical(&image), &expected);
371    }
372
373    #[test]
374    fn test_flip_horizontal_in_place() {
375        let mut image: GrayImage =
376            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
377
378        let expected: GrayImage =
379            ImageBuffer::from_raw(3, 2, vec![2u8, 1u8, 0u8, 12u8, 11u8, 10u8]).unwrap();
380
381        flip_horizontal_in_place(&mut image);
382
383        assert_pixels_eq!(&image, &expected);
384    }
385
386    #[test]
387    fn test_flip_vertical_in_place() {
388        let mut image: GrayImage =
389            ImageBuffer::from_raw(3, 2, vec![0u8, 1u8, 2u8, 10u8, 11u8, 12u8]).unwrap();
390
391        let expected: GrayImage =
392            ImageBuffer::from_raw(3, 2, vec![10u8, 11u8, 12u8, 0u8, 1u8, 2u8]).unwrap();
393
394        flip_vertical_in_place(&mut image);
395
396        assert_pixels_eq!(&image, &expected);
397    }
398
399    #[allow(clippy::type_complexity)]
400    fn pixel_diffs<I, J, P>(left: &I, right: &J) -> Vec<((u32, u32, P), (u32, u32, P))>
401    where
402        I: GenericImage<Pixel = P>,
403        J: GenericImage<Pixel = P>,
404        P: Pixel + Eq,
405    {
406        left.pixels()
407            .zip(right.pixels())
408            .filter(|&(p, q)| p != q)
409            .collect::<Vec<_>>()
410    }
411}