Home | History | Annotate | Download | only in shadows
      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
     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    */
     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    */
     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    */
     69   public Map<String, String> getSetOperations() {
     70     return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
     71   }
     72 
     73   @Implementation
     74   protected boolean isIdentity() {
     75     return simpleMatrix.equals(SimpleMatrix.IDENTITY);
     76   }
     77 
     78   @Implementation(minSdk = LOLLIPOP)
     79   protected boolean isAffine() {
     80     return simpleMatrix.isAffine();
     81   }
     82 
     83   @Implementation
     84   protected boolean rectStaysRect() {
     85     return simpleMatrix.rectStaysRect();
     86   }
     87 
     88   @Implementation
     89   protected void getValues(float[] values) {
     90     simpleMatrix.getValues(values);
     91   }
     92 
     93   @Implementation
     94   protected void setValues(float[] values) {
     95     simpleMatrix = new SimpleMatrix(values);
     96   }
     97 
     98   @Implementation
     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
    111   protected void reset() {
    112     preOps.clear();
    113     postOps.clear();
    114     setOps.clear();
    115     simpleMatrix = SimpleMatrix.newIdentityMatrix();
    116   }
    117 
    118   @Implementation
    119   protected void setTranslate(float dx, float dy) {
    120     setOps.put(TRANSLATE, dx + " " + dy);
    121     simpleMatrix = SimpleMatrix.translate(dx, dy);
    122   }
    123 
    124   @Implementation
    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
    131   protected void setScale(float sx, float sy) {
    132     setOps.put(SCALE, sx + " " + sy);
    133     simpleMatrix = SimpleMatrix.scale(sx, sy);
    134   }
    135 
    136   @Implementation
    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
    143   protected void setRotate(float degrees) {
    144     setOps.put(ROTATE, Float.toString(degrees));
    145     simpleMatrix = SimpleMatrix.rotate(degrees);
    146   }
    147 
    148   @Implementation
    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
    155   protected void setSinCos(float sinValue, float cosValue) {
    156     setOps.put(SINCOS, sinValue + " " + cosValue);
    157     simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
    158   }
    159 
    160   @Implementation
    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
    167   protected void setSkew(float kx, float ky) {
    168     setOps.put(SKEW, kx + " " + ky);
    169     simpleMatrix = SimpleMatrix.skew(kx, ky);
    170   }
    171 
    172   @Implementation
    173   protected boolean setConcat(Matrix a, Matrix b) {
    174     simpleMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
    175     return true;
    176   }
    177 
    178   @Implementation
    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
    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
    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
    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
    203   protected boolean preRotate(float degrees) {
    204     preOps.addFirst(ROTATE + " " + Float.toString(degrees));
    205     return preConcat(SimpleMatrix.rotate(degrees));
    206   }
    207 
    208   @Implementation
    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
    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
    221   protected boolean preConcat(Matrix other) {
    222     preOps.addFirst(MATRIX + " " + other);
    223     return preConcat(getSimpleMatrix(other));
    224   }
    225 
    226   @Implementation
    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
    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
    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
    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
    251   protected boolean postRotate(float degrees) {
    252     postOps.addLast(ROTATE + " " + Float.toString(degrees));
    253     return postConcat(SimpleMatrix.rotate(degrees));
    254   }
    255 
    256   @Implementation
    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
    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
    269   protected boolean postConcat(Matrix other) {
    270     postOps.addLast(MATRIX + " " + other);
    271     return postConcat(getSimpleMatrix(other));
    272   }
    273 
    274   @Implementation
    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 
    287   boolean hasPerspective() {
    288     return (simpleMatrix.mValues[6] != 0 || simpleMatrix.mValues[7] != 0 || simpleMatrix.mValues[8] != 1);
    289   }
    290 
    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 
    305   public PointF mapPoint(float x, float y) {
    306     return simpleMatrix.transform(new PointF(x, y));
    307   }
    308 
    309   public PointF mapPoint(PointF point) {
    310     return simpleMatrix.transform(point);
    311   }
    312 
    313   @Implementation
    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
    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
    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
    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
    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
    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
    383   public int hashCode() {
    384       return Objects.hashCode(simpleMatrix);
    385   }
    386 
    387   public String getDescription() {
    388     return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
    389   }
    390 
    391   private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
    392     final ShadowMatrix otherMatrix = Shadow.extract(matrix);
    393     return otherMatrix.simpleMatrix;
    394   }
    395 
    396   private boolean postConcat(SimpleMatrix matrix) {
    397     simpleMatrix = matrix.multiply(simpleMatrix);
    398     return true;
    399   }
    400 
    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 
    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 
    423     SimpleMatrix(SimpleMatrix matrix) {
    424       mValues = Arrays.copyOf(matrix.mValues, matrix.mValues.length);
    425     }
    426 
    427     private SimpleMatrix(float[] values) {
    428       if (values.length != 9) {
    429         throw new ArrayIndexOutOfBoundsException();
    430       }
    431       mValues = Arrays.copyOf(values, 9);
    432     }
    433 
    434     public boolean isAffine() {
    435       return mValues[6] == 0.0f && mValues[7] == 0.0f && mValues[8] == 1.0f;
    436     }
    437 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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
    620     public boolean equals(Object o) {
    621       return this == o || (o instanceof SimpleMatrix && equals((SimpleMatrix) o));
    622     }
    623 
    624     @SuppressWarnings("NonOverridingEquals")
    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
    638     public int hashCode() {
    639       return Arrays.hashCode(mValues);
    640     }
    641 
    642     private static boolean isNearlyZero(float value) {
    643       return Math.abs(value) < EPSILON;
    644     }
    645 
    646     private static float cross(float a, float b, float c, float d) {
    647       return a * b - c * d;
    648     }
    649 
    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 
    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