1 | //! Functions for performing affine transformations. |
2 | |
3 | use crate::error::{ImageError, ParameterError, ParameterErrorKind}; |
4 | use crate::image::{GenericImage, GenericImageView}; |
5 | use crate::traits::Pixel; |
6 | use crate::ImageBuffer; |
7 | |
8 | /// Rotate an image 90 degrees clockwise. |
9 | pub fn rotate90<I: GenericImageView>( |
10 | image: &I, |
11 | ) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> |
12 | where |
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. |
22 | pub fn rotate180<I: GenericImageView>( |
23 | image: &I, |
24 | ) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> |
25 | where |
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. |
35 | pub fn rotate270<I: GenericImageView>( |
36 | image: &I, |
37 | ) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> |
38 | where |
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`]. |
48 | pub fn rotate90_in<I, Container>( |
49 | image: &I, |
50 | destination: &mut ImageBuffer<I::Pixel, Container>, |
51 | ) -> crate::ImageResult<()> |
52 | where |
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`]. |
74 | pub fn rotate180_in<I, Container>( |
75 | image: &I, |
76 | destination: &mut ImageBuffer<I::Pixel, Container>, |
77 | ) -> crate::ImageResult<()> |
78 | where |
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`]. |
100 | pub fn rotate270_in<I, Container>( |
101 | image: &I, |
102 | destination: &mut ImageBuffer<I::Pixel, Container>, |
103 | ) -> crate::ImageResult<()> |
104 | where |
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 |
126 | pub fn flip_horizontal<I: GenericImageView>( |
127 | image: &I, |
128 | ) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> |
129 | where |
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 |
139 | pub fn flip_vertical<I: GenericImageView>( |
140 | image: &I, |
141 | ) -> ImageBuffer<I::Pixel, Vec<<I::Pixel as Pixel>::Subpixel>> |
142 | where |
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`]. |
152 | pub fn flip_horizontal_in<I, Container>( |
153 | image: &I, |
154 | destination: &mut ImageBuffer<I::Pixel, Container>, |
155 | ) -> crate::ImageResult<()> |
156 | where |
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`]. |
178 | pub fn flip_vertical_in<I, Container>( |
179 | image: &I, |
180 | destination: &mut ImageBuffer<I::Pixel, Container>, |
181 | ) -> crate::ImageResult<()> |
182 | where |
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. |
204 | pub 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. |
235 | pub 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. |
250 | pub 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)] |
265 | mod 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 | |