001/*******************************************************************************
002 * This software is provided as a supplement to the authors' textbooks on digital
003 * image processing published by Springer-Verlag in various languages and editions.
004 * Permission to use and distribute this software is granted under the BSD 2-Clause
005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause).
006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved.
007 * Visit https://imagingbook.com for additional details.
008 ******************************************************************************/
009
010package imagingbook.common.geometry.mappings.nonlinear;
011
012import imagingbook.common.geometry.basic.Pnt2d;
013import imagingbook.common.geometry.basic.Pnt2d.PntDouble;
014import imagingbook.common.geometry.basic.Pnt2d.PntInt;
015import imagingbook.common.geometry.mappings.Mapping2D;
016import imagingbook.common.math.Matrix;
017
018/**
019 * <p>
020 * This class represents a bilinear transformation in 2D with 8 parameters a0, ..., a3, b0, ..., b3 of the form
021 * </p>
022 * <pre>
023 * x' = a0 * x + a1 * y + a2 * x * y + a3
024 * y' = b0 * x + b1 * y + b2 * x * y + b3</pre>
025 * <p>
026 * Note that this is a non-linear transformation because of the mixed term x * y. See Secs. 21.1.5 and 21.3.1 of [1] for
027 * details.
028 * </p>
029 * <p>
030 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
031 * (2022).
032 * </p>
033 *
034 * @author WB
035 */
036public class BilinearMapping2D implements Mapping2D {
037        
038        public final double a0, a1, a2, a3;
039        public final double b0, b1, b2, b3;
040        
041        public BilinearMapping2D(
042                        double a0, double a1, double a2, double a3,
043                        double b0, double b1, double b2, double b3) {
044                this.a0 = a0;   this.a1 = a1;   this.a2 = a2;   this.a3 = a3;
045                this.b0 = b0;   this.b1 = b1;   this.b2 = b2;   this.b3 = b3;           
046        }
047
048        /**
049         * Calculates and returns the bilinear mapping M between two point sets P, Q, with 4 points each, such that q_i =
050         * M(p_i). The inverse mapping is obtained by simply swapping the two point sets.
051         *
052         * @param P the first point sequence
053         * @param Q the second point sequence
054         * @return a new bilinear mapping
055         */
056        public static BilinearMapping2D fromPoints(Pnt2d[] P, Pnt2d[] Q) {      
057                //define column vectors x, y
058                double[] x = {Q[0].getX(), Q[1].getX(), Q[2].getX(), Q[3].getX()};
059                double[] y = {Q[0].getY(), Q[1].getY(), Q[2].getY(), Q[3].getY()};              
060                // mount matrix M
061                double[][] M = new double[][]
062                        {{P[0].getX(), P[0].getY(), P[0].getX() * P[0].getY(), 1},
063                         {P[1].getX(), P[1].getY(), P[1].getX() * P[1].getY(), 1},
064                         {P[2].getX(), P[2].getY(), P[2].getX() * P[2].getY(), 1},
065                         {P[3].getX(), P[3].getY(), P[3].getX() * P[3].getY(), 1}};
066                double[] a = Matrix.solve(M, x);                // solve x = M * a = x (a is unknown)
067                double[] b = Matrix.solve(M, y);                // solve y = M * b = y (b is unknown)           
068                double a1 = a[0];               double b1 = b[0];
069                double a2 = a[1];               double b2 = b[1];
070                double a3 = a[2];               double b3 = b[2];
071                double a4 = a[3];               double b4 = b[3];
072                return new BilinearMapping2D(a1, a2, a3, a4, b1, b2, b3, b4);
073        }
074
075        @Override
076        public Pnt2d applyTo(Pnt2d pnt) {
077                final double x = pnt.getX();
078                final double y = pnt.getY();
079                double xx = a0 * x + a1 * y + a2 * x * y + a3;
080                double yy = b0 * x + b1 * y + b2 * x * y + b3;
081                return PntDouble.from(xx, yy);
082        }
083        
084        @Override
085        public String toString() {
086                return String.format(
087                                "BilinearMapping[A = (%.3f, %.3f, %.3f, %.3f) / B = (%.3f, %.3f, %.3f, %.3f)]",
088                                a0, a1, a2, a3, b0, b1, b2, b3);
089        }
090        
091        // ------------------------------------------------------------------------
092        
093        // /**
094        //  * For testing only.
095        //  * @param args ignored
096        //  */
097        // public static void main(String[] args) {
098        //      Pnt2d[] P = {
099        //                      PntInt.from(2,5),
100        //                      PntInt.from(4,6),
101        //                      PntInt.from(7,9),
102        //                      PntInt.from(5,9),
103        //                      };
104        //
105        //      Pnt2d[] Q = {
106        //                      PntInt.from(4,3),
107        //                      PntInt.from(5,2),
108        //                      PntInt.from(9,3),
109        //                      PntInt.from(7,5),
110        //                      };
111        //
112        //      BilinearMapping2D bm = fromPoints(P, Q);
113        //      System.out.println("\nbilinear mapping = \n" + bm.toString());
114        //
115        //      for (int i = 0; i < P.length; i++) {
116        //              Pnt2d Qi = bm.applyTo(P[i]);
117        //              System.out.println(P[i].toString() + " -> " + Qi.toString());
118        //      }
119        // }
120
121
122}