Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import android.graphics.Matrix;
      4 import android.graphics.PointF;
      5 import android.graphics.RectF;
      6 import java.util.ArrayDeque;
      7 import java.util.ArrayList;
      8 import java.util.Arrays;
      9 import java.util.Collections;
     10 import java.util.Deque;
     11 import java.util.LinkedHashMap;
     12 import java.util.List;
     13 import java.util.Map;
     14 import java.util.Objects;
     15 import org.robolectric.Shadows;
     16 import org.robolectric.annotation.Implementation;
     17 import org.robolectric.annotation.Implements;
     18 import org.robolectric.shadow.api.Shadow;
     19 
     20 @SuppressWarnings({"UnusedDeclaration"})
     21 @Implements(Matrix.class)
     22 public class ShadowMatrix {
     23   public static final String TRANSLATE = "translate";
     24   public static final String SCALE = "scale";
     25   public static final String ROTATE = "rotate";
     26   public static final String SINCOS = "sincos";
     27   public static final String SKEW = "skew";
     28   public static final String MATRIX = "matrix";
     29 
     30   private static final float EPSILON = 1e-3f;
     31 
     32   private final Deque<String> preOps = new ArrayDeque<>();
     33   private final Deque<String> postOps = new ArrayDeque<>();
     34   private final Map<String, String> setOps = new LinkedHashMap<>();
     35 
     36   private SimpleMatrix mMatrix = SimpleMatrix.IDENTITY;
     37 
     38   @Implementation
     39   public void __constructor__(Matrix src) {
     40     set(src);
     41   }
     42 
     43   /**
     44    * A list of all 'pre' operations performed on this Matrix. The last operation performed will
     45    * be first in the list.
     46    * @return A list of all 'pre' operations performed on this Matrix.
     47    */
     48   public List<String> getPreOperations() {
     49     return Collections.unmodifiableList(new ArrayList<>(preOps));
     50   }
     51 
     52   /**
     53    * A list of all 'post' operations performed on this Matrix. The last operation performed will
     54    * be last in the list.
     55    * @return A list of all 'post' operations performed on this Matrix.
     56    */
     57   public List<String> getPostOperations() {
     58     return Collections.unmodifiableList(new ArrayList<>(postOps));
     59   }
     60 
     61   /**
     62    * A map of all 'set' operations performed on this Matrix.
     63    * @return A map of all 'set' operations performed on this Matrix.
     64    */
     65   public Map<String, String> getSetOperations() {
     66     return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
     67   }
     68 
     69   @Implementation
     70   public boolean isIdentity() {
     71     return mMatrix.equals(SimpleMatrix.IDENTITY);
     72   }
     73 
     74   @Implementation
     75   public boolean isAffine() {
     76     return mMatrix.isAffine();
     77   }
     78 
     79   @Implementation
     80   public boolean rectStaysRect() {
     81     return mMatrix.rectStaysRect();
     82   }
     83 
     84   @Implementation
     85   public void getValues(float[] values) {
     86     mMatrix.getValues(values);
     87   }
     88 
     89   @Implementation
     90   public void setValues(float[] values) {
     91     mMatrix = new SimpleMatrix(values);
     92   }
     93 
     94   @Implementation
     95   public void set(Matrix src) {
     96     reset();
     97     if (src != null) {
     98       ShadowMatrix shadowMatrix = Shadows.shadowOf(src);
     99       preOps.addAll(shadowMatrix.preOps);
    100       postOps.addAll(shadowMatrix.postOps);
    101       setOps.putAll(shadowMatrix.setOps);
    102       mMatrix = new SimpleMatrix(getSimpleMatrix(src));
    103     }
    104   }
    105 
    106   @Implementation
    107   public void reset() {
    108     preOps.clear();
    109     postOps.clear();
    110     setOps.clear();
    111     mMatrix = SimpleMatrix.IDENTITY;
    112   }
    113 
    114   @Implementation
    115   public void setTranslate(float dx, float dy) {
    116     setOps.put(TRANSLATE, dx + " " + dy);
    117     mMatrix = SimpleMatrix.translate(dx, dy);
    118   }
    119 
    120   @Implementation
    121   public void setScale(float sx, float sy, float px, float py) {
    122     setOps.put(SCALE, sx + " " + sy + " " + px + " " + py);
    123     mMatrix = SimpleMatrix.scale(sx, sy, px, py);
    124   }
    125 
    126   @Implementation
    127   public void setScale(float sx, float sy) {
    128     setOps.put(SCALE, sx + " " + sy);
    129     mMatrix = SimpleMatrix.scale(sx, sy);
    130   }
    131 
    132   @Implementation
    133   public void setRotate(float degrees, float px, float py) {
    134     setOps.put(ROTATE, degrees + " " + px + " " + py);
    135     mMatrix = SimpleMatrix.rotate(degrees, px, py);
    136   }
    137 
    138   @Implementation
    139   public void setRotate(float degrees) {
    140     setOps.put(ROTATE, Float.toString(degrees));
    141     mMatrix = SimpleMatrix.rotate(degrees);
    142   }
    143 
    144   @Implementation
    145   public void setSinCos(float sinValue, float cosValue, float px, float py) {
    146     setOps.put(SINCOS, sinValue + " " + cosValue + " " + px + " " + py);
    147     mMatrix = SimpleMatrix.sinCos(sinValue, cosValue, px, py);
    148   }
    149 
    150   @Implementation
    151   public void setSinCos(float sinValue, float cosValue) {
    152     setOps.put(SINCOS, sinValue + " " + cosValue);
    153     mMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
    154   }
    155 
    156   @Implementation
    157   public void setSkew(float kx, float ky, float px, float py) {
    158     setOps.put(SKEW, kx + " " + ky + " " + px + " " + py);
    159     mMatrix = SimpleMatrix.skew(kx, ky, px, py);
    160   }
    161 
    162   @Implementation
    163   public void setSkew(float kx, float ky) {
    164     setOps.put(SKEW, kx + " " + ky);
    165     mMatrix = SimpleMatrix.skew(kx, ky);
    166   }
    167 
    168   @Implementation
    169   public boolean setConcat(Matrix a, Matrix b) {
    170     mMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
    171     return true;
    172   }
    173 
    174   @Implementation
    175   public boolean preTranslate(float dx, float dy) {
    176     preOps.addFirst(TRANSLATE + " " + dx + " " + dy);
    177     return preConcat(SimpleMatrix.translate(dx, dy));
    178   }
    179 
    180   @Implementation
    181   public boolean preScale(float sx, float sy, float px, float py) {
    182     preOps.addFirst(SCALE + " " + sx + " " + sy + " " + px + " " + py);
    183     return preConcat(SimpleMatrix.scale(sx, sy, px, py));
    184   }
    185 
    186   @Implementation
    187   public boolean preScale(float sx, float sy) {
    188     preOps.addFirst(SCALE + " " + sx + " " + sy);
    189     return preConcat(SimpleMatrix.scale(sx, sy));
    190   }
    191 
    192   @Implementation
    193   public boolean preRotate(float degrees, float px, float py) {
    194     preOps.addFirst(ROTATE + " " + degrees + " " + px + " " + py);
    195     return preConcat(SimpleMatrix.rotate(degrees, px, py));
    196   }
    197 
    198   @Implementation
    199   public boolean preRotate(float degrees) {
    200     preOps.addFirst(ROTATE + " " + Float.toString(degrees));
    201     return preConcat(SimpleMatrix.rotate(degrees));
    202   }
    203 
    204   @Implementation
    205   public boolean preSkew(float kx, float ky, float px, float py) {
    206     preOps.addFirst(SKEW + " " + kx + " " + ky + " " + px + " " + py);
    207     return preConcat(SimpleMatrix.skew(kx, ky, px, py));
    208   }
    209 
    210   @Implementation
    211   public boolean preSkew(float kx, float ky) {
    212     preOps.addFirst(SKEW + " " + kx + " " + ky);
    213     return preConcat(SimpleMatrix.skew(kx, ky));
    214   }
    215 
    216   @Implementation
    217   public boolean preConcat(Matrix other) {
    218     preOps.addFirst(MATRIX + " " + other);
    219     return preConcat(getSimpleMatrix(other));
    220   }
    221 
    222   @Implementation
    223   public boolean postTranslate(float dx, float dy) {
    224     postOps.addLast(TRANSLATE + " " + dx + " " + dy);
    225     return postConcat(SimpleMatrix.translate(dx, dy));
    226   }
    227 
    228   @Implementation
    229   public boolean postScale(float sx, float sy, float px, float py) {
    230     postOps.addLast(SCALE + " " + sx + " " + sy + " " + px + " " + py);
    231     return postConcat(SimpleMatrix.scale(sx, sy, px, py));
    232   }
    233 
    234   @Implementation
    235   public boolean postScale(float sx, float sy) {
    236     postOps.addLast(SCALE + " " + sx + " " + sy);
    237     return postConcat(SimpleMatrix.scale(sx, sy));
    238   }
    239 
    240   @Implementation
    241   public boolean postRotate(float degrees, float px, float py) {
    242     postOps.addLast(ROTATE + " " + degrees + " " + px + " " + py);
    243     return postConcat(SimpleMatrix.rotate(degrees, px, py));
    244   }
    245 
    246   @Implementation
    247   public boolean postRotate(float degrees) {
    248     postOps.addLast(ROTATE + " " + Float.toString(degrees));
    249     return postConcat(SimpleMatrix.rotate(degrees));
    250   }
    251 
    252   @Implementation
    253   public boolean postSkew(float kx, float ky, float px, float py) {
    254     postOps.addLast(SKEW + " " + kx + " " + ky + " " + px + " " + py);
    255     return postConcat(SimpleMatrix.skew(kx, ky, px, py));
    256   }
    257 
    258   @Implementation
    259   public boolean postSkew(float kx, float ky) {
    260     postOps.addLast(SKEW + " " + kx + " " + ky);
    261     return postConcat(SimpleMatrix.skew(kx, ky));
    262   }
    263 
    264   @Implementation
    265   public boolean postConcat(Matrix other) {
    266     postOps.addLast(MATRIX + " " + other);
    267     return postConcat(getSimpleMatrix(other));
    268   }
    269 
    270   @Implementation
    271   public boolean invert(Matrix inverse) {
    272     final SimpleMatrix inverseMatrix = mMatrix.invert();
    273     if (inverseMatrix != null) {
    274       if (inverse != null) {
    275         final ShadowMatrix shadowInverse = Shadow.extract(inverse);
    276         shadowInverse.mMatrix = inverseMatrix;
    277       }
    278       return true;
    279     }
    280     return false;
    281   }
    282 
    283   public PointF mapPoint(float x, float y) {
    284     return mMatrix.transform(new PointF(x, y));
    285   }
    286 
    287   public PointF mapPoint(PointF point) {
    288     return mMatrix.transform(point);
    289   }
    290 
    291   @Implementation
    292   public boolean mapRect(RectF destination, RectF source) {
    293     final PointF leftTop = mapPoint(source.left, source.top);
    294     final PointF rightBottom = mapPoint(source.right, source.bottom);
    295     destination.set(
    296         Math.min(leftTop.x, rightBottom.x),
    297         Math.min(leftTop.y, rightBottom.y),
    298         Math.max(leftTop.x, rightBottom.x),
    299         Math.max(leftTop.y, rightBottom.y));
    300     return true;
    301   }
    302 
    303   @Implementation
    304   @Override
    305   public boolean equals(Object obj) {
    306     final float[] values;
    307     if (obj instanceof Matrix) {
    308         return getSimpleMatrix(((Matrix) obj)).equals(mMatrix);
    309     } else {
    310         return obj instanceof ShadowMatrix && obj.equals(mMatrix);
    311     }
    312   }
    313 
    314   @Implementation
    315   @Override
    316   public int hashCode() {
    317       return Objects.hashCode(mMatrix);
    318   }
    319 
    320   public String getDescription() {
    321     return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
    322   }
    323 
    324   private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
    325     final ShadowMatrix otherMatrix = Shadow.extract(matrix);
    326     return otherMatrix.mMatrix;
    327   }
    328 
    329   private boolean postConcat(SimpleMatrix matrix) {
    330     mMatrix = matrix.multiply(mMatrix);
    331     return true;
    332   }
    333 
    334   private boolean preConcat(SimpleMatrix matrix) {
    335     mMatrix = mMatrix.multiply(matrix);
    336     return true;
    337   }
    338 
    339   /**
    340    * A simple implementation of an immutable matrix.
    341    */
    342   private static class SimpleMatrix {
    343     private static final SimpleMatrix IDENTITY = new SimpleMatrix(new float[] {
    344         1.0f, 0.0f, 0.0f,
    345         0.0f, 1.0f, 0.0f,
    346         0.0f, 0.0f, 1.0f,
    347     });
    348 
    349     private final float[] mValues;
    350 
    351     SimpleMatrix(SimpleMatrix matrix) {
    352       mValues = Arrays.copyOf(matrix.mValues, matrix.mValues.length);
    353     }
    354 
    355     private SimpleMatrix(float[] values) {
    356       if (values.length != 9) {
    357         throw new ArrayIndexOutOfBoundsException();
    358       }
    359       mValues = Arrays.copyOf(values, 9);
    360     }
    361 
    362     public boolean isAffine() {
    363       return mValues[6] == 0.0f && mValues[7] == 0.0f && mValues[8] == 1.0f;
    364     }
    365 
    366     public boolean rectStaysRect() {
    367       final float m00 = mValues[0];
    368       final float m01 = mValues[1];
    369       final float m10 = mValues[3];
    370       final float m11 = mValues[4];
    371       return (m00 == 0 && m11 == 0 && m01 != 0 && m10 != 0) || (m00 != 0 && m11 != 0 && m01 == 0 && m10 == 0);
    372     }
    373 
    374     public void getValues(float[] values) {
    375       if (values.length < 9) {
    376         throw new ArrayIndexOutOfBoundsException();
    377       }
    378       System.arraycopy(mValues, 0, values, 0, 9);
    379     }
    380 
    381     public static SimpleMatrix translate(float dx, float dy) {
    382       return new SimpleMatrix(new float[] {
    383           1.0f, 0.0f, dx,
    384           0.0f, 1.0f, dy,
    385           0.0f, 0.0f, 1.0f,
    386       });
    387     }
    388 
    389     public static SimpleMatrix scale(float sx, float sy, float px, float py) {
    390       return new SimpleMatrix(new float[] {
    391           sx,   0.0f, px * (1 - sx),
    392           0.0f, sy,   py * (1 - sy),
    393           0.0f, 0.0f, 1.0f,
    394       });
    395     }
    396 
    397     public static SimpleMatrix scale(float sx, float sy) {
    398       return new SimpleMatrix(new float[] {
    399           sx,   0.0f, 0.0f,
    400           0.0f, sy,   0.0f,
    401           0.0f, 0.0f, 1.0f,
    402       });
    403     }
    404 
    405     public static SimpleMatrix rotate(float degrees, float px, float py) {
    406       final double radians = Math.toRadians(degrees);
    407       final float sin = (float) Math.sin(radians);
    408       final float cos = (float) Math.cos(radians);
    409       return sinCos(sin, cos, px, py);
    410     }
    411 
    412     public static SimpleMatrix rotate(float degrees) {
    413       final double radians = Math.toRadians(degrees);
    414       final float sin = (float) Math.sin(radians);
    415       final float cos = (float) Math.cos(radians);
    416       return sinCos(sin, cos);
    417     }
    418 
    419     public static SimpleMatrix sinCos(float sin, float cos, float px, float py) {
    420       return new SimpleMatrix(new float[] {
    421           cos,  -sin, sin * py + (1 - cos) * px,
    422           sin,  cos,  -sin * px + (1 - cos) * py,
    423           0.0f, 0.0f, 1.0f,
    424       });
    425     }
    426 
    427     public static SimpleMatrix sinCos(float sin, float cos) {
    428       return new SimpleMatrix(new float[] {
    429           cos,  -sin, 0.0f,
    430           sin,  cos,  0.0f,
    431           0.0f, 0.0f, 1.0f,
    432       });
    433     }
    434 
    435     public static SimpleMatrix skew(float kx, float ky, float px, float py) {
    436       return new SimpleMatrix(new float[] {
    437           1.0f, kx,   -kx * py,
    438           ky,   1.0f, -ky * px,
    439           0.0f, 0.0f, 1.0f,
    440       });
    441     }
    442 
    443     public static SimpleMatrix skew(float kx, float ky) {
    444       return new SimpleMatrix(new float[] {
    445           1.0f, kx,   0.0f,
    446           ky,   1.0f, 0.0f,
    447           0.0f, 0.0f, 1.0f,
    448       });
    449     }
    450 
    451     public SimpleMatrix multiply(SimpleMatrix matrix) {
    452       final float[] values = new float[9];
    453       for (int i = 0; i < values.length; ++i) {
    454         final int row = i / 3;
    455         final int col = i % 3;
    456         for (int j = 0; j < 3; ++j) {
    457           values[i] += mValues[row * 3 + j] * matrix.mValues[j * 3 + col];
    458         }
    459       }
    460       return new SimpleMatrix(values);
    461     }
    462 
    463     public SimpleMatrix invert() {
    464       final float invDet = inverseDeterminant();
    465       if (invDet == 0) {
    466         return null;
    467       }
    468 
    469       final float[] src = mValues;
    470       final float[] dst = new float[9];
    471       dst[0] = cross_scale(src[4], src[8], src[5], src[7], invDet);
    472       dst[1] = cross_scale(src[2], src[7], src[1], src[8], invDet);
    473       dst[2] = cross_scale(src[1], src[5], src[2], src[4], invDet);
    474 
    475       dst[3] = cross_scale(src[5], src[6], src[3], src[8], invDet);
    476       dst[4] = cross_scale(src[0], src[8], src[2], src[6], invDet);
    477       dst[5] = cross_scale(src[2], src[3], src[0], src[5], invDet);
    478 
    479       dst[6] = cross_scale(src[3], src[7], src[4], src[6], invDet);
    480       dst[7] = cross_scale(src[1], src[6], src[0], src[7], invDet);
    481       dst[8] = cross_scale(src[0], src[4], src[1], src[3], invDet);
    482       return new SimpleMatrix(dst);
    483     }
    484 
    485     public PointF transform(PointF point) {
    486       return new PointF(
    487           point.x * mValues[0] + point.y * mValues[1] + mValues[2],
    488           point.x * mValues[3] + point.y * mValues[4] + mValues[5]);
    489     }
    490 
    491     @Override
    492     public boolean equals(Object o) {
    493       return this == o || (o instanceof SimpleMatrix && equals((SimpleMatrix) o));
    494     }
    495 
    496     @SuppressWarnings("NonOverridingEquals")
    497     public boolean equals(SimpleMatrix matrix) {
    498       if (matrix == null) {
    499         return false;
    500       }
    501       for (int i = 0; i < mValues.length; i++) {
    502         if (!isNearlyZero(matrix.mValues[i] - mValues[i])) {
    503           return false;
    504         }
    505       }
    506       return true;
    507     }
    508 
    509     @Override
    510     public int hashCode() {
    511       return Arrays.hashCode(mValues);
    512     }
    513 
    514     private static boolean isNearlyZero(float value) {
    515       return Math.abs(value) < EPSILON;
    516     }
    517 
    518     private static float cross(float a, float b, float c, float d) {
    519       return a * b - c * d;
    520     }
    521 
    522     private static float cross_scale(float a, float b, float c, float d, float scale) {
    523       return cross(a, b, c, d) * scale;
    524     }
    525 
    526     private float inverseDeterminant() {
    527       final float determinant = mValues[0] * cross(mValues[4], mValues[8], mValues[5], mValues[7]) +
    528           mValues[1] * cross(mValues[5], mValues[6], mValues[3], mValues[8]) +
    529           mValues[2] * cross(mValues[3], mValues[7], mValues[4], mValues[6]);
    530       return isNearlyZero(determinant) ? 0.0f : 1.0f / determinant;
    531     }
    532   }
    533 }
    534