1use crate::definitions::{Clamp, Image};
5use crate::math::cast;
6use conv::ValueInto;
7use image::{GenericImageView, ImageBuffer, Pixel};
8#[cfg(feature = "rayon")]
9use rayon::prelude::*;
10use std::{cmp, ops::Mul};
11
12#[derive(Copy, Clone, Debug)]
13enum TransformationClass {
14 Translation,
15 Affine,
16 Projection,
17}
18
19#[derive(Copy, Clone, Debug)]
38pub struct Projection {
39 transform: [f32; 9],
40 inverse: [f32; 9],
41 class: TransformationClass,
42}
43
44impl Projection {
45 pub fn from_matrix(transform: [f32; 9]) -> Option<Projection> {
49 let transform = normalize(transform);
50 let class = class_from_matrix(transform);
51 try_inverse(&transform).map(|inverse| Projection {
52 transform,
53 inverse,
54 class,
55 })
56 }
57
58 pub fn and_then(self, other: Projection) -> Projection {
61 other * self
62 }
63
64 #[rustfmt::skip]
66 pub fn translate(tx: f32, ty: f32) -> Projection {
67 Projection {
68 transform: [
69 1.0, 0.0, tx,
70 0.0, 1.0, ty,
71 0.0, 0.0, 1.0
72 ],
73 inverse: [
74 1.0, 0.0, -tx,
75 0.0, 1.0, -ty,
76 0.0, 0.0, 1.0
77 ],
78 class: TransformationClass::Translation,
79 }
80 }
81
82 #[rustfmt::skip]
84 pub fn rotate(theta: f32) -> Projection {
85 let (s, c) = theta.sin_cos();
86 Projection {
87 transform: [
88 c, -s, 0.0,
89 s, c, 0.0,
90 0.0, 0.0, 1.0
91 ],
92 inverse: [
93 c, s, 0.0,
94 -s, c, 0.0,
95 0.0, 0.0, 1.0
96 ],
97 class: TransformationClass::Affine,
98 }
99 }
100
101 #[rustfmt::skip]
106 pub fn scale(sx: f32, sy: f32) -> Projection {
107 Projection {
108 transform: [
109 sx, 0.0, 0.0,
110 0.0, sy, 0.0,
111 0.0, 0.0, 1.0
112 ],
113 inverse: [
114 1.0 / sx, 0.0, 0.0,
115 0.0, 1.0 / sy, 0.0,
116 0.0, 0.0, 1.0
117 ],
118 class: TransformationClass::Affine,
119 }
120 }
121
122 pub fn invert(self) -> Projection {
124 Projection {
125 transform: self.inverse,
126 inverse: self.transform,
127 class: self.class,
128 }
129 }
130
131 pub fn from_control_points(from: [(f32, f32); 4], to: [(f32, f32); 4]) -> Option<Projection> {
133 use approx::AbsDiffEq;
134 use nalgebra::{linalg::SVD, OMatrix, OVector, U8};
135
136 let (xf1, yf1, xf2, yf2, xf3, yf3, xf4, yf4) = (
137 from[0].0 as f64,
138 from[0].1 as f64,
139 from[1].0 as f64,
140 from[1].1 as f64,
141 from[2].0 as f64,
142 from[2].1 as f64,
143 from[3].0 as f64,
144 from[3].1 as f64,
145 );
146
147 let (x1, y1, x2, y2, x3, y3, x4, y4) = (
148 to[0].0 as f64,
149 to[0].1 as f64,
150 to[1].0 as f64,
151 to[1].1 as f64,
152 to[2].0 as f64,
153 to[2].1 as f64,
154 to[3].0 as f64,
155 to[3].1 as f64,
156 );
157
158 #[rustfmt::skip]
159 let a = OMatrix::<_, U8, U8>::from_row_slice(&[
160 0.0, 0.0, 0.0, -xf1, -yf1, -1.0, y1 * xf1, y1 * yf1,
161 xf1, yf1, 1.0, 0.0, 0.0, 0.0, -x1 * xf1, -x1 * yf1,
162 0.0, 0.0, 0.0, -xf2, -yf2, -1.0, y2 * xf2, y2 * yf2,
163 xf2, yf2, 1.0, 0.0, 0.0, 0.0, -x2 * xf2, -x2 * yf2,
164 0.0, 0.0, 0.0, -xf3, -yf3, -1.0, y3 * xf3, y3 * yf3,
165 xf3, yf3, 1.0, 0.0, 0.0, 0.0, -x3 * xf3, -x3 * yf3,
166 0.0, 0.0, 0.0, -xf4, -yf4, -1.0, y4 * xf4, y4 * yf4,
167 xf4, yf4, 1.0, 0.0, 0.0, 0.0, -x4 * xf4, -x4 * yf4,
168 ]);
169
170 let b = OVector::<_, U8>::from_row_slice(&[-y1, x1, -y2, x2, -y3, x3, -y4, x4]);
171
172 SVD::try_new(a, true, true, f64::default_epsilon(), 0)
173 .and_then(|svd| svd.solve(&b, f64::default_epsilon()).ok())
174 .and_then(|h| {
175 let mut transform = [
176 h[0] as f32,
177 h[1] as f32,
178 h[2] as f32,
179 h[3] as f32,
180 h[4] as f32,
181 h[5] as f32,
182 h[6] as f32,
183 h[7] as f32,
184 1.0,
185 ];
186
187 transform = normalize(transform);
188 let class = class_from_matrix(transform);
189
190 try_inverse(&transform).map(|inverse| Projection {
191 transform,
192 inverse,
193 class,
194 })
195 })
196 }
197
198 #[inline(always)]
200 fn map_projective(&self, x: f32, y: f32) -> (f32, f32) {
201 let t = &self.transform;
202 let d = t[6] * x + t[7] * y + t[8];
203 (
204 (t[0] * x + t[1] * y + t[2]) / d,
205 (t[3] * x + t[4] * y + t[5]) / d,
206 )
207 }
208
209 #[inline(always)]
210 fn map_affine(&self, x: f32, y: f32) -> (f32, f32) {
211 let t = &self.transform;
212 ((t[0] * x + t[1] * y + t[2]), (t[3] * x + t[4] * y + t[5]))
213 }
214
215 #[inline(always)]
216 fn map_translation(&self, x: f32, y: f32) -> (f32, f32) {
217 let t = &self.transform;
218 let tx = t[2];
219 let ty = t[5];
220 (x + tx, y + ty)
221 }
222}
223
224impl Mul<Projection> for Projection {
225 type Output = Projection;
226
227 fn mul(self, rhs: Projection) -> Projection {
228 use TransformationClass as TC;
229 let t = mul3x3(self.transform, rhs.transform);
230 let i = mul3x3(rhs.inverse, self.inverse);
231
232 let class = match (self.class, rhs.class) {
233 (TC::Translation, TC::Translation) => TC::Translation,
234 (TC::Translation, TC::Affine) => TC::Affine,
235 (TC::Affine, TC::Translation) => TC::Affine,
236 (TC::Affine, TC::Affine) => TC::Affine,
237 (_, _) => TC::Projection,
238 };
239
240 Projection {
241 transform: t,
242 inverse: i,
243 class,
244 }
245 }
246}
247
248impl<'a, 'b> Mul<&'b Projection> for &'a Projection {
249 type Output = Projection;
250
251 fn mul(self, rhs: &Projection) -> Projection {
252 *self * *rhs
253 }
254}
255
256impl Mul<(f32, f32)> for Projection {
257 type Output = (f32, f32);
258
259 fn mul(self, rhs: (f32, f32)) -> (f32, f32) {
260 let (x, y) = rhs;
261 match self.class {
262 TransformationClass::Translation => self.map_translation(x, y),
263 TransformationClass::Affine => self.map_affine(x, y),
264 TransformationClass::Projection => self.map_projective(x, y),
265 }
266 }
267}
268
269impl<'a, 'b> Mul<&'b (f32, f32)> for &'a Projection {
270 type Output = (f32, f32);
271
272 fn mul(self, rhs: &(f32, f32)) -> (f32, f32) {
273 *self * *rhs
274 }
275}
276
277pub fn rotate_about_center<P>(
281 image: &Image<P>,
282 theta: f32,
283 interpolation: Interpolation,
284 default: P,
285) -> Image<P>
286where
287 P: Pixel + Send + Sync,
288 <P as Pixel>::Subpixel: Send + Sync,
289 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
290{
291 let (w, h) = image.dimensions();
292 rotate(
293 image,
294 (w as f32 / 2.0, h as f32 / 2.0),
295 theta,
296 interpolation,
297 default,
298 )
299}
300
301pub fn rotate<P>(
305 image: &Image<P>,
306 center: (f32, f32),
307 theta: f32,
308 interpolation: Interpolation,
309 default: P,
310) -> Image<P>
311where
312 P: Pixel + Send + Sync,
313 <P as Pixel>::Subpixel: Send + Sync,
314 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
315{
316 let (cx, cy) = center;
317 let projection =
318 Projection::translate(cx, cy) * Projection::rotate(theta) * Projection::translate(-cx, -cy);
319 warp(image, &projection, interpolation, default)
320}
321
322pub fn translate<P>(image: &Image<P>, t: (i32, i32)) -> Image<P>
328where
329 P: Pixel,
330{
331 let (width, height) = image.dimensions();
332 let (tx, ty) = t;
333 let (w, h) = (width as i32, height as i32);
334 let num_channels = P::CHANNEL_COUNT as usize;
335 let mut out = ImageBuffer::new(width, height);
336
337 for y in 0..height {
338 let y_in = cmp::max(0, cmp::min(y as i32 - ty, h - 1));
339
340 if tx > 0 {
341 let p_min = *image.get_pixel(0, y_in as u32);
342 for x in 0..tx.min(w) {
343 out.put_pixel(x as u32, y, p_min);
344 }
345
346 if tx < w {
347 let in_base = (y_in as usize * width as usize) * num_channels;
348 let out_base = (y as usize * width as usize + (tx as usize)) * num_channels;
349 let len = (w - tx) as usize * num_channels;
350 (*out)[out_base..][..len].copy_from_slice(&(**image)[in_base..][..len]);
351 }
352 } else {
353 let p_max = *image.get_pixel(width - 1, y_in as u32);
354 for x in (w + tx).max(0)..w {
355 out.put_pixel(x as u32, y, p_max);
356 }
357
358 if w + tx > 0 {
359 let in_base = (y_in as usize * width as usize + (tx.abs() as usize)) * num_channels;
360 let out_base = (y as usize * width as usize) * num_channels;
361 let len = (w + tx) as usize * num_channels;
362 (*out)[out_base..][..len].copy_from_slice(&(**image)[in_base..][..len]);
363 }
364 }
365 }
366
367 out
368}
369
370pub fn warp<P>(
378 image: &Image<P>,
379 projection: &Projection,
380 interpolation: Interpolation,
381 default: P,
382) -> Image<P>
383where
384 P: Pixel + Send + Sync,
385 <P as Pixel>::Subpixel: Send + Sync,
386 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
387{
388 let (width, height) = image.dimensions();
389 let mut out = ImageBuffer::new(width, height);
390 warp_into(image, projection, interpolation, default, &mut out);
391 out
392}
393
394pub fn warp_into<P>(
398 image: &Image<P>,
399 projection: &Projection,
400 interpolation: Interpolation,
401 default: P,
402 out: &mut Image<P>,
403) where
404 P: Pixel + Send + Sync,
405 <P as Pixel>::Subpixel: Send + Sync,
406 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32> + Sync,
407{
408 let projection = projection.invert();
409 let nn = |x, y| interpolate_nearest(image, x, y, default);
410 let bl = |x, y| interpolate_bilinear(image, x, y, default);
411 let bc = |x, y| interpolate_bicubic(image, x, y, default);
412 let wp = |x, y| projection.map_projective(x, y);
413 let wa = |x, y| projection.map_affine(x, y);
414 let wt = |x, y| projection.map_translation(x, y);
415 use Interpolation as I;
416 use TransformationClass as TC;
417
418 match (interpolation, projection.class) {
419 (I::Nearest, TC::Translation) => warp_inner(out, wt, nn),
420 (I::Nearest, TC::Affine) => warp_inner(out, wa, nn),
421 (I::Nearest, TC::Projection) => warp_inner(out, wp, nn),
422 (I::Bilinear, TC::Translation) => warp_inner(out, wt, bl),
423 (I::Bilinear, TC::Affine) => warp_inner(out, wa, bl),
424 (I::Bilinear, TC::Projection) => warp_inner(out, wp, bl),
425 (I::Bicubic, TC::Translation) => warp_inner(out, wt, bc),
426 (I::Bicubic, TC::Affine) => warp_inner(out, wa, bc),
427 (I::Bicubic, TC::Projection) => warp_inner(out, wp, bc),
428 }
429}
430
431pub fn warp_with<P, F>(
449 image: &Image<P>,
450 mapping: F,
451 interpolation: Interpolation,
452 default: P,
453) -> Image<P>
454where
455 F: Fn(f32, f32) -> (f32, f32) + Sync + Send,
456 P: Pixel + Send + Sync,
457 <P as Pixel>::Subpixel: Send + Sync,
458 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
459{
460 let (width, height) = image.dimensions();
461 let mut out = ImageBuffer::new(width, height);
462 warp_into_with(image, mapping, interpolation, default, &mut out);
463 out
464}
465
466pub fn warp_into_with<P, F>(
471 image: &Image<P>,
472 mapping: F,
473 interpolation: Interpolation,
474 default: P,
475 out: &mut Image<P>,
476) where
477 F: Fn(f32, f32) -> (f32, f32) + Send + Sync,
478 P: Pixel + Send + Sync,
479 <P as Pixel>::Subpixel: Send + Sync,
480 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
481{
482 let nn = |x, y| interpolate_nearest(image, x, y, default);
483 let bl = |x, y| interpolate_bilinear(image, x, y, default);
484 let bc = |x, y| interpolate_bicubic(image, x, y, default);
485 use Interpolation as I;
486
487 match interpolation {
488 I::Nearest => warp_inner(out, mapping, nn),
489 I::Bilinear => warp_inner(out, mapping, bl),
490 I::Bicubic => warp_inner(out, mapping, bc),
491 }
492}
493
494fn warp_inner<P, Fc, Fi>(out: &mut Image<P>, mapping: Fc, get_pixel: Fi)
497where
498 P: Pixel,
499 <P as Pixel>::Subpixel: Send + Sync,
500 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
501 Fc: Fn(f32, f32) -> (f32, f32) + Send + Sync,
502 Fi: Fn(f32, f32) -> P + Send + Sync,
503{
504 let width = out.width();
505 let raw_out = out.as_mut();
506 let pitch = P::CHANNEL_COUNT as usize * width as usize;
507
508 #[cfg(feature = "rayon")]
509 let chunks = raw_out.par_chunks_mut(pitch);
510 #[cfg(not(feature = "rayon"))]
511 let chunks = raw_out.chunks_mut(pitch);
512
513 chunks.enumerate().for_each(|(y, row)| {
514 for (x, slice) in row.chunks_mut(P::CHANNEL_COUNT as usize).enumerate() {
515 let (px, py) = mapping(x as f32, y as f32);
516 *P::from_slice_mut(slice) = get_pixel(px, py);
517 }
518 });
519}
520
521fn class_from_matrix(mx: [f32; 9]) -> TransformationClass {
523 if (mx[6] - 0.0).abs() < 1e-10 && (mx[7] - 0.0).abs() < 1e-10 && (mx[8] - 1.0).abs() < 1e-10 {
524 if (mx[0] - 1.0).abs() < 1e-10
525 && (mx[1] - 0.0).abs() < 1e-10
526 && (mx[3] - 0.0).abs() < 1e-10
527 && (mx[4] - 1.0).abs() < 1e-10
528 {
529 TransformationClass::Translation
530 } else {
531 TransformationClass::Affine
532 }
533 } else {
534 TransformationClass::Projection
535 }
536}
537
538fn normalize(mx: [f32; 9]) -> [f32; 9] {
539 [
540 mx[0] / mx[8],
541 mx[1] / mx[8],
542 mx[2] / mx[8],
543 mx[3] / mx[8],
544 mx[4] / mx[8],
545 mx[5] / mx[8],
546 mx[6] / mx[8],
547 mx[7] / mx[8],
548 1.0,
549 ]
550}
551
552fn try_inverse(t: &[f32; 9]) -> Option<[f32; 9]> {
554 let [t00, t01, t02, t10, t11, t12, t20, t21, t22] = t;
555
556 let m00 = t11 * t22 - t12 * t21;
557 let m01 = t10 * t22 - t12 * t20;
558 let m02 = t10 * t21 - t11 * t20;
559
560 let det = t00 * m00 - t01 * m01 + t02 * m02;
561
562 if det.abs() < 1e-10 {
563 return None;
564 }
565
566 let m10 = t01 * t22 - t02 * t21;
567 let m11 = t00 * t22 - t02 * t20;
568 let m12 = t00 * t21 - t01 * t20;
569 let m20 = t01 * t12 - t02 * t11;
570 let m21 = t00 * t12 - t02 * t10;
571 let m22 = t00 * t11 - t01 * t10;
572
573 #[rustfmt::skip]
574 let inv = [
575 m00 / det, -m10 / det, m20 / det,
576 -m01 / det, m11 / det, -m21 / det,
577 m02 / det, -m12 / det, m22 / det,
578 ];
579
580 Some(normalize(inv))
581}
582
583fn mul3x3(a: [f32; 9], b: [f32; 9]) -> [f32; 9] {
584 let [a00, a01, a02, a10, a11, a12, a20, a21, a22] = a;
585 let [b00, b01, b02, b10, b11, b12, b20, b21, b22] = b;
586 [
587 a00 * b00 + a01 * b10 + a02 * b20,
588 a00 * b01 + a01 * b11 + a02 * b21,
589 a00 * b02 + a01 * b12 + a02 * b22,
590 a10 * b00 + a11 * b10 + a12 * b20,
591 a10 * b01 + a11 * b11 + a12 * b21,
592 a10 * b02 + a11 * b12 + a12 * b22,
593 a20 * b00 + a21 * b10 + a22 * b20,
594 a20 * b01 + a21 * b11 + a22 * b21,
595 a20 * b02 + a21 * b12 + a22 * b22,
596 ]
597}
598
599fn blend_cubic<P>(px0: &P, px1: &P, px2: &P, px3: &P, x: f32) -> P
600where
601 P: Pixel,
602 P::Subpixel: ValueInto<f32> + Clamp<f32>,
603{
604 let mut outp = *px0;
605
606 for i in 0..(P::CHANNEL_COUNT as usize) {
607 let p0 = cast(px0.channels()[i]);
608 let p1 = cast(px1.channels()[i]);
609 let p2 = cast(px2.channels()[i]);
610 let p3 = cast(px3.channels()[i]);
611 #[rustfmt::skip]
612 let pval = p1 + 0.5 * x * (p2 - p0 + x * (2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3 + x * (3.0 * (p1 - p2) + p3 - p0)));
613 outp.channels_mut()[i] = <P as Pixel>::Subpixel::clamp(pval);
614 }
615
616 outp
617}
618
619fn interpolate_bicubic<P>(image: &Image<P>, x: f32, y: f32, default: P) -> P
620where
621 P: Pixel,
622 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
623{
624 let left = x.floor() - 1f32;
625 let right = left + 4f32;
626 let top = y.floor() - 1f32;
627 let bottom = top + 4f32;
628
629 let x_weight = x - (left + 1f32);
630 let y_weight = y - (top + 1f32);
631
632 let mut col: [P; 4] = [default, default, default, default];
633
634 let (width, height) = image.dimensions();
635 if left < 0f32 || right >= width as f32 || top < 0f32 || bottom >= height as f32 {
636 default
637 } else {
638 for row in top as u32..bottom as u32 {
639 let (p0, p1, p2, p3): (P, P, P, P) = unsafe {
640 (
641 image.unsafe_get_pixel(left as u32, row),
642 image.unsafe_get_pixel(left as u32 + 1, row),
643 image.unsafe_get_pixel(left as u32 + 2, row),
644 image.unsafe_get_pixel(left as u32 + 3, row),
645 )
646 };
647
648 let c = blend_cubic(&p0, &p1, &p2, &p3, x_weight);
649 col[row as usize - top as usize] = c;
650 }
651
652 blend_cubic(&col[0], &col[1], &col[2], &col[3], y_weight)
653 }
654}
655
656fn blend_bilinear<P>(
657 top_left: P,
658 top_right: P,
659 bottom_left: P,
660 bottom_right: P,
661 right_weight: f32,
662 bottom_weight: f32,
663) -> P
664where
665 P: Pixel,
666 P::Subpixel: ValueInto<f32> + Clamp<f32>,
667{
668 let top = top_left.map2(&top_right, |u, v| {
669 P::Subpixel::clamp((1f32 - right_weight) * cast(u) + right_weight * cast(v))
670 });
671
672 let bottom = bottom_left.map2(&bottom_right, |u, v| {
673 P::Subpixel::clamp((1f32 - right_weight) * cast(u) + right_weight * cast(v))
674 });
675
676 top.map2(&bottom, |u, v| {
677 P::Subpixel::clamp((1f32 - bottom_weight) * cast(u) + bottom_weight * cast(v))
678 })
679}
680
681fn interpolate_bilinear<P>(image: &Image<P>, x: f32, y: f32, default: P) -> P
682where
683 P: Pixel,
684 <P as Pixel>::Subpixel: ValueInto<f32> + Clamp<f32>,
685{
686 let left = x.floor();
687 let right = left + 1f32;
688 let top = y.floor();
689 let bottom = top + 1f32;
690
691 let right_weight = x - left;
692 let bottom_weight = y - top;
693
694 let (width, height) = image.dimensions();
696 if left < 0f32 || right >= width as f32 || top < 0f32 || bottom >= height as f32 {
697 default
698 } else {
699 let (tl, tr, bl, br) = unsafe {
700 (
701 image.unsafe_get_pixel(left as u32, top as u32),
702 image.unsafe_get_pixel(right as u32, top as u32),
703 image.unsafe_get_pixel(left as u32, bottom as u32),
704 image.unsafe_get_pixel(right as u32, bottom as u32),
705 )
706 };
707 blend_bilinear(tl, tr, bl, br, right_weight, bottom_weight)
708 }
709}
710
711#[inline(always)]
712fn interpolate_nearest<P: Pixel>(image: &Image<P>, x: f32, y: f32, default: P) -> P {
713 let rx = x.round();
714 let ry = y.round();
715
716 let (width, height) = image.dimensions();
717 if rx < 0f32 || rx >= width as f32 || ry < 0f32 || ry >= height as f32 {
718 default
719 } else {
720 unsafe { image.unsafe_get_pixel(rx as u32, ry as u32) }
721 }
722}
723
724#[derive(Debug, PartialEq, Eq, Copy, Clone)]
726pub enum Interpolation {
727 Nearest,
730 Bilinear,
733 Bicubic,
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use crate::utils::gray_bench_image;
742 use image::{GrayImage, Luma};
743 use test::{black_box, Bencher};
744
745 #[test]
746 fn test_rotate_nearest_zero_radians() {
747 let image = gray_image!(
748 00, 01, 02;
749 10, 11, 12);
750
751 let rotated = rotate(
752 &image,
753 (0f32, 0f32),
754 0f32,
755 Interpolation::Nearest,
756 Luma([99u8]),
757 );
758 assert_pixels_eq!(rotated, image);
759 }
760
761 #[test]
762 fn text_rotate_nearest_quarter_turn_clockwise() {
763 let image = gray_image!(
764 00, 01, 02;
765 10, 11, 12);
766
767 let expected = gray_image!(
768 11, 01, 99;
769 12, 02, 99);
770 let c = Projection::translate(1.0, 0.0);
771 let rot = c * Projection::rotate(90f32.to_radians()) * c.invert();
772
773 let rotated = warp(&image, &rot, Interpolation::Nearest, Luma([99u8]));
774 assert_pixels_eq!(rotated, expected);
775 }
776
777 #[test]
778 fn text_rotate_nearest_half_turn_anticlockwise() {
779 let image = gray_image!(
780 00, 01, 02;
781 10, 11, 12);
782
783 let expected = gray_image!(
784 12, 11, 10;
785 02, 01, 00);
786 let c = Projection::translate(1.0, 0.5);
787
788 let rot = c * Projection::rotate((-180f32).to_radians()) * c.invert();
789
790 let rotated = warp(&image, &rot, Interpolation::Nearest, Luma([99u8]));
791 assert_pixels_eq!(rotated, expected);
792 }
793
794 #[bench]
795 fn bench_rotate_nearest(b: &mut Bencher) {
796 let image = GrayImage::from_pixel(200, 200, Luma([15u8]));
797 let c = Projection::translate(3.0, 3.0);
798 let rot = c * Projection::rotate(1f32.to_degrees()) * c.invert();
799 b.iter(|| {
800 let rotated = warp(&image, &rot, Interpolation::Nearest, Luma([98u8]));
801 black_box(rotated);
802 });
803 }
804
805 #[bench]
806 fn bench_rotate_bilinear(b: &mut Bencher) {
807 let image = GrayImage::from_pixel(200, 200, Luma([15u8]));
808 let c = Projection::translate(3.0, 3.0);
809 let rot = c * Projection::rotate(1f32.to_degrees()) * c.invert();
810 b.iter(|| {
811 let rotated = warp(&image, &rot, Interpolation::Bilinear, Luma([98u8]));
812 black_box(rotated);
813 });
814 }
815
816 #[bench]
817 fn bench_rotate_bicubic(b: &mut Bencher) {
818 let image = GrayImage::from_pixel(200, 200, Luma([15u8]));
819 let c = Projection::translate(3.0, 3.0);
820 let rot = c * Projection::rotate(1f32.to_degrees()) * c.invert();
821 b.iter(|| {
822 let rotated = warp(&image, &rot, Interpolation::Bicubic, Luma([98u8]));
823 black_box(rotated);
824 });
825 }
826
827 #[test]
828 fn test_translate_positive_x_positive_y() {
829 let image = gray_image!(
830 00, 01, 02;
831 10, 11, 12;
832 20, 21, 22);
833
834 let expected = gray_image!(
835 00, 00, 01;
836 00, 00, 01;
837 10, 10, 11);
838
839 let translated = translate(&image, (1, 1));
840 assert_pixels_eq!(translated, expected);
841 }
842
843 #[test]
844 fn test_translate_positive_x_negative_y() {
845 let image = gray_image!(
846 00, 01, 02;
847 10, 11, 12;
848 20, 21, 22);
849
850 let expected = gray_image!(
851 10, 10, 11;
852 20, 20, 21;
853 20, 20, 21);
854
855 let translated = translate(&image, (1, -1));
856 assert_pixels_eq!(translated, expected);
857 }
858
859 #[test]
860 fn test_translate_negative_x() {
861 let image = gray_image!(
862 00, 01, 02;
863 10, 11, 12;
864 20, 21, 22);
865
866 let expected = gray_image!(
867 01, 02, 02;
868 11, 12, 12;
869 21, 22, 22);
870
871 let translated = translate(&image, (-1, 0));
872 assert_pixels_eq!(translated, expected);
873 }
874
875 #[test]
876 fn test_translate_large_x_large_y() {
877 let image = gray_image!(
878 00, 01, 02;
879 10, 11, 12;
880 20, 21, 22);
881
882 let expected = gray_image!(
883 00, 00, 00;
884 00, 00, 00;
885 00, 00, 00);
886
887 let translated = translate(&image, (5, 5));
889 assert_pixels_eq!(translated, expected);
890 }
891
892 #[bench]
893 fn bench_translate(b: &mut Bencher) {
894 let image = gray_bench_image(500, 500);
895 b.iter(|| {
896 let translated = translate(&image, (30, 30));
897 black_box(translated);
898 });
899 }
900
901 #[test]
902 fn test_translate_positive_x_positive_y_projection() {
903 let image = gray_image!(
904 00, 01, 02;
905 10, 11, 12;
906 20, 21, 22);
907
908 let expected = gray_image!(
909 00, 00, 00;
910 00, 00, 01;
911 00, 10, 11);
912
913 let translated = warp(
914 &image,
915 &Projection::translate(1.0, 1.0),
916 Interpolation::Nearest,
917 Luma([0u8]),
918 );
919 assert_pixels_eq!(translated, expected);
920 }
921
922 #[test]
923 fn test_translate_positive_x_negative_y_projection() {
924 let image = gray_image!(
925 00, 01, 02;
926 10, 11, 12;
927 20, 21, 22);
928
929 let expected = gray_image!(
930 00, 10, 11;
931 00, 20, 21;
932 00, 00, 00);
933
934 let translated = warp(
935 &image,
936 &Projection::translate(1.0, -1.0),
937 Interpolation::Nearest,
938 Luma([0u8]),
939 );
940 assert_pixels_eq!(translated, expected);
941 }
942
943 #[test]
944 fn test_translate_large_x_large_y_projection() {
945 let image = gray_image!(
946 00, 01, 02;
947 10, 11, 12;
948 20, 21, 22);
949
950 let expected = gray_image!(
951 00, 00, 00;
952 00, 00, 00;
953 00, 00, 00);
954
955 let translated = warp(
957 &image,
958 &Projection::translate(5.0, 5.0),
959 Interpolation::Nearest,
960 Luma([0u8]),
961 );
962 assert_pixels_eq!(translated, expected);
963 }
964
965 #[bench]
966 fn bench_translate_projection(b: &mut Bencher) {
967 let image = gray_bench_image(500, 500);
968 let t = Projection::translate(-30.0, -30.0);
969
970 b.iter(|| {
971 let translated = warp(&image, &t, Interpolation::Nearest, Luma([0u8]));
972 black_box(translated);
973 });
974 }
975
976 #[bench]
977 fn bench_translate_with(b: &mut Bencher) {
978 let image = gray_bench_image(500, 500);
979
980 b.iter(|| {
981 let (width, height) = image.dimensions();
982 let mut out = ImageBuffer::new(width, height);
983 warp_into_with(
984 &image,
985 |x, y| (x - 30.0, y - 30.0),
986 Interpolation::Nearest,
987 Luma([0u8]),
988 &mut out,
989 );
990 black_box(out);
991 });
992 }
993
994 #[test]
995 fn test_affine() {
996 let image = gray_image!(
997 00, 01, 02;
998 10, 11, 12;
999 20, 21, 22);
1000
1001 let expected = gray_image!(
1002 00, 00, 00;
1003 00, 00, 01;
1004 00, 10, 11);
1005
1006 #[rustfmt::skip]
1007 let aff = Projection::from_matrix([
1008 1.0, 0.0, 1.0,
1009 0.0, 1.0, 1.0,
1010 0.0, 0.0, 1.0
1011 ]).unwrap();
1012
1013 let translated_nearest = warp(&image, &aff, Interpolation::Nearest, Luma([0u8]));
1014 assert_pixels_eq!(translated_nearest, expected);
1015 let translated_bilinear = warp(&image, &aff, Interpolation::Bilinear, Luma([0u8]));
1016 assert_pixels_eq!(translated_bilinear, expected);
1017 }
1018
1019 #[test]
1020 fn test_affine_bicubic() {
1021 let image = gray_image!(
1022 99, 01, 02, 03, 04;
1023 10, 11, 12, 13, 14;
1024 20, 21, 22, 23, 24;
1025 30, 31, 32, 33, 34;
1026 40, 41, 42, 43, 44);
1027
1028 let expected = gray_image!(
1030 00, 00, 00, 00, 00;
1031 00, 00, 00, 00, 00;
1032 00, 00, 11, 00, 00;
1033 00, 00, 00, 00, 00;
1034 00, 00, 00, 00, 00);
1035
1036 #[rustfmt::skip]
1037 let aff = Projection::from_matrix([
1038 1.0, 0.0, 1.0,
1039 0.0, 1.0, 1.0,
1040 0.0, 0.0, 1.0
1041 ]).unwrap();
1042
1043 let translated_bicubic = warp(&image, &aff, Interpolation::Bicubic, Luma([0u8]));
1044 assert_pixels_eq!(translated_bicubic, expected);
1045 }
1046
1047 #[bench]
1048 fn bench_affine_nearest(b: &mut Bencher) {
1049 let image = GrayImage::from_pixel(200, 200, Luma([15u8]));
1050
1051 #[rustfmt::skip]
1052 let aff = Projection::from_matrix([
1053 1.0, 0.0, -1.0,
1054 0.0, 1.0, -1.0,
1055 0.0, 0.0, 1.0
1056 ]).unwrap();
1057
1058 b.iter(|| {
1059 let transformed = warp(&image, &aff, Interpolation::Nearest, Luma([0u8]));
1060 black_box(transformed);
1061 });
1062 }
1063
1064 #[bench]
1065 fn bench_affine_bilinear(b: &mut Bencher) {
1066 let image = GrayImage::from_pixel(200, 200, Luma([15u8]));
1067
1068 #[rustfmt::skip]
1069 let aff = Projection::from_matrix([
1070 1.8, -0.2, 5.0,
1071 0.2, 1.9, 6.0,
1072 0.0002, 0.0003, 1.0
1073 ]).unwrap();
1074
1075 b.iter(|| {
1076 let transformed = warp(&image, &aff, Interpolation::Bilinear, Luma([0u8]));
1077 black_box(transformed);
1078 });
1079 }
1080
1081 #[bench]
1082 fn bench_affine_bicubic(b: &mut test::Bencher) {
1083 let image = GrayImage::from_pixel(200, 200, Luma([15u8]));
1084
1085 #[rustfmt::skip]
1086 let aff = Projection::from_matrix([
1087 1.8, -0.2, 5.0,
1088 0.2, 1.9, 6.0,
1089 0.0002, 0.0003, 1.0
1090 ]).unwrap();
1091
1092 b.iter(|| {
1093 let transformed = warp(&image, &aff, Interpolation::Bicubic, Luma([0u8]));
1094 black_box(transformed);
1095 });
1096 }
1097
1098 #[test]
1099 fn test_from_control_points_translate() {
1100 let from = [(0f32, 0.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1101 let to = [(10f32, 5.0), (60.0, 55.0), (60.0, 5.0), (10.0, 55.0)];
1102
1103 let p = Projection::from_control_points(from, to);
1104 assert!(p.is_some());
1105
1106 let out = p.unwrap() * (0f32, 0f32);
1107
1108 assert_approx_eq!(out.0, 10.0, 1e-10);
1109 assert_approx_eq!(out.1, 5.0, 1e-10);
1110 }
1111
1112 #[test]
1113 fn test_from_control_points() {
1114 let from = [(0f32, 0.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1115 let to = [(16f32, 20.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1116
1117 let p = Projection::from_control_points(from, to);
1118 assert!(p.is_some());
1119
1120 let out = p.unwrap() * (0f32, 0f32);
1121
1122 assert_approx_eq!(out.0, 16.0, 1e-10);
1123 assert_approx_eq!(out.1, 20.0, 1e-10);
1124 }
1125
1126 #[test]
1127 fn test_from_control_points_2() {
1128 let from = [
1129 (67.24537, 427.96024),
1130 (65.51512, 67.96736),
1131 (569.6426, 62.33165),
1132 (584.4605, 425.33667),
1133 ];
1134 let to = [(0.0, 0.0), (640.0, 0.0), (640.0, 480.0), (0.0, 480.0)];
1135
1136 let p = Projection::from_control_points(from, to);
1137 assert!(p.is_some());
1138 }
1139
1140 #[test]
1141 fn test_from_control_points_nofreeze() {
1143 let from = [
1144 (0.0, 0.0),
1145 (250.0, 17.481735),
1146 (7.257017, 82.94814),
1147 (250.0, 104.18543),
1148 ];
1149 let to = [(0.0, 0.0), (249.0, 0.0), (0.0, 105.0), (249.0, 105.0)];
1150
1151 Projection::from_control_points(from, to);
1152 }
1153
1154 #[test]
1155 fn test_from_control_points_known_transform() {
1156 let t = Projection::translate(10f32, 10f32);
1157 let p = t * Projection::rotate(90f32.to_radians()) * t.invert();
1158
1159 let from = [(0f32, 0.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1160 let to = [p * from[0], p * from[1], p * from[2], p * from[3]];
1161
1162 let p_est = Projection::from_control_points(from, to);
1163 assert!(p_est.is_some());
1164 let p_est = p_est.unwrap();
1165
1166 for i in 0..50 {
1167 for j in 0..50 {
1168 let pt = (i as f32, j as f32);
1169 assert_approx_eq!((p * pt).0, (p_est * pt).0, 1e-3);
1170 assert_approx_eq!((p * pt).1, (p_est * pt).1, 1e-3);
1171 }
1172 }
1173 }
1174
1175 #[test]
1176 fn test_from_control_points_colinear() {
1177 let from = [(0f32, 0.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1178 let to = [(0f32, 5.0), (0.0, 55.0), (0.0, 5.0), (10.0, 55.0)];
1179
1180 let p = Projection::from_control_points(from, to);
1181 assert!(p.is_none());
1183 }
1184
1185 #[test]
1186 fn test_from_control_points_translation() {
1187 let p = Projection::translate(10f32, 15f32);
1188
1189 let from = [(0f32, 0.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1190 let to = [(10f32, 15.0), (60.0, 65.0), (60.0, 15.0), (10.0, 65.0)];
1191
1192 let p_est = Projection::from_control_points(from, to).unwrap();
1193
1194 for i in 0..50 {
1195 for j in 0..50 {
1196 let pt = (i as f32, j as f32);
1197 assert_approx_eq!((p * pt).0, (p_est * pt).0, 1e-3);
1198 assert_approx_eq!((p * pt).1, (p_est * pt).1, 1e-3);
1199 }
1200 }
1201 }
1202
1203 #[test]
1204 fn test_from_control_points_underdetermined() {
1205 let from = [
1206 (307.12073f32, 3.2),
1207 (330.89783, 3.2),
1208 (21.333334, 248.17337),
1209 (21.333334, 230.34056),
1210 ];
1211 let to = [(0.0f32, 0.0), (3.0, 0.0), (3.0, 3.0), (0.0, 3.0)];
1212
1213 let p = Projection::from_control_points(from, to);
1214 p.unwrap();
1215 }
1216
1217 #[bench]
1218 fn bench_from_control_points(b: &mut Bencher) {
1219 let from = [(0f32, 0.0), (50.0, 50.0), (50.0, 0.0), (0.0, 50.0)];
1220 let to = [(10f32, 5.0), (60.0, 55.0), (60.0, 5.0), (10.0, 55.0)];
1221
1222 b.iter(|| {
1223 let proj = Projection::from_control_points(from, to);
1224 black_box(proj);
1225 });
1226 }
1227}