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.linear; 011 012import imagingbook.common.geometry.basic.Pnt2d; 013import imagingbook.common.geometry.basic.Pnt2d.PntDouble; 014import imagingbook.common.geometry.mappings.Inversion; 015import imagingbook.common.geometry.mappings.Mapping2D; 016import imagingbook.common.math.Arithmetic; 017import imagingbook.common.math.Matrix; 018 019/** 020 * <p> 021 * This class represents an arbitrary linear transformation in 2D. Instances of this class and any subclass are 022 * immutable. See Secs. 21.1 and 21.3 of [1] for details. 023 * </p> 024 * <p> 025 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 026 * (2022). 027 * </p> 028 */ 029public class LinearMapping2D implements Mapping2D, Inversion { 030 031 protected final double 032 a00, a01, a02, 033 a10, a11, a12, 034 a20, a21, a22; 035 036 // constructors ----------------------------------------------------- 037 038 /** 039 * Constructor, creates a new identity mapping. 040 */ 041 public LinearMapping2D() { 042 a00 = 1; a01 = 0; a02 = 0; 043 a10 = 0; a11 = 1; a12 = 0; 044 a20 = 0; a21 = 0; a22 = 1; 045 } 046 047 /** 048 * Creates a linear mapping from a transformation matrix of arbitrary size (may be even empty). The actual 049 * transformation matrix is formed by inserting the given matrix into a 3x3 identity matrix starting at position 050 * (0,0). 051 * 052 * @param A a matrix of arbitrary size 053 */ 054 public LinearMapping2D(double[][] A) { 055 double[][] M = extract3x3Matrix(A); 056 a00 = M[0][0]; a01 = M[0][1]; a02 = M[0][2]; 057 a10 = M[1][0]; a11 = M[1][1]; a12 = M[1][2]; 058 a20 = M[2][0]; a21 = M[2][1]; a22 = M[2][2]; 059 } 060 061 /** 062 * Inserts the given matrix into a new 3x3 identity matrix, starting at element (0,0). All elements outside 3x3 are 063 * ignored. 064 * 065 * @param A the original matrix 066 * @return a 3x3 matrix 067 */ 068 private static double[][] extract3x3Matrix(double[][] A) { 069 double[][] M = Matrix.idMatrix(3); 070 final int m = Math.min(3, A.length); // max. 3 rows 071 for (int i = 0; i < m; i++) { 072 final int n = Math.min(3, A[i].length); // max. 3 columns 073 for (int j = 0; j < n; j++) { 074 M[i][j] = A[i][j]; 075 } 076 } 077 return M; 078 } 079 080 /** 081 * Creates an arbitrary linear mapping from the specified matrix elements. 082 * 083 * @param a00 matrix element A_00 084 * @param a01 matrix element A_01 085 * @param a02 matrix element A_02 086 * @param a10 matrix element A_10 087 * @param a11 matrix element A_11 088 * @param a12 matrix element A_12 089 * @param a20 matrix element A_20 090 * @param a21 matrix element A_21 091 * @param a22 matrix element A_22 092 */ 093 public LinearMapping2D ( 094 double a00, double a01, double a02, 095 double a10, double a11, double a12, 096 double a20, double a21, double a22) { 097 this.a00 = a00; this.a01 = a01; this.a02 = a02; 098 this.a10 = a10; this.a11 = a11; this.a12 = a12; 099 this.a20 = a20; this.a21 = a21; this.a22 = a22; 100 } 101 102 /** 103 * Creates a new linear mapping from an existing linear mapping. 104 * 105 * @param lm a given linear mapping 106 */ 107 public LinearMapping2D (LinearMapping2D lm) { 108 this(lm.getTransformationMatrix()); 109 } 110 111 // ---------------------------------------------------------- 112 113 /** 114 * Scales the transformation matrix such that a22 becomes 1. Any linear mapping can be normalized and thereby be 115 * converted to a projective mapping (see {@link ProjectiveMapping2D}. 116 * 117 * @return the normalized linear mapping (i.e., a projective mapping) 118 */ 119 public ProjectiveMapping2D normalize() { 120 if (Arithmetic.isZero(Math.abs(a22))) { 121 throw new ArithmeticException("Zero value in a22, cannot normalize linear mapping!"); 122 } 123 double b00 = a00/a22; double b01 = a01/a22; double b02 = a02/a22; 124 double b10 = a10/a22; double b11 = a11/a22; double b12 = a12/a22; 125 double b20 = a20/a22; double b21 = a21/a22; 126 return new ProjectiveMapping2D(b00, b01, b02, b10, b11, b12, b20, b21); 127 } 128 129 // ---------------------------------------------------------- 130 131 @Override 132 public Pnt2d applyTo(Pnt2d pnt) { 133 final double x = pnt.getX(); 134 final double y = pnt.getY(); 135 final double h = (a20 * x + a21 * y + a22); 136 double x1 = (a00 * x + a01 * y + a02) / h; 137 double y1 = (a10 * x + a11 * y + a12) / h; 138 return PntDouble.from(x1, y1); 139 } 140 141 /** 142 * Calculates and returns the inverse mapping. 143 */ 144 @Override 145 public LinearMapping2D getInverse() { 146 // System.out.println("LinearMapping getInverse()"); 147 double[][] ai = Matrix.inverse(this.getTransformationMatrix()); 148 return new LinearMapping2D(ai); 149 } 150 151 /** 152 * Concatenates this mapping A with another linear mapping B and returns a new mapping C, such that C(x) = B(A(x)). 153 * 154 * @param B the second mapping 155 * @return the concatenated mapping 156 */ 157 public LinearMapping2D concat(LinearMapping2D B) { 158 double[][] C = Matrix.multiply(B.getTransformationMatrix(), this.getTransformationMatrix()); 159 return new LinearMapping2D(C); 160 } 161 162 /** 163 * Concatenates a sequence of linear mappings AA = (A1, A2, ..., An), with the result A(x) = A1(A2(...(An(x))...)). 164 * Thus, the mapping An is applied first and A1 last, with the associated transformation matrix a = a1 * a2 * ... * 165 * an. If AA is empty, the identity mapping is returned. If AA contains only a single mapping, a copy of this 166 * mapping is returned. 167 * 168 * @param AA a (possibly empty) sequence of linear transformations 169 * @return the concatenated linear transformation 170 */ 171 public static LinearMapping2D concatenate(LinearMapping2D... AA) { 172 if (AA.length == 0) { 173 return new LinearMapping2D(); // identity 174 } 175 else { 176 double[][] a = AA[0].getTransformationMatrix(); 177 for (int i = 1; i < AA.length; i++) { 178 a = Matrix.multiply(a, AA[i].getTransformationMatrix()); 179 } 180 return new LinearMapping2D(a); 181 } 182 } 183 184 /** 185 * Retrieves the transformation matrix for this mapping. 186 * @return the 3x3 transformation matrix 187 */ 188 public double[][] getTransformationMatrix() { 189 return new double[][] 190 {{a00, a01, a02}, 191 {a10, a11, a12}, 192 {a20, a21, a22}}; 193 } 194 195 /** 196 * Returns a copy of this mapping. 197 * @return a new mapping of the same type 198 */ 199 public LinearMapping2D duplicate() { 200 return new LinearMapping2D(this); 201 } 202 203 @Override 204 public String toString() { 205 return Matrix.toString(getTransformationMatrix()); 206 } 207 208 // ----------------------------------------------------------- 209 210// /** 211// * For testing only. 212// * @param args ignored 213// */ 214// public static void main(String[] args) { 215// PrintPrecision.set(6); 216// double[][] A = 217// {{-1.230769, 2.076923, -1.769231}, 218// {-2.461538, 2.615385, -3.538462}, 219// {-0.307692, 0.230769, 1.000000}}; 220// System.out.println("A = " + Matrix.toString(A)); 221// 222// double[][] Ai = Matrix.inverse(A); 223// System.out.println("Ai = " + Matrix.toString(Ai)); 224// 225// double[][] I = Matrix.multiply(A, Ai); 226// System.out.println("\ntest: should be the identity matrix: = \n" + Matrix.toString(I)); 227// 228// ProjectiveMapping2D pA = (new LinearMapping2D(A)).normalize(); 229// System.out.println("mapping pA = \n" + pA.toString()); 230// 231// ProjectiveMapping2D AA1 = pA.concat(pA); 232// System.out.println("mapping AA1 = \n" + AA1.toString()); 233// 234// ProjectiveMapping2D AA2 = LinearMapping2D.concatenate(pA, pA).normalize(); 235// System.out.println("mapping AA1 = \n" + AA2.toString()); 236// } 237 238 239 240} 241 242 243 244