image/imageops/
affine.rs

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