org.robolectric.shadows.ShadowLegacyMatrix Maven / Gradle / Ivy
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
import android.graphics.PointF;
import android.graphics.RectF;
import java.awt.geom.AffineTransform;
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.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
@SuppressWarnings({"UnusedDeclaration"})
@Implements(value = Matrix.class, isInAndroidSdk = false)
public class ShadowLegacyMatrix extends ShadowMatrix {
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 simpleMatrix = SimpleMatrix.newIdentityMatrix();
@Implementation
protected 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.
*/
@Override
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.
*/
@Override
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.
*/
@Override
public Map getSetOperations() {
return Collections.unmodifiableMap(new LinkedHashMap<>(setOps));
}
@Implementation
protected boolean isIdentity() {
return simpleMatrix.equals(SimpleMatrix.IDENTITY);
}
@Implementation(minSdk = LOLLIPOP)
protected boolean isAffine() {
return simpleMatrix.isAffine();
}
@Implementation
protected boolean rectStaysRect() {
return simpleMatrix.rectStaysRect();
}
@Implementation
protected void getValues(float[] values) {
simpleMatrix.getValues(values);
}
@Implementation
protected void setValues(float[] values) {
simpleMatrix = new SimpleMatrix(values);
}
@Implementation
protected void set(Matrix src) {
reset();
if (src != null) {
ShadowLegacyMatrix shadowMatrix = Shadow.extract(src);
preOps.addAll(shadowMatrix.preOps);
postOps.addAll(shadowMatrix.postOps);
setOps.putAll(shadowMatrix.setOps);
simpleMatrix = new SimpleMatrix(getSimpleMatrix(src));
}
}
@Implementation
protected void reset() {
preOps.clear();
postOps.clear();
setOps.clear();
simpleMatrix = SimpleMatrix.newIdentityMatrix();
}
@Implementation
protected void setTranslate(float dx, float dy) {
setOps.put(TRANSLATE, dx + " " + dy);
simpleMatrix = SimpleMatrix.translate(dx, dy);
}
@Implementation
protected void setScale(float sx, float sy, float px, float py) {
setOps.put(SCALE, sx + " " + sy + " " + px + " " + py);
simpleMatrix = SimpleMatrix.scale(sx, sy, px, py);
}
@Implementation
protected void setScale(float sx, float sy) {
setOps.put(SCALE, sx + " " + sy);
simpleMatrix = SimpleMatrix.scale(sx, sy);
}
@Implementation
protected void setRotate(float degrees, float px, float py) {
setOps.put(ROTATE, degrees + " " + px + " " + py);
simpleMatrix = SimpleMatrix.rotate(degrees, px, py);
}
@Implementation
protected void setRotate(float degrees) {
setOps.put(ROTATE, Float.toString(degrees));
simpleMatrix = SimpleMatrix.rotate(degrees);
}
@Implementation
protected void setSinCos(float sinValue, float cosValue, float px, float py) {
setOps.put(SINCOS, sinValue + " " + cosValue + " " + px + " " + py);
simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue, px, py);
}
@Implementation
protected void setSinCos(float sinValue, float cosValue) {
setOps.put(SINCOS, sinValue + " " + cosValue);
simpleMatrix = SimpleMatrix.sinCos(sinValue, cosValue);
}
@Implementation
protected void setSkew(float kx, float ky, float px, float py) {
setOps.put(SKEW, kx + " " + ky + " " + px + " " + py);
simpleMatrix = SimpleMatrix.skew(kx, ky, px, py);
}
@Implementation
protected void setSkew(float kx, float ky) {
setOps.put(SKEW, kx + " " + ky);
simpleMatrix = SimpleMatrix.skew(kx, ky);
}
@Implementation
protected boolean setConcat(Matrix a, Matrix b) {
simpleMatrix = getSimpleMatrix(a).multiply(getSimpleMatrix(b));
return true;
}
@Implementation
protected boolean preTranslate(float dx, float dy) {
preOps.addFirst(TRANSLATE + " " + dx + " " + dy);
return preConcat(SimpleMatrix.translate(dx, dy));
}
@Implementation
protected 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
protected boolean preScale(float sx, float sy) {
preOps.addFirst(SCALE + " " + sx + " " + sy);
return preConcat(SimpleMatrix.scale(sx, sy));
}
@Implementation
protected boolean preRotate(float degrees, float px, float py) {
preOps.addFirst(ROTATE + " " + degrees + " " + px + " " + py);
return preConcat(SimpleMatrix.rotate(degrees, px, py));
}
@Implementation
protected boolean preRotate(float degrees) {
preOps.addFirst(ROTATE + " " + Float.toString(degrees));
return preConcat(SimpleMatrix.rotate(degrees));
}
@Implementation
protected 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
protected boolean preSkew(float kx, float ky) {
preOps.addFirst(SKEW + " " + kx + " " + ky);
return preConcat(SimpleMatrix.skew(kx, ky));
}
@Implementation
protected boolean preConcat(Matrix other) {
preOps.addFirst(MATRIX + " " + other);
return preConcat(getSimpleMatrix(other));
}
@Implementation
protected boolean postTranslate(float dx, float dy) {
postOps.addLast(TRANSLATE + " " + dx + " " + dy);
return postConcat(SimpleMatrix.translate(dx, dy));
}
@Implementation
protected 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
protected boolean postScale(float sx, float sy) {
postOps.addLast(SCALE + " " + sx + " " + sy);
return postConcat(SimpleMatrix.scale(sx, sy));
}
@Implementation
protected boolean postRotate(float degrees, float px, float py) {
postOps.addLast(ROTATE + " " + degrees + " " + px + " " + py);
return postConcat(SimpleMatrix.rotate(degrees, px, py));
}
@Implementation
protected boolean postRotate(float degrees) {
postOps.addLast(ROTATE + " " + Float.toString(degrees));
return postConcat(SimpleMatrix.rotate(degrees));
}
@Implementation
protected 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
protected boolean postSkew(float kx, float ky) {
postOps.addLast(SKEW + " " + kx + " " + ky);
return postConcat(SimpleMatrix.skew(kx, ky));
}
@Implementation
protected boolean postConcat(Matrix other) {
postOps.addLast(MATRIX + " " + other);
return postConcat(getSimpleMatrix(other));
}
@Implementation
protected boolean invert(Matrix inverse) {
final SimpleMatrix inverseMatrix = simpleMatrix.invert();
if (inverseMatrix != null) {
if (inverse != null) {
final ShadowLegacyMatrix shadowInverse = Shadow.extract(inverse);
shadowInverse.simpleMatrix = inverseMatrix;
}
return true;
}
return false;
}
boolean hasPerspective() {
return (simpleMatrix.mValues[6] != 0 || simpleMatrix.mValues[7] != 0 || simpleMatrix.mValues[8] != 1);
}
protected AffineTransform getAffineTransform() {
// the AffineTransform constructor takes the value in a different order
// for a matrix [ 0 1 2 ]
// [ 3 4 5 ]
// the order is 0, 3, 1, 4, 2, 5...
return new AffineTransform(
simpleMatrix.mValues[0],
simpleMatrix.mValues[3],
simpleMatrix.mValues[1],
simpleMatrix.mValues[4],
simpleMatrix.mValues[2],
simpleMatrix.mValues[5]);
}
public PointF mapPoint(float x, float y) {
return simpleMatrix.transform(new PointF(x, y));
}
public PointF mapPoint(PointF point) {
return simpleMatrix.transform(point);
}
@Implementation
protected 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
protected void mapPoints(float[] dst, int dstIndex, float[] src, int srcIndex, int pointCount) {
for (int i = 0; i < pointCount; i++) {
final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
dst[dstIndex + i * 2] = mapped.x;
dst[dstIndex + i * 2 + 1] = mapped.y;
}
}
@Implementation
protected void mapVectors(float[] dst, int dstIndex, float[] src, int srcIndex, int vectorCount) {
final float transX = simpleMatrix.mValues[Matrix.MTRANS_X];
final float transY = simpleMatrix.mValues[Matrix.MTRANS_Y];
simpleMatrix.mValues[Matrix.MTRANS_X] = 0;
simpleMatrix.mValues[Matrix.MTRANS_Y] = 0;
for (int i = 0; i < vectorCount; i++) {
final PointF mapped = mapPoint(src[srcIndex + i * 2], src[srcIndex + i * 2 + 1]);
dst[dstIndex + i * 2] = mapped.x;
dst[dstIndex + i * 2 + 1] = mapped.y;
}
simpleMatrix.mValues[Matrix.MTRANS_X] = transX;
simpleMatrix.mValues[Matrix.MTRANS_Y] = transY;
}
@Implementation
protected float mapRadius(float radius) {
float[] src = new float[] {radius, 0.f, 0.f, radius};
mapVectors(src, 0, src, 0, 2);
float l1 = (float) Math.hypot(src[0], src[1]);
float l2 = (float) Math.hypot(src[2], src[3]);
return (float) Math.sqrt(l1 * l2);
}
@Implementation
protected boolean setRectToRect(RectF src, RectF dst, Matrix.ScaleToFit stf) {
if (src.isEmpty()) {
reset();
return false;
}
return simpleMatrix.setRectToRect(src, dst, stf);
}
@Implementation
@Override
public boolean equals(Object obj) {
if (obj instanceof Matrix) {
return getSimpleMatrix(((Matrix) obj)).equals(simpleMatrix);
} else {
return obj instanceof ShadowMatrix && obj.equals(simpleMatrix);
}
}
@Implementation(minSdk = KITKAT)
@Override
public int hashCode() {
return Objects.hashCode(simpleMatrix);
}
@Override
public String getDescription() {
return "Matrix[pre=" + preOps + ", set=" + setOps + ", post=" + postOps + "]";
}
private static SimpleMatrix getSimpleMatrix(Matrix matrix) {
final ShadowLegacyMatrix otherMatrix = Shadow.extract(matrix);
return otherMatrix.simpleMatrix;
}
private boolean postConcat(SimpleMatrix matrix) {
simpleMatrix = matrix.multiply(simpleMatrix);
return true;
}
private boolean preConcat(SimpleMatrix matrix) {
simpleMatrix = simpleMatrix.multiply(matrix);
return true;
}
/**
* A simple implementation of an immutable matrix.
*/
private static class SimpleMatrix {
private static final SimpleMatrix IDENTITY = newIdentityMatrix();
private static SimpleMatrix newIdentityMatrix() {
return 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]);
}
// See: https://android.googlesource.com/platform/frameworks/base/+/6fca81de9b2079ec88e785f58bf49bf1f0c105e2/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java
protected boolean setRectToRect(RectF src, RectF dst, ScaleToFit stf) {
if (dst.isEmpty()) {
mValues[0] =
mValues[1] =
mValues[2] = mValues[3] = mValues[4] = mValues[5] = mValues[6] = mValues[7] = 0;
mValues[8] = 1;
} else {
float tx = dst.width() / src.width();
float sx = dst.width() / src.width();
float ty = dst.height() / src.height();
float sy = dst.height() / src.height();
boolean xLarger = false;
if (stf != ScaleToFit.FILL) {
if (sx > sy) {
xLarger = true;
sx = sy;
} else {
sy = sx;
}
}
tx = dst.left - src.left * sx;
ty = dst.top - src.top * sy;
if (stf == ScaleToFit.CENTER || stf == ScaleToFit.END) {
float diff;
if (xLarger) {
diff = dst.width() - src.width() * sy;
} else {
diff = dst.height() - src.height() * sy;
}
if (stf == ScaleToFit.CENTER) {
diff = diff / 2;
}
if (xLarger) {
tx += diff;
} else {
ty += diff;
}
}
mValues[0] = sx;
mValues[4] = sy;
mValues[2] = tx;
mValues[5] = ty;
mValues[1] = mValues[3] = mValues[6] = mValues[7] = 0;
}
// shared cleanup
mValues[8] = 1;
return true;
}
@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;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy