1 package org.robolectric.shadows;
2 
3 import android.graphics.Matrix;
4 import android.graphics.Matrix.ScaleToFit;
5 import android.graphics.PointF;
6 import android.graphics.RectF;
7 import java.awt.geom.AffineTransform;
8 import java.util.ArrayDeque;
9 import java.util.ArrayList;
10 import java.util.Arrays;
11 import java.util.Collections;
12 import java.util.Deque;
13 import java.util.LinkedHashMap;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Objects;
17 import org.robolectric.annotation.Implementation;
18 import org.robolectric.annotation.Implements;
19 import org.robolectric.shadow.api.Shadow;
20 
21 @SuppressWarnings({"UnusedDeclaration"})
22 @Implements(value = Matrix.class, isInAndroidSdk = false)
23 public class ShadowLegacyMatrix extends ShadowMatrix {
24 
25   private static final float EPSILON = 1e-3f;
26 
27   private final Deque<String> preOps = new ArrayDeque<>();
28   private final Deque<String> postOps = new ArrayDeque<>();
29   private final Map<String, String> setOps = new LinkedHashMap<>();
30 
31   private SimpleMatrix simpleMatrix = SimpleMatrix.newIdentityMatrix();
32 
33   @Implementation
__constructor__(Matrix src)34   protected void __constructor__(Matrix src) {
35     set(src);
36   }
37 
38   /**
39    * A list of all 'pre' operations performed on this Matrix. The last operation performed will be
40    * first in the list.
41    *
42    * @return A list of all 'pre' operations performed on this Matrix.
43    */
44   @Override
getPreOperations()45   public List<String> getPreOperations() {
46     return Collections.unmodifiableList(new ArrayList<>(preOps));
47   }
48 
49   /**
50    * A list of all 'post' operations performed on this Matrix. The last operation performed will be
51    * last in the list.
52    *
53    * @return A list of all 'post' operations performed on this Matrix.
54    */
55   @Override
getPostOperations()56   public List<String> getPostOperations() {
57     return Collections.unmodifiableList(new ArrayList<>(postOps));
58   }
59 
60   /**
61    * A map of all 'set' operations performed on this Matrix.
62    *
63    * @return A map of all 'set' operations performed on this Matrix.
64    */
65   @Override
getSetOperations()66   public Map<String, String> getSetOperations() {
67     return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
68   }
69 
70   @Implementation
isIdentity()71   protected boolean isIdentity() {
72     return simpleMatrix.equals(SimpleMatrix.IDENTITY);
73   }
74 
75   @Implementation
isAffine()76   protected boolean isAffine() {
77     return simpleMatrix.isAffine();
78   }
79 
80   @Implementation
rectStaysRect()81   protected boolean rectStaysRect() {
82     return simpleMatrix.rectStaysRect();
83   }
84 
85   @Implementation
getValues(float[] values)86   protected void getValues(float[] values) {
87     simpleMatrix.getValues(values);
88   }
89 
90   @Implementation
setValues(float[] values)91   protected void setValues(float[] values) {
92     simpleMatrix = new SimpleMatrix(values);
93   }
94 
95   @Implementation
set(Matrix src)96   protected void set(Matrix src) {
97     reset();
98     if (src != null) {
99       ShadowLegacyMatrix shadowMatrix = Shadow.extract(src);
100       preOps.addAll(shadowMatrix.preOps);
101       postOps.addAll(shadowMatrix.postOps);
102       setOps.putAll(shadowMatrix.setOps);
103       simpleMatrix = new SimpleMatrix(getSimpleMatrix(src));
104     }
105   }
106 
107   @Implementation
reset()108   protected void reset() {
109     preOps.clear();
110     postOps.clear();
111     setOps.clear();
112     simpleMatrix = SimpleMatrix.newIdentityMatrix();
113   }
114 
115   @Implementation
setTranslate(float dx, float dy)116   protected void setTranslate(float dx, float dy) {
117     setOps.put(TRANSLATE, dx + " " + dy);
118     simpleMatrix = SimpleMatrix.translate(dx, dy);
119   }
120 
121   @Implementation
setScale(float sx, float sy, float px, float py)122   protected void setScale(float sx, float sy, float px, float py) {
123     setOps.put(SCALE, sx + " " + sy + " " + px + " " + py);
124     simpleMatrix = SimpleMatrix.scale(sx, sy, px, py);
125   }
126 
127   @Implementation
setScale(float sx, float sy)128   protected void setScale(float sx, float sy) {
129     setOps.put(SCALE, sx + " " + sy);
130     simpleMatrix = SimpleMatrix.scale(sx, sy);
131   }
132 
133   @Implementation
setRotate(float degrees, float px, float py)134   protected void setRotate(float degrees, float px, float py) {
135     setOps.put(ROTATE, degrees + " " + px + " " + py);
136     simpleMatrix = SimpleMatrix.rotate(degrees, px, py);
137   }
138 
139   @Implementation
setRotate(float degrees)140   protected void setRotate(float degrees) {
141     setOps.put(ROTATE, Float.toString(degrees));
142     simpleMatrix = SimpleMatrix.rotate(degrees);
143   }
144 
145   @Implementation
setSinCos(float sinValue, float cosValue, float px, float py)146   protected void setSinCos(float sinValue, float cosValue, float px, float py) {
147     setOps.put(SINCOS, sinValue + " " + cosValue + " " + px + " " + py);
148     simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue, px, py);
149   }
150 
151   @Implementation
setSinCos(float sinValue, float cosValue)152   protected void setSinCos(float sinValue, float cosValue) {
153     setOps.put(SINCOS, sinValue + " " + cosValue);
154     simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
155   }
156 
157   @Implementation
setSkew(float kx, float ky, float px, float py)158   protected void setSkew(float kx, float ky, float px, float py) {
159     setOps.put(SKEW, kx + " " + ky + " " + px + " " + py);
160     simpleMatrix = SimpleMatrix.skew(kx, ky, px, py);
161   }
162 
163   @Implementation
setSkew(float kx, float ky)164   protected void setSkew(float kx, float ky) {
165     setOps.put(SKEW, kx + " " + ky);
166     simpleMatrix = SimpleMatrix.skew(kx, ky);
167   }
168 
169   @Implementation
setConcat(Matrix a, Matrix b)170   protected boolean setConcat(Matrix a, Matrix b) {
171     simpleMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
172     return true;
173   }
174 
175   @Implementation
preTranslate(float dx, float dy)176   protected boolean preTranslate(float dx, float dy) {
177     preOps.addFirst(TRANSLATE + " " + dx + " " + dy);
178     return preConcat(SimpleMatrix.translate(dx, dy));
179   }
180 
181   @Implementation
preScale(float sx, float sy, float px, float py)182   protected boolean preScale(float sx, float sy, float px, float py) {
183     preOps.addFirst(SCALE + " " + sx + " " + sy + " " + px + " " + py);
184     return preConcat(SimpleMatrix.scale(sx, sy, px, py));
185   }
186 
187   @Implementation
preScale(float sx, float sy)188   protected boolean preScale(float sx, float sy) {
189     preOps.addFirst(SCALE + " " + sx + " " + sy);
190     return preConcat(SimpleMatrix.scale(sx, sy));
191   }
192 
193   @Implementation
preRotate(float degrees, float px, float py)194   protected boolean preRotate(float degrees, float px, float py) {
195     preOps.addFirst(ROTATE + " " + degrees + " " + px + " " + py);
196     return preConcat(SimpleMatrix.rotate(degrees, px, py));
197   }
198 
199   @Implementation
preRotate(float degrees)200   protected boolean preRotate(float degrees) {
201     preOps.addFirst(ROTATE + " " + Float.toString(degrees));
202     return preConcat(SimpleMatrix.rotate(degrees));
203   }
204 
205   @Implementation
preSkew(float kx, float ky, float px, float py)206   protected boolean preSkew(float kx, float ky, float px, float py) {
207     preOps.addFirst(SKEW + " " + kx + " " + ky + " " + px + " " + py);
208     return preConcat(SimpleMatrix.skew(kx, ky, px, py));
209   }
210 
211   @Implementation
preSkew(float kx, float ky)212   protected boolean preSkew(float kx, float ky) {
213     preOps.addFirst(SKEW + " " + kx + " " + ky);
214     return preConcat(SimpleMatrix.skew(kx, ky));
215   }
216 
217   @Implementation
preConcat(Matrix other)218   protected boolean preConcat(Matrix other) {
219     preOps.addFirst(MATRIX + " " + other);
220     return preConcat(getSimpleMatrix(other));
221   }
222 
223   @Implementation
postTranslate(float dx, float dy)224   protected boolean postTranslate(float dx, float dy) {
225     postOps.addLast(TRANSLATE + " " + dx + " " + dy);
226     return postConcat(SimpleMatrix.translate(dx, dy));
227   }
228 
229   @Implementation
postScale(float sx, float sy, float px, float py)230   protected boolean postScale(float sx, float sy, float px, float py) {
231     postOps.addLast(SCALE + " " + sx + " " + sy + " " + px + " " + py);
232     return postConcat(SimpleMatrix.scale(sx, sy, px, py));
233   }
234 
235   @Implementation
postScale(float sx, float sy)236   protected boolean postScale(float sx, float sy) {
237     postOps.addLast(SCALE + " " + sx + " " + sy);
238     return postConcat(SimpleMatrix.scale(sx, sy));
239   }
240 
241   @Implementation
postRotate(float degrees, float px, float py)242   protected boolean postRotate(float degrees, float px, float py) {
243     postOps.addLast(ROTATE + " " + degrees + " " + px + " " + py);
244     return postConcat(SimpleMatrix.rotate(degrees, px, py));
245   }
246 
247   @Implementation
postRotate(float degrees)248   protected boolean postRotate(float degrees) {
249     postOps.addLast(ROTATE + " " + Float.toString(degrees));
250     return postConcat(SimpleMatrix.rotate(degrees));
251   }
252 
253   @Implementation
postSkew(float kx, float ky, float px, float py)254   protected boolean postSkew(float kx, float ky, float px, float py) {
255     postOps.addLast(SKEW + " " + kx + " " + ky + " " + px + " " + py);
256     return postConcat(SimpleMatrix.skew(kx, ky, px, py));
257   }
258 
259   @Implementation
postSkew(float kx, float ky)260   protected boolean postSkew(float kx, float ky) {
261     postOps.addLast(SKEW + " " + kx + " " + ky);
262     return postConcat(SimpleMatrix.skew(kx, ky));
263   }
264 
265   @Implementation
postConcat(Matrix other)266   protected boolean postConcat(Matrix other) {
267     postOps.addLast(MATRIX + " " + other);
268     return postConcat(getSimpleMatrix(other));
269   }
270 
271   @Implementation
invert(Matrix inverse)272   protected boolean invert(Matrix inverse) {
273     final SimpleMatrix inverseMatrix = simpleMatrix.invert();
274     if (inverseMatrix != null) {
275       if (inverse != null) {
276         final ShadowLegacyMatrix shadowInverse = Shadow.extract(inverse);
277         shadowInverse.simpleMatrix = inverseMatrix;
278       }
279       return true;
280     }
281     return false;
282   }
283 
hasPerspective()284   boolean hasPerspective() {
285     return (simpleMatrix.mValues[6] != 0
286         || simpleMatrix.mValues[7] != 0
287         || simpleMatrix.mValues[8] != 1);
288   }
289 
getAffineTransform()290   protected AffineTransform getAffineTransform() {
291     // the AffineTransform constructor takes the value in a different order
292     // for a matrix [ 0 1 2 ]
293     //              [ 3 4 5 ]
294     // the order is 0, 3, 1, 4, 2, 5...
295     return new AffineTransform(
296         simpleMatrix.mValues[0],
297         simpleMatrix.mValues[3],
298         simpleMatrix.mValues[1],
299         simpleMatrix.mValues[4],
300         simpleMatrix.mValues[2],
301         simpleMatrix.mValues[5]);
302   }
303 
mapPoint(float x, float y)304   public PointF mapPoint(float x, float y) {
305     return simpleMatrix.transform(new PointF(x, y));
306   }
307 
mapPoint(PointF point)308   public PointF mapPoint(PointF point) {
309     return simpleMatrix.transform(point);
310   }
311 
312   @Implementation
mapRect(RectF destination, RectF source)313   protected boolean mapRect(RectF destination, RectF source) {
314     final PointF leftTop = mapPoint(source.left, source.top);
315     final PointF rightBottom = mapPoint(source.right, source.bottom);
316     destination.set(
317         Math.min(leftTop.x, rightBottom.x),
318         Math.min(leftTop.y, rightBottom.y),
319         Math.max(leftTop.x, rightBottom.x),
320         Math.max(leftTop.y, rightBottom.y));
321     return true;
322   }
323 
324   @Implementation
mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, int pointCount)325   protected void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, int pointCount) {
326     for (int i = 0; i < pointCount; i++) {
327       final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
328       dst[dstIndex + i * 2] = mapped.x;
329       dst[dstIndex + i * 2 + 1] = mapped.y;
330     }
331   }
332 
333   @Implementation
mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount)334   protected void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount) {
335     final float transX = simpleMatrix.mValues[Matrix.MTRANS_X];
336     final float transY = simpleMatrix.mValues[Matrix.MTRANS_Y];
337 
338     simpleMatrix.mValues[Matrix.MTRANS_X] = 0;
339     simpleMatrix.mValues[Matrix.MTRANS_Y] = 0;
340 
341     for (int i = 0; i < vectorCount; i++) {
342       final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
343       dst[dstIndex + i * 2] = mapped.x;
344       dst[dstIndex + i * 2 + 1] = mapped.y;
345     }
346 
347     simpleMatrix.mValues[Matrix.MTRANS_X] = transX;
348     simpleMatrix.mValues[Matrix.MTRANS_Y] = transY;
349   }
350 
351   @Implementation
mapRadius(float radius)352   protected float mapRadius(float radius) {
353     float[] src = new float[] {radius, 0.f, 0.f, radius};
354     mapVectors(src, 0, src, 0, 2);
355 
356     float l1 = (float) Math.hypot(src[0], src[1]);
357     float l2 = (float) Math.hypot(src[2], src[3]);
358     return (float) Math.sqrt(l1 * l2);
359   }
360 
361   @Implementation
setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf)362   protected boolean setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf) {
363     if (src.isEmpty()) {
364       reset();
365       return false;
366     }
367     return simpleMatrix.setRectToRect(src, dst, stf);
368   }
369 
370   @Implementation
371   @Override
equals(Object obj)372   public boolean equals(Object obj) {
373     if (obj instanceof Matrix) {
374       return getSimpleMatrix(((Matrix) obj)).equals(simpleMatrix);
375     } else {
376       return obj instanceof ShadowMatrix && obj.equals(simpleMatrix);
377     }
378   }
379 
380   @Implementation
381   @Override
hashCode()382   public int hashCode() {
383     return Objects.hashCode(simpleMatrix);
384   }
385 
386   @Override
getDescription()387   public String getDescription() {
388     return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
389   }
390 
getSimpleMatrix(Matrix matrix)391   private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
392     final ShadowLegacyMatrix otherMatrix = Shadow.extract(matrix);
393     return otherMatrix.simpleMatrix;
394   }
395 
postConcat(SimpleMatrix matrix)396   private boolean postConcat(SimpleMatrix matrix) {
397     simpleMatrix = matrix.multiply(simpleMatrix);
398     return true;
399   }
400 
preConcat(SimpleMatrix matrix)401   private boolean preConcat(SimpleMatrix matrix) {
402     simpleMatrix = simpleMatrix.multiply(matrix);
403     return true;
404   }
405 
406   /** A simple implementation of an immutable matrix. */
407   private static class SimpleMatrix {
408     private static final SimpleMatrix IDENTITY = newIdentityMatrix();
409 
newIdentityMatrix()410     private static SimpleMatrix newIdentityMatrix() {
411       return new SimpleMatrix(
412           new float[] {
413             1.0f, 0.0f, 0.0f,
414             0.0f, 1.0f, 0.0f,
415             0.0f, 0.0f, 1.0f,
416           });
417     }
418 
419     private final float[] mValues;
420 
SimpleMatrix(SimpleMatrix matrix)421     SimpleMatrix(SimpleMatrix matrix) {
422       mValues = Arrays.copyOf(matrix.mValues, matrix.mValues.length);
423     }
424 
SimpleMatrix(float[] values)425     private SimpleMatrix(float[] values) {
426       if (values.length < 9) {
427         throw new ArrayIndexOutOfBoundsException();
428       }
429       mValues = Arrays.copyOf(values, 9);
430     }
431 
isAffine()432     public boolean isAffine() {
433       return mValues[6] == 0.0f && mValues[7] == 0.0f && mValues[8] == 1.0f;
434     }
435 
rectStaysRect()436     public boolean rectStaysRect() {
437       final float m00 = mValues[0];
438       final float m01 = mValues[1];
439       final float m10 = mValues[3];
440       final float m11 = mValues[4];
441       return (m00 == 0 && m11 == 0 && m01 != 0 && m10 != 0)
442           || (m00 != 0 && m11 != 0 && m01 == 0 && m10 == 0);
443     }
444 
getValues(float[] values)445     public void getValues(float[] values) {
446       if (values.length < 9) {
447         throw new ArrayIndexOutOfBoundsException();
448       }
449       System.arraycopy(mValues, 0, values, 0, 9);
450     }
451 
translate(float dx, float dy)452     public static SimpleMatrix translate(float dx, float dy) {
453       return new SimpleMatrix(
454           new float[] {
455             1.0f, 0.0f, dx,
456             0.0f, 1.0f, dy,
457             0.0f, 0.0f, 1.0f,
458           });
459     }
460 
scale(float sx, float sy, float px, float py)461     public static SimpleMatrix scale(float sx, float sy, float px, float py) {
462       return new SimpleMatrix(
463           new float[] {
464             sx, 0.0f, px * (1 - sx), 0.0f, sy, py * (1 - sy), 0.0f, 0.0f, 1.0f,
465           });
466     }
467 
scale(float sx, float sy)468     public static SimpleMatrix scale(float sx, float sy) {
469       return new SimpleMatrix(
470           new float[] {
471             sx, 0.0f, 0.0f, 0.0f, sy, 0.0f, 0.0f, 0.0f, 1.0f,
472           });
473     }
474 
rotate(float degrees, float px, float py)475     public static SimpleMatrix rotate(float degrees, float px, float py) {
476       final double radians = Math.toRadians(degrees);
477       final float sin = (float) Math.sin(radians);
478       final float cos = (float) Math.cos(radians);
479       return sinCos(sin, cos, px, py);
480     }
481 
rotate(float degrees)482     public static SimpleMatrix rotate(float degrees) {
483       final double radians = Math.toRadians(degrees);
484       final float sin = (float) Math.sin(radians);
485       final float cos = (float) Math.cos(radians);
486       return sinCos(sin, cos);
487     }
488 
sinCos(float sin, float cos, float px, float py)489     public static SimpleMatrix sinCos(float sin, float cos, float px, float py) {
490       return new SimpleMatrix(
491           new float[] {
492             cos,
493             -sin,
494             sin * py + (1 - cos) * px,
495             sin,
496             cos,
497             -sin * px + (1 - cos) * py,
498             0.0f,
499             0.0f,
500             1.0f,
501           });
502     }
503 
sinCos(float sin, float cos)504     public static SimpleMatrix sinCos(float sin, float cos) {
505       return new SimpleMatrix(
506           new float[] {
507             cos, -sin, 0.0f, sin, cos, 0.0f, 0.0f, 0.0f, 1.0f,
508           });
509     }
510 
skew(float kx, float ky, float px, float py)511     public static SimpleMatrix skew(float kx, float ky, float px, float py) {
512       return new SimpleMatrix(
513           new float[] {
514             1.0f, kx, -kx * py, ky, 1.0f, -ky * px, 0.0f, 0.0f, 1.0f,
515           });
516     }
517 
skew(float kx, float ky)518     public static SimpleMatrix skew(float kx, float ky) {
519       return new SimpleMatrix(
520           new float[] {
521             1.0f, kx, 0.0f, ky, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f,
522           });
523     }
524 
multiply(SimpleMatrix matrix)525     public SimpleMatrix multiply(SimpleMatrix matrix) {
526       final float[] values = new float[9];
527       for (int i = 0; i < values.length; ++i) {
528         final int row = i / 3;
529         final int col = i % 3;
530         for (int j = 0; j < 3; ++j) {
531           values[i] += mValues[row * 3 + j] * matrix.mValues[j * 3 + col];
532         }
533       }
534       return new SimpleMatrix(values);
535     }
536 
invert()537     public SimpleMatrix invert() {
538       final float invDet = inverseDeterminant();
539       if (invDet == 0) {
540         return null;
541       }
542 
543       final float[] src = mValues;
544       final float[] dst = new float[9];
545       dst[0] = cross_scale(src[4], src[8], src[5], src[7], invDet);
546       dst[1] = cross_scale(src[2], src[7], src[1], src[8], invDet);
547       dst[2] = cross_scale(src[1], src[5], src[2], src[4], invDet);
548 
549       dst[3] = cross_scale(src[5], src[6], src[3], src[8], invDet);
550       dst[4] = cross_scale(src[0], src[8], src[2], src[6], invDet);
551       dst[5] = cross_scale(src[2], src[3], src[0], src[5], invDet);
552 
553       dst[6] = cross_scale(src[3], src[7], src[4], src[6], invDet);
554       dst[7] = cross_scale(src[1], src[6], src[0], src[7], invDet);
555       dst[8] = cross_scale(src[0], src[4], src[1], src[3], invDet);
556       return new SimpleMatrix(dst);
557     }
558 
transform(PointF point)559     public PointF transform(PointF point) {
560       return new PointF(
561           point.x * mValues[0] + point.y * mValues[1] + mValues[2],
562           point.x * mValues[3] + point.y * mValues[4] + mValues[5]);
563     }
564 
565     // See:
566     // https://android.googlesource.com/platform/frameworks/base/+/6fca81de9b2079ec88e785f58bf49bf1f0c105e2/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
setRectToRect(RectF src, RectF dst, ScaleToFit stf)567     protected boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
568       if (dst.isEmpty()) {
569         mValues[0] =
570             mValues[1] =
571                 mValues[2] = mValues[3] = mValues[4] = mValues[5] = mValues[6] = mValues[7] = 0;
572         mValues[8] = 1;
573       } else {
574         float tx = dst.width() / src.width();
575         float sx = dst.width() / src.width();
576         float ty = dst.height() / src.height();
577         float sy = dst.height() / src.height();
578         boolean xLarger = false;
579 
580         if (stf != ScaleToFit.FILL) {
581           if (sx > sy) {
582             xLarger = true;
583             sx = sy;
584           } else {
585             sy = sx;
586           }
587         }
588 
589         tx = dst.left - src.left * sx;
590         ty = dst.top - src.top * sy;
591         if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) {
592           float diff;
593 
594           if (xLarger) {
595             diff = dst.width() - src.width() * sy;
596           } else {
597             diff = dst.height() - src.height() * sy;
598           }
599 
600           if (stf == ScaleToFit.CENTER) {
601             diff = diff / 2;
602           }
603 
604           if (xLarger) {
605             tx += diff;
606           } else {
607             ty += diff;
608           }
609         }
610 
611         mValues[0] = sx;
612         mValues[4] = sy;
613         mValues[2] = tx;
614         mValues[5] = ty;
615         mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0;
616       }
617       // shared cleanup
618       mValues[8] = 1;
619       return true;
620     }
621 
622     @Override
equals(Object o)623     public boolean equals(Object o) {
624       return this == o || (o instanceof SimpleMatrix && equals((SimpleMatrix) o));
625     }
626 
627     @SuppressWarnings("NonOverridingEquals")
equals(SimpleMatrix matrix)628     public boolean equals(SimpleMatrix matrix) {
629       if (matrix == null) {
630         return false;
631       }
632       for (int i = 0; i < mValues.length; i++) {
633         if (!isNearlyZero(matrix.mValues[i] - mValues[i])) {
634           return false;
635         }
636       }
637       return true;
638     }
639 
640     @Override
hashCode()641     public int hashCode() {
642       return Arrays.hashCode(mValues);
643     }
644 
isNearlyZero(float value)645     private static boolean isNearlyZero(float value) {
646       return Math.abs(value) < EPSILON;
647     }
648 
cross(float a, float b, float c, float d)649     private static float cross(float a, float b, float c, float d) {
650       return a * b - c * d;
651     }
652 
cross_scale(float a, float b, float c, float d, float scale)653     private static float cross_scale(float a, float b, float c, float d, float scale) {
654       return cross(a, b, c, d) * scale;
655     }
656 
inverseDeterminant()657     private float inverseDeterminant() {
658       final float determinant =
659           mValues[0] * cross(mValues[4], mValues[8], mValues[5], mValues[7])
660               + mValues[1] * cross(mValues[5], mValues[6], mValues[3], mValues[8])
661               + mValues[2] * cross(mValues[3], mValues[7], mValues[4], mValues[6]);
662       return isNearlyZero(determinant) ? 0.0f : 1.0f / determinant;
663     }
664   }
665 }
666