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: u32, height: u32) = image.dimensions();
16 let mut out: ImageBuffer<::Pixel, …> = ImageBuffer::new(width:height, 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: u32, height: u32) = image.dimensions();
29 let mut out: ImageBuffer<::Pixel, …> = 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: u32, height: u32) = image.dimensions();
42 let mut out: ImageBuffer<::Pixel, …> = ImageBuffer::new(width:height, 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: u32, h0: u32), (w1: u32, h1: u32)) = (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: u32 in 0..h0 {
65 for x: u32 in 0..w0 {
66 let p: ::Pixel = image.get_pixel(x, y);
67 destination.put_pixel(x:h0 - y - 1, y:x, pixel: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: u32, h0: u32), (w1: u32, h1: u32)) = (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: u32 in 0..h0 {
91 for x: u32 in 0..w0 {
92 let p: ::Pixel = image.get_pixel(x, y);
93 destination.put_pixel(x:w0 - x - 1, y:h0 - y - 1, pixel: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: u32, h0: u32), (w1: u32, h1: u32)) = (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: u32 in 0..h0 {
117 for x: u32 in 0..w0 {
118 let p: ::Pixel = image.get_pixel(x, y);
119 destination.put_pixel(x:y, y:w0 - x - 1, pixel: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: u32, height: u32) = image.dimensions();
133 let mut out: ImageBuffer<::Pixel, …> = 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: u32, height: u32) = image.dimensions();
146 let mut out: ImageBuffer<::Pixel, …> = 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: u32, h0: u32), (w1: u32, h1: u32)) = (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: u32 in 0..h0 {
169 for x: u32 in 0..w0 {
170 let p: ::Pixel = image.get_pixel(x, y);
171 destination.put_pixel(x:w0 - x - 1, y, pixel: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: u32, h0: u32), (w1: u32, h1: u32)) = (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: u32 in 0..h0 {
195 for x: u32 in 0..w0 {
196 let p: ::Pixel = image.get_pixel(x, y);
197 destination.put_pixel(x, y:h0 - 1 - y, pixel: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: u32, height: u32) = image.dimensions();
237
238 for y: u32 in 0..height {
239 for x: u32 in 0..width / 2 {
240 let x2: u32 = width - x - 1;
241 let p2: ::Pixel = image.get_pixel(x:x2, y);
242 let p: ::Pixel = image.get_pixel(x, y);
243 image.put_pixel(x:x2, y, pixel:p);
244 image.put_pixel(x, y, pixel: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: u32, height: u32) = image.dimensions();
252
253 for y: u32 in 0..height / 2 {
254 for x: u32 in 0..width {
255 let y2: u32 = height - y - 1;
256 let p2: ::Pixel = image.get_pixel(x, y:y2);
257 let p: ::Pixel = image.get_pixel(x, y);
258 image.put_pixel(x, y:y2, pixel:p);
259 image.put_pixel(x, y, pixel: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}
412