
org.robolectric.shadows.ShadowMatrix Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of framework Show documentation
Show all versions of framework Show documentation
An alternative Android testing framework.
The newest version!
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 preOps = new ArrayDeque<>();
private final Deque postOps = new ArrayDeque<>();
private final Map setOps = new LinkedHashMap<>();
private SimpleMatrix mMatrix = SimpleMatrix.IDENTITY;
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 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 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 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);
}
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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy