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