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