1package org.robolectric.shadows;
2
3import android.graphics.Matrix;
4import android.graphics.PointF;
5import android.graphics.RectF;
6import java.util.ArrayDeque;
7import java.util.ArrayList;
8import java.util.Arrays;
9import java.util.Collections;
10import java.util.Deque;
11import java.util.LinkedHashMap;
12import java.util.List;
13import java.util.Map;
14import java.util.Objects;
15import org.robolectric.Shadows;
16import org.robolectric.annotation.Implementation;
17import org.robolectric.annotation.Implements;
18import org.robolectric.shadow.api.Shadow;
19
20@SuppressWarnings({"UnusedDeclaration"})
21@Implements(Matrix.class)
22public 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