All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robolectric.shadows.ShadowMatrix Maven / Gradle / Ivy

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