View Javadoc
1   /*
2    * Copyright (C) 2012 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package com.irurueta.geometry;
17  
18  import com.irurueta.algebra.AlgebraException;
19  import com.irurueta.algebra.ArrayUtils;
20  import com.irurueta.algebra.Matrix;
21  import com.irurueta.algebra.RQDecomposer;
22  import com.irurueta.algebra.SingularValueDecomposer;
23  import com.irurueta.algebra.Utils;
24  import com.irurueta.algebra.WrongSizeException;
25  
26  import java.io.Serializable;
27  import java.util.Arrays;
28  
29  /**
30   * This class performs affine transformations on 2D space.
31   * Affine transformations include transformations related to rotations,
32   * translations, independently scaling horizontal or vertical coordinates
33   * or skewing the coordinates axis.
34   * This class is not intended to be used on points located at infinity or
35   * at very large coordinates, since numerical instabilities may occur. For
36   * those cases use a ProjectiveTransformation2D instead.
37   */
38  @SuppressWarnings("DuplicatedCode")
39  public class AffineTransformation2D extends Transformation2D implements Serializable {
40  
41      /**
42       * Constant indicating number of coordinates required in translation arrays.
43       */
44      public static final int NUM_TRANSLATION_COORDS = 2;
45  
46  
47      /**
48       * Constant defining number of inhomogeneous coordinates in 2D space.
49       */
50      public static final int INHOM_COORDS = 2;
51  
52      /**
53       * Constant defining number of homogeneous coordinates in 2D space.
54       */
55      public static final int HOM_COORDS = 3;
56  
57      /**
58       * Linear mapping.
59       */
60      private Matrix a;
61  
62      /**
63       * 2D translation to be performed on geometric objects.
64       * Translation is specified using inhomogeneous coordinates.
65       */
66      private double[] translation;
67  
68      /**
69       * Empty constructor.
70       * Creates transformation that has no effect.
71       */
72      public AffineTransformation2D() {
73          super();
74          try {
75              a = Matrix.identity(INHOM_COORDS, INHOM_COORDS);
76          } catch (final WrongSizeException ignore) {
77              // never happens
78          }
79          translation = new double[NUM_TRANSLATION_COORDS];
80      }
81  
82      /**
83       * Creates transformation with provided linear mapping matrix.
84       *
85       * @param a linear mapping.
86       * @throws NullPointerException     raised if provided rotation is null.
87       * @throws IllegalArgumentException raised if provided matrix does not have
88       *                                  size 2x2.
89       */
90      public AffineTransformation2D(final Matrix a) {
91          setA(a);
92          translation = new double[NUM_TRANSLATION_COORDS];
93      }
94  
95      /**
96       * Creates transformation with provided scale value.
97       *
98       * @param scale Scale value. Values between 0.0 and 1.0 reduce objects,
99       *              values greater than 1.0 enlarge objects and negative values reverse
100      *              objects.
101      */
102     public AffineTransformation2D(final double scale) {
103         final var diag = new double[INHOM_COORDS];
104         Arrays.fill(diag, scale);
105         a = Matrix.diagonal(diag);
106         translation = new double[NUM_TRANSLATION_COORDS];
107     }
108 
109     /**
110      * Creates transformation with provided rotation.
111      *
112      * @param rotation a 2D rotation.
113      * @throws NullPointerException Raised if provided rotation is null.
114      */
115     public AffineTransformation2D(final Rotation2D rotation) {
116         a = rotation.asInhomogeneousMatrix();
117         translation = new double[NUM_TRANSLATION_COORDS];
118     }
119 
120     /**
121      * Creates transformation with provided scale and rotation.
122      *
123      * @param scale    Scale value. Values between 0.0 and 1.0 reduce objects,
124      *                 values greater than 1.0 enlarge objects and negative values reverse
125      *                 objects.
126      * @param rotation a 2D rotation.
127      * @throws NullPointerException raised if provided rotation is null.
128      */
129     public AffineTransformation2D(final double scale, final Rotation2D rotation) {
130         final var diag = new double[INHOM_COORDS];
131         Arrays.fill(diag, scale);
132         a = Matrix.diagonal(diag);
133         try {
134             a.multiply(rotation.asInhomogeneousMatrix());
135         } catch (final WrongSizeException ignore) {
136             // never happens
137         }
138         translation = new double[NUM_TRANSLATION_COORDS];
139     }
140 
141     /**
142      * Creates transformation with provided affine parameters and rotation.
143      *
144      * @param params   affine parameters including horizontal scaling, vertical
145      *                 scaling and skewness.
146      * @param rotation a 2D rotation.
147      * @throws NullPointerException raised if provided parameters are null or
148      *                              if provided rotation is null.
149      */
150     public AffineTransformation2D(final AffineParameters2D params, final Rotation2D rotation) {
151         a = params.asMatrix();
152         try {
153             a.multiply(rotation.asInhomogeneousMatrix());
154         } catch (final WrongSizeException ignore) {
155             // never happens
156         }
157         translation = new double[NUM_TRANSLATION_COORDS];
158     }
159 
160     /**
161      * Creates transformation with provided 2D translation.
162      *
163      * @param translation array indicating 2D translation using inhomogeneous
164      *                    coordinates.
165      * @throws NullPointerException     raised if provided array is null.
166      * @throws IllegalArgumentException raised if length of array is not equal
167      *                                  to NUM_TRANSLATION_COORDS.
168      */
169     public AffineTransformation2D(final double[] translation) {
170         if (translation.length != NUM_TRANSLATION_COORDS) {
171             throw new IllegalArgumentException();
172         }
173 
174         try {
175             a = Matrix.identity(INHOM_COORDS, INHOM_COORDS);
176         } catch (final WrongSizeException ignore) {
177             // never happens
178         }
179         this.translation = translation;
180     }
181 
182     /**
183      * Creates transformation with provided linear mapping and translation.
184      *
185      * @param a           linear mapping.
186      * @param translation array indicating 2D translation using inhomogeneous
187      *                    coordinates.
188      * @throws NullPointerException     raised if provided array is null or if
189      *                                  linear mapping is null.
190      * @throws IllegalArgumentException raised if length of array is not equal
191      *                                  to NUM_TRANSLATION_COORDS.
192      */
193     public AffineTransformation2D(final Matrix a, final double[] translation) {
194         if (translation.length != NUM_TRANSLATION_COORDS) {
195             throw new IllegalArgumentException();
196         }
197         this.translation = translation;
198 
199         setA(a);
200     }
201 
202     /**
203      * Creates transformation with provided scale and translation.
204      *
205      * @param scale       scale value. Values between 0.0 and 1.0 reduce objects,
206      *                    values greater than 1.0 enlarge objects and negative values reverse
207      *                    objects.
208      * @param translation array indicating 2D translation using inhomogeneous
209      *                    coordinates.
210      * @throws NullPointerException     raised if provided translation is null
211      * @throws IllegalArgumentException raised if provided translation does not
212      *                                  have length 2.
213      */
214     public AffineTransformation2D(final double scale, final double[] translation) {
215         if (translation.length != NUM_TRANSLATION_COORDS) {
216             throw new IllegalArgumentException();
217         }
218 
219         final var diag = new double[INHOM_COORDS];
220         Arrays.fill(diag, scale);
221         a = Matrix.diagonal(diag);
222 
223         this.translation = translation;
224     }
225 
226     /**
227      * Creates transformation with provided rotation and translation.
228      *
229      * @param rotation    a 2D rotation.
230      * @param translation array indicating 2D translation using inhomogeneous
231      *                    coordinates.
232      * @throws NullPointerException     raised if provided rotation or translation
233      *                                  is null.
234      * @throws IllegalArgumentException raised if provided translation does not
235      *                                  have length 2.
236      */
237     public AffineTransformation2D(final Rotation2D rotation, final double[] translation) {
238         if (translation.length != NUM_TRANSLATION_COORDS) {
239             throw new IllegalArgumentException();
240         }
241 
242         a = rotation.asInhomogeneousMatrix();
243         this.translation = translation;
244     }
245 
246     /**
247      * Creates transformation with provided scale, rotation and translation
248      *
249      * @param scale       Scale value. Values between 0.0 and 1.0 reduce objects,
250      *                    values greater than 1.0 enlarge objects and negative values reverse
251      *                    objects.
252      * @param rotation    a 2D rotation.
253      * @param translation array indicating 2D translation using inhomogeneous
254      *                    coordinates.
255      * @throws NullPointerException     raised if provided rotation or translation
256      *                                  is null.
257      * @throws IllegalArgumentException raised if provided translation does not
258      *                                  have length 2.
259      */
260     public AffineTransformation2D(final double scale, final Rotation2D rotation, final double[] translation) {
261         if (translation.length != NUM_TRANSLATION_COORDS) {
262             throw new IllegalArgumentException();
263         }
264 
265         final var diag = new double[INHOM_COORDS];
266         Arrays.fill(diag, scale);
267         a = Matrix.diagonal(diag);
268         try {
269             a.multiply(rotation.asInhomogeneousMatrix());
270         } catch (final WrongSizeException ignore) {
271             // never happens
272         }
273 
274         this.translation = translation;
275     }
276 
277     /**
278      * Creates transformation with provided parameters, rotation and
279      * translation.
280      *
281      * @param params      affine parameters including horizontal scaling, vertical
282      *                    scaling and skewness.
283      * @param rotation    a 2D rotation.
284      * @param translation array indicating 2D translation using inhomogeneous
285      *                    coordinates.
286      * @throws NullPointerException     raised if provided parameters, rotation or
287      *                                  translation is null.
288      * @throws IllegalArgumentException raised if provided translation does not
289      *                                  have length 2.
290      */
291     public AffineTransformation2D(final AffineParameters2D params, final Rotation2D rotation,
292                                   final double[] translation) {
293         if (translation.length != NUM_TRANSLATION_COORDS) {
294             throw new IllegalArgumentException();
295         }
296 
297         a = params.asMatrix();
298         try {
299             a.multiply(rotation.asInhomogeneousMatrix());
300         } catch (final WrongSizeException ignore) {
301             // never happens
302         }
303         this.translation = translation;
304     }
305 
306     /**
307      * Creates transformation by estimating its internal values using provided 3
308      * corresponding original and transformed points.
309      *
310      * @param inputPoint1  1st input point.
311      * @param inputPoint2  2nd input point.
312      * @param inputPoint3  3rd input point.
313      * @param outputPoint1 1st transformed point corresponding to 1st input
314      *                     point.
315      * @param outputPoint2 2nd transformed point corresponding to 2nd input
316      *                     point.
317      * @param outputPoint3 3rd transformed point corresponding to 3rd input
318      *                     point.
319      * @throws CoincidentPointsException raised if transformation cannot be
320      *                                   estimated for some reason (point configuration degeneracy, duplicate
321      *                                   points or numerical instabilities).
322      */
323     public AffineTransformation2D(
324             final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3,
325             final Point2D outputPoint1, final Point2D outputPoint2, final Point2D outputPoint3)
326             throws CoincidentPointsException {
327         try {
328             a = new Matrix(INHOM_COORDS, INHOM_COORDS);
329         } catch (final WrongSizeException ignore) {
330             // never happens
331         }
332         translation = new double[NUM_TRANSLATION_COORDS];
333         setTransformationFromPoints(inputPoint1, inputPoint2, inputPoint3, outputPoint1, outputPoint2, outputPoint3);
334     }
335 
336     /**
337      * Creates transformation by estimating its internal values using provided 3
338      * corresponding original and transformed lines.
339      *
340      * @param inputLine1  1st input line.
341      * @param inputLine2  2nd input line.
342      * @param inputLine3  3rd input line.
343      * @param outputLine1 1st transformed line corresponding to 1st input line.
344      * @param outputLine2 2nd transformed line corresponding to 2nd input line.
345      * @param outputLine3 3rd transformed line corresponding to 3rd input line.
346      * @throws CoincidentLinesException raised if transformation cannot be
347      *                                  estimated for some reason (line configuration degeneracy, duplicate lines
348      *                                  or numerical instabilities).
349      */
350     public AffineTransformation2D(
351             final Line2D inputLine1, final Line2D inputLine2, final Line2D inputLine3,
352             final Line2D outputLine1, final Line2D outputLine2, final Line2D outputLine3)
353             throws CoincidentLinesException {
354         setTransformationFromLines(inputLine1, inputLine2, inputLine3, outputLine1, outputLine2, outputLine3);
355     }
356 
357     /**
358      * Returns linear mapping matrix to perform affine transformation.
359      * Point transformation is computed as a * x + t, where x is a point and t
360      * is the amount of translation.
361      *
362      * @return linear mapping matrix.
363      */
364     public Matrix getA() {
365         return a;
366     }
367 
368     /**
369      * Sets linear mapping matrix to perform affine transformation.
370      *
371      * @param a linear mapping matrix.
372      * @throws NullPointerException     raised if provided matrix is null.
373      * @throws IllegalArgumentException raised if provided matrix does not have
374      *                                  size 2x2.
375      */
376     public final void setA(final Matrix a) {
377         if (a == null) {
378             throw new NullPointerException();
379         }
380         if (a.getRows() != INHOM_COORDS || a.getColumns() != INHOM_COORDS) {
381             throw new IllegalArgumentException();
382         }
383 
384         this.a = a;
385     }
386 
387     /**
388      * Returns 2D rotation assigned to this transformation.
389      * Note: if this rotation instance is modified, its changes won't be
390      * reflected on this instance until rotation is set again.
391      *
392      * @return 2D rotation.
393      * @throws AlgebraException if for some reason rotation cannot
394      *                          be estimated (usually because of numerical instability).
395      */
396     public Rotation2D getRotation() throws AlgebraException {
397         // Use QR decomposition to retrieve rotation
398         final var decomposer = new RQDecomposer(a);
399         try {
400             decomposer.decompose();
401             return new Rotation2D(decomposer.getQ());
402         } catch (final InvalidRotationMatrixException ignore) {
403             return null;
404         }
405     }
406 
407     /**
408      * Sets 2D rotation for this transformation.
409      *
410      * @param rotation a 2D rotation.
411      * @throws NullPointerException raised if provided rotation is null.
412      * @throws AlgebraException     raised if for numerical reasons rotation cannot
413      *                              be set (usually because of numerical instability in parameters of this
414      *                              transformation).
415      */
416     public void setRotation(final Rotation2D rotation) throws AlgebraException {
417         final var rotMatrix = rotation.asInhomogeneousMatrix();
418 
419         // Use QR decomposition to retrieve parameters matrix
420         final var decomposer = new RQDecomposer(a);
421         decomposer.decompose();
422         // retrieves params matrix
423         final var localA = decomposer.getR();
424         localA.multiply(rotMatrix);
425         this.a = localA;
426     }
427 
428     /**
429      * Adds provided rotation to current rotation assigned to this
430      * transformation.
431      *
432      * @param rotation 2D rotation to be added.
433      * @throws AlgebraException raised if for numerical reasons rotation cannot
434      *                          be set (usually because of numerical instability in parameters of this
435      *                          transformation).
436      */
437     public void addRotation(final Rotation2D rotation) throws AlgebraException {
438         final var localRotation = getRotation();
439         localRotation.combine(rotation);
440         setRotation(localRotation);
441     }
442 
443     /**
444      * Sets scale of this transformation.
445      *
446      * @param scale scale value to be set. a value between 0.0 and 1.0 indicates
447      *              that objects will be reduced, a value greater than 1.0 indicates that
448      *              objects will be enlarged, and a negative value indicates that objects
449      *              will be reversed.
450      * @throws AlgebraException raised if for numerical reasons scale cannot
451      *                          be set (usually because of numerical instability in parameters of this
452      *                          transformation).
453      */
454     public void setScale(final double scale) throws AlgebraException {
455         final var decomposer = new RQDecomposer(a);
456         decomposer.decompose();
457         // params
458         final var localA = decomposer.getR();
459         localA.setElementAt(0, 0, scale);
460         localA.setElementAt(1, 1, scale);
461         // multiply by rotation
462         localA.multiply(decomposer.getQ());
463         this.a = localA;
464     }
465 
466     /**
467      * Gets affine parameters of this instance.
468      * Affine parameters contain horizontal scale, vertical scale and skewness
469      * of axes.
470      *
471      * @return affine parameters.
472      * @throws AlgebraException raised if for numerical reasons affine
473      *                          parameters cannot be retrieved (usually because of numerical instability
474      *                          in matrix a).
475      */
476     public AffineParameters2D getParameters() throws AlgebraException {
477         final var parameters = new AffineParameters2D();
478         getParameters(parameters);
479         return parameters;
480     }
481 
482     /**
483      * Computes affine parameters of this instance and stores the result in
484      * provided instance.
485      * Affine parameters contain horizontal scale, vertical scale and skewness
486      * of axes.
487      *
488      * @param result instance where affine parameters will be stored.
489      * @throws AlgebraException raised if for numerical reasons affine
490      *                          parameters cannot be retrieved (usually because of numerical instability
491      *                          in matrix a).
492      */
493     public void getParameters(final AffineParameters2D result) throws AlgebraException {
494         final var decomposer = new RQDecomposer(a);
495         decomposer.decompose();
496         final var params = decomposer.getR();
497         result.fromMatrix(params);
498     }
499 
500     /**
501      * Sets affine parameters of this instance.
502      * Affine parameters contain horizontal scale, vertical scale and skewness
503      * of axes.
504      *
505      * @param parameters affine parameters to be set.
506      * @throws AlgebraException raised if for numerical reasons affine
507      *                          parameters cannot be set (usually because of numerical instability in
508      *                          current matrix a).
509      */
510     public void setParameters(final AffineParameters2D parameters) throws AlgebraException {
511         final var decomposer = new RQDecomposer(a);
512         decomposer.decompose();
513         final var params = parameters.asMatrix();
514         final var rotation = decomposer.getQ();
515 
516         params.multiply(rotation);
517         a = params;
518     }
519 
520     /**
521      * Returns 2D translation assigned to this transformation as an array
522      * expressed in inhomogeneous coordinates.
523      *
524      * @return 2D translation array.
525      */
526     public double[] getTranslation() {
527         return translation;
528     }
529 
530     /**
531      * Sets 2D translation assigned to this transformation as an array expressed
532      * in inhomogeneous coordinates.
533      *
534      * @param translation 2D translation array.
535      * @throws IllegalArgumentException raised if provided array does not have
536      *                                  length equal to NUM_TRANSLATION_COORDS.
537      */
538     public void setTranslation(final double[] translation) {
539         if (translation.length != NUM_TRANSLATION_COORDS) {
540             throw new IllegalArgumentException();
541         }
542 
543         this.translation = translation;
544     }
545 
546     /**
547      * Adds provided translation to current translation on this transformation.
548      * Provided translation must be expressed as an array of inhomogeneous
549      * coordinates.
550      *
551      * @param translation 2D translation array.
552      * @throws IllegalArgumentException raised if provided array does not have
553      *                                  length equal to NUM_TRANSLATION_COORDS.
554      */
555     public void addTranslation(final double[] translation) {
556         ArrayUtils.sum(this.translation, translation, this.translation);
557     }
558 
559     /**
560      * Returns current x coordinate translation assigned to this transformation.
561      *
562      * @return X coordinate translation.
563      */
564     public double getTranslationX() {
565         return translation[0];
566     }
567 
568     /**
569      * Sets x coordinate translation to be made by this transformation.
570      *
571      * @param translationX X coordinate translation to be set.
572      */
573     public void setTranslationX(final double translationX) {
574         translation[0] = translationX;
575     }
576 
577     /**
578      * Returns current y coordinate translation assigned to this transformation.
579      *
580      * @return Y coordinate translation.
581      */
582     public double getTranslationY() {
583         return translation[1];
584     }
585 
586     /**
587      * Sets y coordinate translation to be made by this transformation.
588      *
589      * @param translationY Y coordinate translation to be set.
590      */
591     public void setTranslationY(final double translationY) {
592         translation[1] = translationY;
593     }
594 
595     /**
596      * Sets x, y coordinates of translation to be made by this transformation.
597      *
598      * @param translationX translation x coordinate to be set.
599      * @param translationY translation y coordinate to be set.
600      */
601     public void setTranslation(final double translationX, final double translationY) {
602         translation[0] = translationX;
603         translation[1] = translationY;
604     }
605 
606     /**
607      * Sets x, y, coordinates of translation to be made by this transformation.
608      *
609      * @param translation translation to be set.
610      */
611     public void setTranslation(final Point2D translation) {
612         setTranslation(translation.getInhomX(), translation.getInhomY());
613     }
614 
615     /**
616      * Gets x, y coordinates of translation to be made by this transformation
617      * as a new point.
618      *
619      * @return a new point containing translation coordinates.
620      */
621     public Point2D getTranslationPoint() {
622         final var out = Point2D.create();
623         getTranslationPoint(out);
624         return out;
625     }
626 
627     /**
628      * Gets x, y coordinates of translation to be made by this transformation
629      * and stores them into provided point.
630      *
631      * @param out point where translation coordinates will be stored.
632      */
633     public void getTranslationPoint(final Point2D out) {
634         out.setInhomogeneousCoordinates(translation[0], translation[1]);
635     }
636 
637     /**
638      * Adds provided x coordinate to current translation assigned to this
639      * transformation.
640      *
641      * @param translationX X coordinate to be added to current translation.
642      */
643     public void addTranslationX(final double translationX) {
644         translation[0] += translationX;
645     }
646 
647     /**
648      * Adds provided y coordinate to current translation assigned to this
649      * transformation.
650      *
651      * @param translationY Y coordinate to be added to current translation.
652      */
653     public void addTranslationY(final double translationY) {
654         translation[1] += translationY;
655     }
656 
657     /**
658      * Adds provided coordinates to current translation assigned to this
659      * transformation.
660      *
661      * @param translationX x coordinate to be added to current translation.
662      * @param translationY y coordinate to be added to current translation.
663      */
664     public void addTranslation(final double translationX, final double translationY) {
665         translation[0] += translationX;
666         translation[1] += translationY;
667     }
668 
669     /**
670      * Adds provided coordinates to current translation assigned to this
671      * transformation.
672      *
673      * @param translation x, y, coordinates to be added to current translation.
674      */
675     public void addTranslation(final Point2D translation) {
676         addTranslation(translation.getInhomX(), translation.getInhomY());
677     }
678 
679     /**
680      * Represents this transformation as a 3x3 matrix.
681      * a point can be transformed as T * p, where T is the transformation matrix
682      * and p is a point expressed as an homogeneous vector.
683      *
684      * @return This transformation in matrix form.
685      */
686     @Override
687     public Matrix asMatrix() {
688         Matrix m = null;
689         try {
690             m = new Matrix(HOM_COORDS, HOM_COORDS);
691             asMatrix(m);
692         } catch (final WrongSizeException ignore) {
693             // never happens
694         }
695         return m;
696     }
697 
698     /**
699      * Represents this transformation as a 3x3 matrix and stores the result in
700      * provided instance.
701      *
702      * @param m instance where transformation matrix will be stored.
703      * @throws IllegalArgumentException raised if provided instance is not a 3x3
704      *                                  matrix.
705      */
706     @Override
707     public void asMatrix(final Matrix m) {
708         if (m.getRows() != HOM_COORDS || m.getColumns() != HOM_COORDS) {
709             throw new IllegalArgumentException();
710         }
711 
712         // set rotation
713         m.setSubmatrix(0, 0, INHOM_COORDS - 1, INHOM_COORDS - 1,
714                 a);
715 
716         // set translation
717         m.setSubmatrix(0, HOM_COORDS - 1, translation.length - 1,
718                 HOM_COORDS - 1, translation);
719 
720         // set last element
721         for (var i = 0; i < INHOM_COORDS; i++) {
722             m.setElementAt(INHOM_COORDS, i, 0.0);
723         }
724         m.setElementAt(HOM_COORDS - 1, HOM_COORDS - 1, 1.0);
725     }
726 
727     /**
728      * Transforms input point using this transformation and stores the result in
729      * provided output points.
730      *
731      * @param inputPoint  point to be transformed.
732      * @param outputPoint instance where transformed point data will be stored.
733      */
734     @Override
735     public void transform(final Point2D inputPoint, final Point2D outputPoint) {
736         try {
737             final var coords = new double[Point2D.POINT2D_INHOMOGENEOUS_COORDINATES_LENGTH];
738             coords[0] = inputPoint.getInhomX();
739             coords[1] = inputPoint.getInhomY();
740 
741             final var p = a.multiplyAndReturnNew(Matrix.newFromArray(coords, true));
742 
743             outputPoint.setInhomogeneousCoordinates(p.getElementAtIndex(0) + translation[0],
744                     p.getElementAtIndex(1) + translation[1]);
745         } catch (final WrongSizeException ignore) {
746             // this exception will never be raised
747         }
748     }
749 
750     /**
751      * Transforms a conic using this transformation and stores the result into
752      * provided output conic.
753      *
754      * @param inputConic  conic to be transformed.
755      * @param outputConic instance where data of transformed conic will be
756      *                    stored.
757      * @throws NonSymmetricMatrixException raised if due to numerical precision
758      *                                     the resulting output conic matrix is not considered to be symmetric.
759      * @throws AlgebraException            raised if transform cannot be computed because of
760      *                                     numerical instabilities.
761      */
762     @Override
763     public void transform(final Conic inputConic, final Conic outputConic)
764             throws NonSymmetricMatrixException, AlgebraException {
765         // point' * conic * point = 0
766         // point' * T' * transformedConic * T * point = 0
767         // where:
768         // - transformedPoint = T * point
769 
770         // Hence:
771         // transformedConic = T^-1' * conic * T^-1
772 
773         inputConic.normalize();
774 
775         final var c = inputConic.asMatrix();
776         final var invT = inverseAndReturnNew().asMatrix();
777         // normalize transformation matrix invT to increase accuracy
778         var norm = Utils.normF(invT);
779         invT.multiplyByScalar(1.0 / norm);
780 
781         final var m = invT.transposeAndReturnNew();
782         try {
783             m.multiply(c);
784             m.multiply(invT);
785         } catch (final WrongSizeException ignore) {
786             // never happens
787         }
788 
789         // normalize resulting m matrix to increase accuracy so that it can be
790         // considered symmetric
791         norm = Utils.normF(m);
792         m.multiplyByScalar(1.0 / norm);
793 
794         outputConic.setParameters(m);
795     }
796 
797     /**
798      * Transforms a dual conic using this transformation and stores the result
799      * into provided output dual conic.
800      *
801      * @param inputDualConic  dual conic to be transformed.
802      * @param outputDualConic instance where data of transformed dual conic will
803      *                        be stored.
804      * @throws NonSymmetricMatrixException raised if due to numerical precision
805      *                                     the resulting output dual conic matrix is not considered to be symmetric.
806      */
807     @Override
808     public void transform(final DualConic inputDualConic, final DualConic outputDualConic)
809             throws NonSymmetricMatrixException {
810         // line' * dualConic * line = 0
811         // line' * T^-1 * T * dualConic * T' * T^-1'* line
812 
813         // Hence:
814         // transformed plane: T^-1'* line
815         // transformed dual quadric: T * dualQuadric * T'
816 
817         inputDualConic.normalize();
818 
819         final var dualC = inputDualConic.asMatrix();
820         final var t = asMatrix();
821         // normalize transformation matrix T to increase accuracy
822         var norm = Utils.normF(t);
823         t.multiplyByScalar(1.0 / norm);
824 
825         final var transT = t.transposeAndReturnNew();
826         try {
827             t.multiply(dualC);
828             t.multiply(transT);
829         } catch (final WrongSizeException ignore) {
830             //never happens
831         }
832 
833         // normalize resulting m matrix to increase accuracy so that it can be
834         // considered symmetric
835         norm = Utils.normF(t);
836         t.multiplyByScalar(1.0 / norm);
837 
838         outputDualConic.setParameters(t);
839     }
840 
841     /**
842      * Transforms provided input line using this transformation and stores the
843      * result into provided output line instance.
844      *
845      * @param inputLine  line to be transformed.
846      * @param outputLine instance where data of transformed line will be stored.
847      * @throws AlgebraException raised if transform cannot be computed because
848      *                          of numerical instabilities.
849      */
850     @Override
851     public void transform(final Line2D inputLine, final Line2D outputLine) throws AlgebraException {
852         // line' * point = 0 --> line' * T^-1 * T * point
853         // (line' * T^-1)*(T*point) = (T^-1'*line)'*(T*point)
854         // where:
855         // - transformedLine = T^-1'*line
856         // - transformedPoint = T*point
857 
858         inputLine.normalize();
859 
860         final var invT = inverseAndReturnNew().asMatrix();
861         final var l = Matrix.newFromArray(inputLine.asArray());
862 
863         // normalize transformation matrix T to increase accuracy
864         final var norm = Utils.normF(invT);
865         invT.multiplyByScalar(1.0 / norm);
866 
867         invT.transpose();
868         invT.multiply(l);
869 
870         outputLine.setParameters(invT.toArray());
871     }
872 
873     /**
874      * Inverses this transformation.
875      *
876      * @throws AlgebraException if inverse transform cannot be computed because
877      *                          of numerical instabilities.
878      */
879     public void inverse() throws AlgebraException {
880         inverse(this);
881     }
882 
883     /**
884      * Computes the inverse of this transformation and returns the result as a
885      * new transformation instance.
886      *
887      * @return Inverse transformation.
888      * @throws AlgebraException if inverse transform cannot be computed because
889      *                          of numerical instabilities.
890      */
891     public Transformation2D inverseAndReturnNew() throws AlgebraException {
892         final var result = new AffineTransformation2D();
893         inverse(result);
894         return result;
895     }
896 
897     /**
898      * Computes the inverse of this transformation and stores the result in
899      * provided instance.
900      *
901      * @param result instance where inverse transformation will be stored.
902      * @throws AlgebraException if inverse transform cannot be computed because
903      *                          of numerical instabilities.
904      */
905     public void inverse(final AffineTransformation2D result) throws AlgebraException {
906 
907         // x' = a * x + t -->
908         // a^-1 * x' = a^-1 * a * x + a^-1 * t = x + a^-1 * t -->
909         // x = a^-1 * x' - a^-1 * t
910 
911         try {
912             // reverse rotation
913             final var invA = Utils.inverse(a);
914             result.a = invA;
915 
916             // reverse translation
917             final var t = Matrix.newFromArray(translation, true);
918             t.multiplyByScalar(-1.0);
919 
920             final var resultT = invA.multiplyAndReturnNew(t);
921             result.translation = resultT.toArray();
922         } catch (final WrongSizeException ignore) {
923             // never happens
924         }
925     }
926 
927     /**
928      * Converts this transformation into a metric transformation.
929      *
930      * @return This transformation converted into a projective transformation.
931      */
932     public ProjectiveTransformation2D toProjective() {
933         return new ProjectiveTransformation2D(a, translation);
934     }
935 
936     /**
937      * Combines this transformation with provided transformation.
938      * The combination is equivalent to multiplying the matrix of this
939      * transformation with the matrix of provided transformation.
940      *
941      * @param transformation Transformation to be combined with.
942      */
943     public void combine(final AffineTransformation2D transformation) {
944         combine(transformation, this);
945     }
946 
947     /**
948      * Combines this transformation with provided transformation and returns
949      * the result as a new transformation instance.
950      * The combination is equivalent to multiplying the matrix of this
951      * transformation with the matrix of provided transformation.
952      *
953      * @param transformation Transformation to be combined with
954      * @return a new transformation resulting of the combination with this
955      * transformation and provided transformation.
956      */
957     public AffineTransformation2D combineAndReturnNew(final AffineTransformation2D transformation) {
958 
959         final var result = new AffineTransformation2D();
960         combine(transformation, result);
961         return result;
962     }
963 
964     /**
965      * Combines this transformation with provided input transformation and
966      * stores the result into provided output transformation.
967      * The combination is equivalent to multiplying the matrix of this
968      * transformation with the matrix of provided input transformation.
969      *
970      * @param inputTransformation  transformation to be combined with.
971      * @param outputTransformation transformation where result will be stored.
972      */
973     private void combine(final AffineTransformation2D inputTransformation,
974                          final AffineTransformation2D outputTransformation) {
975         // combination in matrix representation is:
976         // [A1 t1] * [A2 t2] = [A1*A2 + t1*0T  A1*t2 + t1*1] = [A1*A2 A1*t2 + t1]
977         // [0T 1 ]   [0T 1 ]   [0T*A2 + 1*0T   0T*t2 + 1*1 ]   [0T    1         ]
978 
979         try {
980             // we do translation first, because this.rotation might change later
981             final var a1 = new Matrix(this.a);
982             final var t2 = Matrix.newFromArray(inputTransformation.translation, true);
983             // this is R1 * t2
984             a1.multiply(t2);
985 
986             ArrayUtils.sum(a1.toArray(), this.translation, outputTransformation.translation);
987 
988             outputTransformation.a = this.a.multiplyAndReturnNew(inputTransformation.a);
989 
990         } catch (final WrongSizeException ignore) {
991             // never happens
992         }
993     }
994 
995     /**
996      * Estimates this transformation internal parameters by using 3
997      * corresponding original and transformed points.
998      *
999      * @param inputPoint1  1st input point.
1000      * @param inputPoint2  2nd input point.
1001      * @param inputPoint3  3rd input point.
1002      * @param outputPoint1 1st transformed point corresponding to 1st input
1003      *                     point.
1004      * @param outputPoint2 2nd transformed point corresponding to 2nd input
1005      *                     point.
1006      * @param outputPoint3 3rd transformed point corresponding to 3rd input
1007      *                     point.
1008      * @throws CoincidentPointsException raised if transformation cannot be
1009      *                                   estimated for some reason (point configuration degeneracy, duplicate
1010      *                                   points or numerical instabilities).
1011      */
1012     public final void setTransformationFromPoints(
1013             final Point2D inputPoint1, final Point2D inputPoint2, final Point2D inputPoint3,
1014             final Point2D outputPoint1, final Point2D outputPoint2, final Point2D outputPoint3)
1015             throws CoincidentPointsException {
1016 
1017         // normalize points to increase accuracy
1018         inputPoint1.normalize();
1019         inputPoint2.normalize();
1020         inputPoint3.normalize();
1021 
1022         outputPoint1.normalize();
1023         outputPoint2.normalize();
1024         outputPoint3.normalize();
1025 
1026         // matrix of homogeneous linear system of equations.
1027         // There are 7 unknowns and 6 equations (2 for each pair of corresponding
1028         // points)
1029         Matrix m = null;
1030         try {
1031             // build matrix initialized to zero
1032             m = new Matrix(6, 7);
1033 
1034             // 1st pair of points
1035             var iX = inputPoint1.getHomX();
1036             var iY = inputPoint1.getHomY();
1037             var iW = inputPoint1.getHomW();
1038 
1039             var oX = outputPoint1.getHomX();
1040             var oY = outputPoint1.getHomY();
1041             var oW = outputPoint1.getHomW();
1042 
1043             var oWiX = oW * iX;
1044             var oWiY = oW * iY;
1045             var oWiW = oW * iW;
1046 
1047             var oXiW = oX * iW;
1048             var oYiW = oY * iW;
1049 
1050             var tmp = oWiX * oWiX + oWiY * oWiY + oWiW * oWiW;
1051             var norm = Math.sqrt(tmp + oXiW * oXiW);
1052 
1053             m.setElementAt(0, 0, oWiX / norm);
1054             m.setElementAt(0, 1, oWiY / norm);
1055             m.setElementAt(0, 4, oWiW / norm);
1056             m.setElementAt(0, 6, -oXiW / norm);
1057 
1058             norm = Math.sqrt(tmp + oYiW * oYiW);
1059 
1060             m.setElementAt(1, 2, oWiX / norm);
1061             m.setElementAt(1, 3, oWiY / norm);
1062             m.setElementAt(1, 5, oWiW / norm);
1063             m.setElementAt(1, 6, -oYiW / norm);
1064 
1065             // 2nd pair of points
1066             iX = inputPoint2.getHomX();
1067             iY = inputPoint2.getHomY();
1068             iW = inputPoint2.getHomW();
1069 
1070             oX = outputPoint2.getHomX();
1071             oY = outputPoint2.getHomY();
1072             oW = outputPoint2.getHomW();
1073 
1074             oWiX = oW * iX;
1075             oWiY = oW * iY;
1076             oWiW = oW * iW;
1077 
1078             oXiW = oX * iW;
1079             oYiW = oY * iW;
1080 
1081             tmp = oWiX * oWiX + oWiY * oWiY + oWiW * oWiW;
1082             norm = Math.sqrt(tmp + oXiW * oXiW);
1083 
1084             m.setElementAt(2, 0, oWiX / norm);
1085             m.setElementAt(2, 1, oWiY / norm);
1086             m.setElementAt(2, 4, oWiW / norm);
1087             m.setElementAt(2, 6, -oXiW / norm);
1088 
1089             norm = Math.sqrt(tmp + oYiW * oYiW);
1090 
1091             m.setElementAt(3, 2, oWiX / norm);
1092             m.setElementAt(3, 3, oWiY / norm);
1093             m.setElementAt(3, 5, oWiW / norm);
1094             m.setElementAt(3, 6, -oYiW / norm);
1095 
1096             // 3rd pair of points
1097             iX = inputPoint3.getHomX();
1098             iY = inputPoint3.getHomY();
1099             iW = inputPoint3.getHomW();
1100 
1101             oX = outputPoint3.getHomX();
1102             oY = outputPoint3.getHomY();
1103             oW = outputPoint3.getHomW();
1104 
1105             oWiX = oW * iX;
1106             oWiY = oW * iY;
1107             oWiW = oW * iW;
1108 
1109             oXiW = oX * iW;
1110             oYiW = oY * iW;
1111 
1112             tmp = oWiX * oWiX + oWiY * oWiY + oWiW * oWiW;
1113             norm = Math.sqrt(tmp + oXiW * oXiW);
1114 
1115             m.setElementAt(4, 0, oWiX / norm);
1116             m.setElementAt(4, 1, oWiY / norm);
1117             m.setElementAt(4, 4, oWiW / norm);
1118             m.setElementAt(4, 6, -oXiW / norm);
1119 
1120             norm = Math.sqrt(tmp + oYiW * oYiW);
1121 
1122             m.setElementAt(5, 2, oWiX / norm);
1123             m.setElementAt(5, 3, oWiY / norm);
1124             m.setElementAt(5, 5, oWiW / norm);
1125             m.setElementAt(5, 6, -oYiW / norm);
1126         } catch (final WrongSizeException ignore) {
1127             // never happens
1128         }
1129 
1130         // use SVD to decompose matrix m
1131         Matrix v;
1132         try {
1133             final var decomposer = new SingularValueDecomposer(m);
1134             decomposer.decompose();
1135 
1136             // ensure that matrix m has enough rank and there is a unique
1137             // solution (up to scale)
1138             if (decomposer.getRank() < 6) {
1139                 throw new CoincidentPointsException();
1140             }
1141             // V is 7x7
1142             v = decomposer.getV();
1143 
1144             // last column of V will contain parameters of transformation
1145             final var value = v.getElementAt(6, 6);
1146             a.setElementAt(0, 0, v.getElementAt(0, 6) / value);
1147             a.setElementAt(0, 1, v.getElementAt(1, 6) / value);
1148             a.setElementAt(1, 0, v.getElementAt(2, 6) / value);
1149             a.setElementAt(1, 1, v.getElementAt(3, 6) / value);
1150 
1151             translation[0] = v.getElementAt(4, 6) / value;
1152             translation[1] = v.getElementAt(5, 6) / value;
1153 
1154         } catch (final AlgebraException e) {
1155             throw new CoincidentPointsException(e);
1156         }
1157     }
1158 
1159     /**
1160      * Estimates this transformation internal parameters by using 3
1161      * corresponding original and transformed lines.
1162      *
1163      * @param inputLine1  1st input line.
1164      * @param inputLine2  2nd input line.
1165      * @param inputLine3  3rd input line.
1166      * @param outputLine1 1st transformed line corresponding to 1st input
1167      *                    line.
1168      * @param outputLine2 2nd transformed line corresponding to 2nd input
1169      *                    line.
1170      * @param outputLine3 3rd transformed line corresponding to 3rd input
1171      *                    line.
1172      * @throws CoincidentLinesException raised if transformation cannot be
1173      *                                  estimated for some reason (line configuration degeneracy, duplicate
1174      *                                  lines or numerical instabilities).
1175      */
1176     public final void setTransformationFromLines(
1177             final Line2D inputLine1, final Line2D inputLine2,
1178             final Line2D inputLine3, final Line2D outputLine1,
1179             final Line2D outputLine2, final Line2D outputLine3)
1180             throws CoincidentLinesException {
1181 
1182         // normalize points to increase accuracy
1183         inputLine1.normalize();
1184         inputLine2.normalize();
1185         inputLine3.normalize();
1186 
1187         outputLine1.normalize();
1188         outputLine2.normalize();
1189         outputLine3.normalize();
1190 
1191         // matrix of homogeneous linear system of equations.
1192         // There are 7 unknowns and 6 equations (2 for each pair of corresponding
1193         // points)
1194         Matrix m = null;
1195         try {
1196             // build matrix initialized to zero
1197             m = new Matrix(6, 7);
1198 
1199             // 1st pair of lines
1200             var iA = inputLine1.getA();
1201             var iB = inputLine1.getB();
1202             var iC = inputLine1.getC();
1203 
1204             var oA = outputLine1.getA();
1205             var oB = outputLine1.getB();
1206             var oC = outputLine1.getC();
1207 
1208             var oCiA = oC * iA;
1209             var oCiB = oC * iB;
1210 
1211             var oAiA = oA * iA;
1212             var oAiB = oA * iB;
1213             var oAiC = oA * iC;
1214 
1215             var oBiA = oB * iA;
1216             var oBiB = oB * iB;
1217             var oBiC = oB * iC;
1218 
1219             var tmp = oCiA * oCiA + oCiB * oCiB;
1220             var norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC);
1221 
1222             m.setElementAt(0, 0, oCiA / norm);
1223             m.setElementAt(0, 1, oCiB / norm);
1224             m.setElementAt(0, 4, -oAiA / norm);
1225             m.setElementAt(0, 5, -oAiB / norm);
1226             m.setElementAt(0, 6, -oAiC / norm);
1227 
1228             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC);
1229 
1230             m.setElementAt(1, 2, oCiA / norm);
1231             m.setElementAt(1, 3, oCiB / norm);
1232             m.setElementAt(1, 4, -oBiA / norm);
1233             m.setElementAt(1, 5, -oBiB / norm);
1234             m.setElementAt(1, 6, -oBiC / norm);
1235 
1236             // 2nd pair of lines
1237             iA = inputLine2.getA();
1238             iB = inputLine2.getB();
1239             iC = inputLine2.getC();
1240 
1241             oA = outputLine2.getA();
1242             oB = outputLine2.getB();
1243             oC = outputLine2.getC();
1244 
1245             oCiA = oC * iA;
1246             oCiB = oC * iB;
1247 
1248             oAiA = oA * iA;
1249             oAiB = oA * iB;
1250             oAiC = oA * iC;
1251 
1252             oBiA = oB * iA;
1253             oBiB = oB * iB;
1254             oBiC = oB * iC;
1255 
1256             tmp = oCiA * oCiA + oCiB * oCiB;
1257             norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC);
1258 
1259             m.setElementAt(2, 0, oCiA / norm);
1260             m.setElementAt(2, 1, oCiB / norm);
1261             m.setElementAt(2, 4, -oAiA / norm);
1262             m.setElementAt(2, 5, -oAiB / norm);
1263             m.setElementAt(2, 6, -oAiC / norm);
1264 
1265             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC);
1266 
1267             m.setElementAt(3, 2, oCiA / norm);
1268             m.setElementAt(3, 3, oCiB / norm);
1269             m.setElementAt(3, 4, -oBiA / norm);
1270             m.setElementAt(3, 5, -oBiB / norm);
1271             m.setElementAt(3, 6, -oBiC / norm);
1272 
1273             // 3rd pair of lines
1274             iA = inputLine3.getA();
1275             iB = inputLine3.getB();
1276             iC = inputLine3.getC();
1277 
1278             oA = outputLine3.getA();
1279             oB = outputLine3.getB();
1280             oC = outputLine3.getC();
1281 
1282             oCiA = oC * iA;
1283             oCiB = oC * iB;
1284 
1285             oAiA = oA * iA;
1286             oAiB = oA * iB;
1287             oAiC = oA * iC;
1288 
1289             oBiA = oB * iA;
1290             oBiB = oB * iB;
1291             oBiC = oB * iC;
1292 
1293             tmp = oCiA * oCiA + oCiB * oCiB;
1294             norm = Math.sqrt(tmp + oAiA * oAiA + oAiB * oAiB + oAiC * oAiC);
1295 
1296             m.setElementAt(4, 0, oCiA / norm);
1297             m.setElementAt(4, 1, oCiB / norm);
1298             m.setElementAt(4, 4, -oAiA / norm);
1299             m.setElementAt(4, 5, -oAiB / norm);
1300             m.setElementAt(4, 6, -oAiC / norm);
1301 
1302             norm = Math.sqrt(tmp + oBiA * oBiA + oBiB * oBiB + oBiC * oBiC);
1303 
1304             m.setElementAt(5, 2, oCiA / norm);
1305             m.setElementAt(5, 3, oCiB / norm);
1306             m.setElementAt(5, 4, -oBiA / norm);
1307             m.setElementAt(5, 5, -oBiB / norm);
1308             m.setElementAt(5, 6, -oBiC / norm);
1309         } catch (final WrongSizeException ignore) {
1310             // never happens
1311         }
1312 
1313         // use SVD to decompose matrix m
1314         Matrix v;
1315         try {
1316             final var decomposer = new SingularValueDecomposer(m);
1317             decomposer.decompose();
1318 
1319             // ensure that matrix m has enough rank and there is a unique
1320             // solution (up to scale)
1321             if (decomposer.getRank() < 6) {
1322                 throw new CoincidentLinesException();
1323             }
1324             // V is 7x7
1325             v = decomposer.getV();
1326 
1327             // last column of V will contain parameters of transformation
1328             final var value = v.getElementAt(6, 6);
1329 
1330             final var invTransA = new Matrix(AffineParameters2D.INHOM_COORDS, AffineParameters2D.INHOM_COORDS);
1331             // copy former 4 elements of 7th column of V into a in row order
1332             invTransA.setSubmatrix(0, 0, 1, 1,
1333                     v.getSubmatrixAsArray(0, 6, 3, 6),
1334                     false);
1335             // normalize by scale value
1336             invTransA.multiplyByScalar(1.0 / value);
1337 
1338             // initially a contains the inverse of its transpose, so to obtain a we need
1339             // to transpose it and invert it
1340             invTransA.transpose();
1341             final var a2 = Utils.inverse(invTransA);
1342 
1343             final var invt = new Matrix(1, 2);
1344             invt.setSubmatrix(0, 0, 0, 1,
1345                     v.getSubmatrixAsArray(4, 6, 5, 6),
1346                     false);
1347             // normalize by scale value (we need to change sign as well)
1348             invt.multiplyByScalar(-1.0 / value);
1349             invt.transpose();
1350 
1351             final var t = a2.multiplyAndReturnNew(invt);
1352 
1353             this.a = a2;
1354             this.translation = t.getBuffer();
1355         } catch (final AlgebraException e) {
1356             throw new CoincidentLinesException(e);
1357         }
1358     }
1359 }