blob: 0f45df4685e6b1a187283e4d54abe39c5d74e64d [file] [log] [blame]
package org.robolectric.shadows;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.robolectric.Shadows;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Matrix.class)
public class ShadowMatrix {
public static final String TRANSLATE = "translate";
public static final String SCALE = "scale";
public static final String ROTATE = "rotate";
public static final String SINCOS = "sincos";
public static final String SKEW = "skew";
public static final String MATRIX = "matrix";
private static final float EPSILON = 1e-3f;
private final Deque<String> preOps = new ArrayDeque<>();
private final Deque<String> postOps = new ArrayDeque<>();
private final Map<String, String> setOps = new LinkedHashMap<>();
private SimpleMatrix mMatrix = SimpleMatrix.IDENTITY;
@Implementation
public void __constructor__(Matrix src) {
set(src);
}
/**
* A list of all 'pre' operations performed on this Matrix. The last operation performed will
* be first in the list.
* @return A list of all 'pre' operations performed on this Matrix.
*/
public List<String> getPreOperations() {
return Collections.unmodifiableList(new ArrayList<>(preOps));
}
/**
* A list of all 'post' operations performed on this Matrix. The last operation performed will
* be last in the list.
* @return A list of all 'post' operations performed on this Matrix.
*/
public List<String> getPostOperations() {
return Collections.unmodifiableList(new ArrayList<>(postOps));
}
/**
* A map of all 'set' operations performed on this Matrix.
* @return A map of all 'set' operations performed on this Matrix.
*/
public Map<String, String> getSetOperations() {
return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
}
@Implementation
public boolean isIdentity() {
return mMatrix.equals(SimpleMatrix.IDENTITY);
}
@Implementation
public boolean isAffine() {
return mMatrix.isAffine();
}
@Implementation
public boolean rectStaysRect() {
return mMatrix.rectStaysRect();
}
@Implementation
public void getValues(float[] values) {
mMatrix.getValues(values);
}
@Implementation
public void setValues(float[] values) {
mMatrix = new SimpleMatrix(values);
}
@Implementation
public void set(Matrix src) {
reset();
if (src != null) {
ShadowMatrix shadowMatrix = Shadows.shadowOf(src);
preOps.addAll(shadowMatrix.preOps);
postOps.addAll(shadowMatrix.postOps);
setOps.putAll(shadowMatrix.setOps);
mMatrix = new SimpleMatrix(getSimpleMatrix(src));
}
}
@Implementation
public void reset() {
preOps.clear();
postOps.clear();
setOps.clear();
mMatrix = SimpleMatrix.IDENTITY;
}
@Implementation
public void setTranslate(float dx, float dy) {
setOps.put(TRANSLATE, dx + " " + dy);
mMatrix = SimpleMatrix.translate(dx, dy);
}
@Implementation
public void setScale(float sx, float sy, float px, float py) {
setOps.put(SCALE, sx + " " + sy + " " + px + " " + py);
mMatrix = SimpleMatrix.scale(sx, sy, px, py);
}
@Implementation
public void setScale(float sx, float sy) {
setOps.put(SCALE, sx + " " + sy);
mMatrix = SimpleMatrix.scale(sx, sy);
}
@Implementation
public void setRotate(float degrees, float px, float py) {
setOps.put(ROTATE, degrees + " " + px + " " + py);
mMatrix = SimpleMatrix.rotate(degrees, px, py);
}
@Implementation
public void setRotate(float degrees) {
setOps.put(ROTATE, Float.toString(degrees));
mMatrix = SimpleMatrix.rotate(degrees);
}
@Implementation
public void setSinCos(float sinValue, float cosValue, float px, float py) {
setOps.put(SINCOS, sinValue + " " + cosValue + " " + px + " " + py);
mMatrix = SimpleMatrix.sinCos(sinValue, cosValue, px, py);
}
@Implementation
public void setSinCos(float sinValue, float cosValue) {
setOps.put(SINCOS, sinValue + " " + cosValue);
mMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
}
@Implementation
public void setSkew(float kx, float ky, float px, float py) {
setOps.put(SKEW, kx + " " + ky + " " + px + " " + py);
mMatrix = SimpleMatrix.skew(kx, ky, px, py);
}
@Implementation
public void setSkew(float kx, float ky) {
setOps.put(SKEW, kx + " " + ky);
mMatrix = SimpleMatrix.skew(kx, ky);
}
@Implementation
public boolean setConcat(Matrix a, Matrix b) {
mMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
return true;
}
@Implementation
public boolean preTranslate(float dx, float dy) {
preOps.addFirst(TRANSLATE + " " + dx + " " + dy);
return preConcat(SimpleMatrix.translate(dx, dy));
}
@Implementation
public boolean preScale(float sx, float sy, float px, float py) {
preOps.addFirst(SCALE + " " + sx + " " + sy + " " + px + " " + py);
return preConcat(SimpleMatrix.scale(sx, sy, px, py));
}
@Implementation
public boolean preScale(float sx, float sy) {
preOps.addFirst(SCALE + " " + sx + " " + sy);
return preConcat(SimpleMatrix.scale(sx, sy));
}
@Implementation
public boolean preRotate(float degrees, float px, float py) {
preOps.addFirst(ROTATE + " " + degrees + " " + px + " " + py);
return preConcat(SimpleMatrix.rotate(degrees, px, py));
}
@Implementation
public boolean preRotate(float degrees) {
preOps.addFirst(ROTATE + " " + Float.toString(degrees));
return preConcat(SimpleMatrix.rotate(degrees));
}
@Implementation
public boolean preSkew(float kx, float ky, float px, float py) {
preOps.addFirst(SKEW + " " + kx + " " + ky + " " + px + " " + py);
return preConcat(SimpleMatrix.skew(kx, ky, px, py));
}
@Implementation
public boolean preSkew(float kx, float ky) {
preOps.addFirst(SKEW + " " + kx + " " + ky);
return preConcat(SimpleMatrix.skew(kx, ky));
}
@Implementation
public boolean preConcat(Matrix other) {
preOps.addFirst(MATRIX + " " + other);
return preConcat(getSimpleMatrix(other));
}
@Implementation
public boolean postTranslate(float dx, float dy) {
postOps.addLast(TRANSLATE + " " + dx + " " + dy);
return postConcat(SimpleMatrix.translate(dx, dy));
}
@Implementation
public boolean postScale(float sx, float sy, float px, float py) {
postOps.addLast(SCALE + " " + sx + " " + sy + " " + px + " " + py);
return postConcat(SimpleMatrix.scale(sx, sy, px, py));
}
@Implementation
public boolean postScale(float sx, float sy) {
postOps.addLast(SCALE + " " + sx + " " + sy);
return postConcat(SimpleMatrix.scale(sx, sy));
}
@Implementation
public boolean postRotate(float degrees, float px, float py) {
postOps.addLast(ROTATE + " " + degrees + " " + px + " " + py);
return postConcat(SimpleMatrix.rotate(degrees, px, py));
}
@Implementation
public boolean postRotate(float degrees) {
postOps.addLast(ROTATE + " " + Float.toString(degrees));
return postConcat(SimpleMatrix.rotate(degrees));
}
@Implementation
public boolean postSkew(float kx, float ky, float px, float py) {
postOps.addLast(SKEW + " " + kx + " " + ky + " " + px + " " + py);
return postConcat(SimpleMatrix.skew(kx, ky, px, py));
}
@Implementation
public boolean postSkew(float kx, float ky) {
postOps.addLast(SKEW + " " + kx + " " + ky);
return postConcat(SimpleMatrix.skew(kx, ky));
}
@Implementation
public boolean postConcat(Matrix other) {
postOps.addLast(MATRIX + " " + other);
return postConcat(getSimpleMatrix(other));
}
@Implementation
public boolean invert(Matrix inverse) {
final SimpleMatrix inverseMatrix = mMatrix.invert();
if (inverseMatrix != null) {
if (inverse != null) {
final ShadowMatrix shadowInverse = Shadow.extract(inverse);
shadowInverse.mMatrix = inverseMatrix;
}
return true;
}
return false;
}
public PointF mapPoint(float x, float y) {
return mMatrix.transform(new PointF(x, y));
}
public PointF mapPoint(PointF point) {
return mMatrix.transform(point);
}
@Implementation
public boolean mapRect(RectF destination, RectF source) {
final PointF leftTop = mapPoint(source.left, source.top);
final PointF rightBottom = mapPoint(source.right, source.bottom);
destination.set(
Math.min(leftTop.x, rightBottom.x),
Math.min(leftTop.y, rightBottom.y),
Math.max(leftTop.x, rightBottom.x),
Math.max(leftTop.y, rightBottom.y));
return true;
}
@Implementation
@Override
public boolean equals(Object obj) {
final float[] values;
if (obj instanceof Matrix) {
return getSimpleMatrix(((Matrix) obj)).equals(mMatrix);
} else {
return obj instanceof ShadowMatrix && obj.equals(mMatrix);
}
}
@Implementation
@Override
public int hashCode() {
return Objects.hashCode(mMatrix);
}
public String getDescription() {
return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
}
private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
final ShadowMatrix otherMatrix = Shadow.extract(matrix);
return otherMatrix.mMatrix;
}
private boolean postConcat(SimpleMatrix matrix) {
mMatrix = matrix.multiply(mMatrix);
return true;
}
private boolean preConcat(SimpleMatrix matrix) {
mMatrix = mMatrix.multiply(matrix);
return true;
}
/**
* A simple implementation of an immutable matrix.
*/
private static class SimpleMatrix {
private static final SimpleMatrix IDENTITY = new SimpleMatrix(new float[] {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
});
private final float[] mValues;
SimpleMatrix(SimpleMatrix matrix) {
mValues = Arrays.copyOf(matrix.mValues, matrix.mValues.length);
}
private SimpleMatrix(float[] values) {
if (values.length != 9) {
throw new ArrayIndexOutOfBoundsException();
}
mValues = Arrays.copyOf(values, 9);
}
public boolean isAffine() {
return mValues[6] == 0.0f && mValues[7] == 0.0f && mValues[8] == 1.0f;
}
public boolean rectStaysRect() {
final float m00 = mValues[0];
final float m01 = mValues[1];
final float m10 = mValues[3];
final float m11 = mValues[4];
return (m00 == 0 && m11 == 0 && m01 != 0 && m10 != 0) || (m00 != 0 && m11 != 0 && m01 == 0 && m10 == 0);
}
public void getValues(float[] values) {
if (values.length < 9) {
throw new ArrayIndexOutOfBoundsException();
}
System.arraycopy(mValues, 0, values, 0, 9);
}
public static SimpleMatrix translate(float dx, float dy) {
return new SimpleMatrix(new float[] {
1.0f, 0.0f, dx,
0.0f, 1.0f, dy,
0.0f, 0.0f, 1.0f,
});
}
public static SimpleMatrix scale(float sx, float sy, float px, float py) {
return new SimpleMatrix(new float[] {
sx, 0.0f, px * (1 - sx),
0.0f, sy, py * (1 - sy),
0.0f, 0.0f, 1.0f,
});
}
public static SimpleMatrix scale(float sx, float sy) {
return new SimpleMatrix(new float[] {
sx, 0.0f, 0.0f,
0.0f, sy, 0.0f,
0.0f, 0.0f, 1.0f,
});
}
public static SimpleMatrix rotate(float degrees, float px, float py) {
final double radians = Math.toRadians(degrees);
final float sin = (float) Math.sin(radians);
final float cos = (float) Math.cos(radians);
return sinCos(sin, cos, px, py);
}
public static SimpleMatrix rotate(float degrees) {
final double radians = Math.toRadians(degrees);
final float sin = (float) Math.sin(radians);
final float cos = (float) Math.cos(radians);
return sinCos(sin, cos);
}
public static SimpleMatrix sinCos(float sin, float cos, float px, float py) {
return new SimpleMatrix(new float[] {
cos, -sin, sin * py + (1 - cos) * px,
sin, cos, -sin * px + (1 - cos) * py,
0.0f, 0.0f, 1.0f,
});
}
public static SimpleMatrix sinCos(float sin, float cos) {
return new SimpleMatrix(new float[] {
cos, -sin, 0.0f,
sin, cos, 0.0f,
0.0f, 0.0f, 1.0f,
});
}
public static SimpleMatrix skew(float kx, float ky, float px, float py) {
return new SimpleMatrix(new float[] {
1.0f, kx, -kx * py,
ky, 1.0f, -ky * px,
0.0f, 0.0f, 1.0f,
});
}
public static SimpleMatrix skew(float kx, float ky) {
return new SimpleMatrix(new float[] {
1.0f, kx, 0.0f,
ky, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
});
}
public SimpleMatrix multiply(SimpleMatrix matrix) {
final float[] values = new float[9];
for (int i = 0; i < values.length; ++i) {
final int row = i / 3;
final int col = i % 3;
for (int j = 0; j < 3; ++j) {
values[i] += mValues[row * 3 + j] * matrix.mValues[j * 3 + col];
}
}
return new SimpleMatrix(values);
}
public SimpleMatrix invert() {
final float invDet = inverseDeterminant();
if (invDet == 0) {
return null;
}
final float[] src = mValues;
final float[] dst = new float[9];
dst[0] = cross_scale(src[4], src[8], src[5], src[7], invDet);
dst[1] = cross_scale(src[2], src[7], src[1], src[8], invDet);
dst[2] = cross_scale(src[1], src[5], src[2], src[4], invDet);
dst[3] = cross_scale(src[5], src[6], src[3], src[8], invDet);
dst[4] = cross_scale(src[0], src[8], src[2], src[6], invDet);
dst[5] = cross_scale(src[2], src[3], src[0], src[5], invDet);
dst[6] = cross_scale(src[3], src[7], src[4], src[6], invDet);
dst[7] = cross_scale(src[1], src[6], src[0], src[7], invDet);
dst[8] = cross_scale(src[0], src[4], src[1], src[3], invDet);
return new SimpleMatrix(dst);
}
public PointF transform(PointF point) {
return new PointF(
point.x * mValues[0] + point.y * mValues[1] + mValues[2],
point.x * mValues[3] + point.y * mValues[4] + mValues[5]);
}
@Override
public boolean equals(Object o) {
return this == o || (o instanceof SimpleMatrix && equals((SimpleMatrix) o));
}
@SuppressWarnings("NonOverridingEquals")
public boolean equals(SimpleMatrix matrix) {
if (matrix == null) {
return false;
}
for (int i = 0; i < mValues.length; i++) {
if (!isNearlyZero(matrix.mValues[i] - mValues[i])) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Arrays.hashCode(mValues);
}
private static boolean isNearlyZero(float value) {
return Math.abs(value) < EPSILON;
}
private static float cross(float a, float b, float c, float d) {
return a * b - c * d;
}
private static float cross_scale(float a, float b, float c, float d, float scale) {
return cross(a, b, c, d) * scale;
}
private float inverseDeterminant() {
final float determinant = mValues[0] * cross(mValues[4], mValues[8], mValues[5], mValues[7]) +
mValues[1] * cross(mValues[5], mValues[6], mValues[3], mValues[8]) +
mValues[2] * cross(mValues[3], mValues[7], mValues[4], mValues[6]);
return isNearlyZero(determinant) ? 0.0f : 1.0f / determinant;
}
}
}