imageproc/
geometric_transformations.rs

1//! Geometric transformations of images. This includes rotations, translation, and general
2//! projective transformations.
3
4use 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/// A 2d projective transformation, stored as a row major 3x3 matrix.
20///
21/// Transformations combine by pre-multiplication, i.e. applying `P * Q` is equivalent to
22/// applying `Q` and then applying `P`. For example, the following defines a rotation
23/// about the point (320.0, 240.0).
24///
25/// ```
26/// use imageproc::geometric_transformations::*;
27/// use std::f32::consts::PI;
28///
29/// let (cx, cy) = (320.0, 240.0);
30///
31/// let c_rotation = Projection::translate(cx, cy)
32///     * Projection::rotate(PI / 6.0)
33///     * Projection::translate(-cx, -cy);
34/// ```
35///
36/// See ./examples/projection.rs for more examples.
37#[derive(Copy, Clone, Debug)]
38pub struct Projection {
39    transform: [f32; 9],
40    inverse: [f32; 9],
41    class: TransformationClass,
42}
43
44impl Projection {
45    /// Creates a 2d projective transform from a row-major 3x3 matrix in homogeneous coordinates.
46    ///
47    /// Returns `None` if the matrix is not invertible.
48    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    /// Combine the transformation with another one. The resulting transformation is equivalent to
59    /// applying this transformation followed by the `other` transformation.
60    pub fn and_then(self, other: Projection) -> Projection {
61        other * self
62    }
63
64    /// A translation by (tx, ty).
65    #[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    /// A clockwise rotation around the top-left corner of the image by theta radians.
83    #[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    /// An anisotropic scaling (sx, sy).
102    ///
103    /// Note that the `warp` function does not change the size of the input image.
104    /// If you want to resize an image then use the `imageops` module in the `image` crate.
105    #[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    /// Inverts the transformation.
123    pub fn invert(self) -> Projection {
124        Projection {
125            transform: self.inverse,
126            inverse: self.transform,
127            class: self.class,
128        }
129    }
130
131    /// Calculates a projection from a set of four control point pairs.
132    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    // Helper functions used as optimization in warp.
199    #[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
277/// Rotates an image clockwise about its center.
278/// The output image has the same dimensions as the input. Output pixels
279/// whose pre-image lies outside the input image are set to `default`.
280pub 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
301/// Rotates an image clockwise about the provided center by theta radians.
302/// The output image has the same dimensions as the input. Output pixels
303/// whose pre-image lies outside the input image are set to `default`.
304pub 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
322/// Translates the input image by t. Note that image coordinates increase from
323/// top left to bottom right. Output pixels whose pre-image are not in the input
324/// image are set to the boundary pixel in the input image nearest to their pre-image.
325// TODO: it's confusing that this has different behaviour to
326// TODO: attempting the equivalent transformation using Projection.
327pub 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
370/// Applies a projective transformation to an image.
371///
372/// The returned image has the same dimensions as `image`. Output pixels
373/// whose pre-image lies outside the input image are set to `default`.
374///
375/// The provided projection defines a mapping from locations in the input image to their
376/// corresponding location in the output image.
377pub 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
394/// Applies a projective transformation to an image, writing to a provided output.
395///
396/// See the [`warp`](fn.warp.html) documentation for more information.
397pub 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
431/// Warps an image using the provided function to define the pre-image of each output pixel.
432///
433/// # Examples
434/// Applying a wave pattern.
435/// ```
436/// use image::{ImageBuffer, Luma};
437/// use imageproc::utils::gray_bench_image;
438/// use imageproc::geometric_transformations::*;
439///
440/// let image = gray_bench_image(300, 300);
441/// let warped = warp_with(
442///     &image,
443///     |x, y| (x, y + (x / 30.0).sin()),
444///     Interpolation::Nearest,
445///     Luma([0u8])
446/// );
447/// ```
448pub 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
466/// Warps an image using the provided function to define the pre-image of each output pixel,
467/// writing into a preallocated output.
468///
469/// See the [`warp_with`](fn.warp_with.html) documentation for more information.
470pub 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
494// Work horse of all warp functions
495// TODO: make faster by avoiding boundary checks in inner section of src image
496fn 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
521// Classifies transformation by looking up transformation matrix coefficients
522fn 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
552// TODO: write me in f64
553fn 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    // default if out of bound
695    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/// How to handle pixels whose pre-image lies between input pixels.
725#[derive(Debug, PartialEq, Eq, Copy, Clone)]
726pub enum Interpolation {
727    /// Choose the nearest pixel to the pre-image of the
728    /// output pixel.
729    Nearest,
730    /// Bilinearly interpolate between the four pixels
731    /// closest to the pre-image of the output pixel.
732    Bilinear,
733    /// Bicubicly interpolate between the four pixels
734    /// closest to the pre-image of the output pixel.
735    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        // Translating by more than the image width and height
888        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        // Translating by more than the image width and height
956        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        // Expect 2 pixels each side lost due to kernel size
1029        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    /// Test case from https://github.com/image-rs/imageproc/issues/412
1142    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        // Should fail if 3 points are colinear
1182        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}