1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1; 4 import static android.os.Build.VERSION_CODES.KITKAT; 5 import static android.os.Build.VERSION_CODES.M; 6 7 import android.graphics.Bitmap; 8 import android.graphics.Color; 9 import android.graphics.Matrix; 10 import android.graphics.RectF; 11 import android.os.Build; 12 import android.os.Parcel; 13 import android.util.DisplayMetrics; 14 import java.io.FileDescriptor; 15 import java.io.InputStream; 16 import java.io.OutputStream; 17 import java.nio.Buffer; 18 import java.nio.ByteBuffer; 19 import java.util.Arrays; 20 import org.robolectric.annotation.Implementation; 21 import org.robolectric.annotation.Implements; 22 import org.robolectric.annotation.RealObject; 23 import org.robolectric.shadow.api.Shadow; 24 import org.robolectric.util.ReflectionHelpers; 25 26 @SuppressWarnings({"UnusedDeclaration"}) 27 @Implements(Bitmap.class) 28 public class ShadowBitmap { 29 /** Number of bytes used internally to represent each pixel (in the {@link #colors} array) */ 30 private static final int INTERNAL_BYTES_PER_PIXEL = 4; 31 32 @RealObject 33 private Bitmap realBitmap; 34 35 int createdFromResId = -1; 36 String createdFromPath; 37 InputStream createdFromStream; 38 FileDescriptor createdFromFileDescriptor; 39 byte[] createdFromBytes; 40 private Bitmap createdFromBitmap; 41 private int createdFromX = -1; 42 private int createdFromY = -1; 43 private int createdFromWidth = -1; 44 private int createdFromHeight = -1; 45 private int[] createdFromColors; 46 private Matrix createdFromMatrix; 47 private boolean createdFromFilter; 48 private boolean hasAlpha; 49 50 private int width; 51 private int height; 52 private int density; 53 private int[] colors; 54 private Bitmap.Config config; 55 private boolean mutable; 56 private String description = ""; 57 private boolean recycled = false; 58 private boolean hasMipMap; 59 private boolean isPremultiplied; 60 61 /** 62 * Returns a textual representation of the appearance of the object. 63 * 64 * @param bitmap the bitmap to visualize 65 * @return Textual representation of the appearance of the object. 66 */ visualize(Bitmap bitmap)67 public static String visualize(Bitmap bitmap) { 68 ShadowBitmap shadowBitmap = Shadow.extract(bitmap); 69 return shadowBitmap.getDescription(); 70 } 71 72 /** 73 * Reference to original Bitmap from which this Bitmap was created. {@code null} if this Bitmap 74 * was not copied from another instance. 75 * 76 * @return Original Bitmap from which this Bitmap was created. 77 */ getCreatedFromBitmap()78 public Bitmap getCreatedFromBitmap() { 79 return createdFromBitmap; 80 } 81 82 /** 83 * Resource ID from which this Bitmap was created. {@code 0} if this Bitmap was not created 84 * from a resource. 85 * 86 * @return Resource ID from which this Bitmap was created. 87 */ getCreatedFromResId()88 public int getCreatedFromResId() { 89 return createdFromResId; 90 } 91 92 /** 93 * Path from which this Bitmap was created. {@code null} if this Bitmap was not create from a 94 * path. 95 * 96 * @return Path from which this Bitmap was created. 97 */ getCreatedFromPath()98 public String getCreatedFromPath() { 99 return createdFromPath; 100 } 101 102 /** 103 * {@link InputStream} from which this Bitmap was created. {@code null} if this Bitmap was not 104 * created from a stream. 105 * 106 * @return InputStream from which this Bitmap was created. 107 */ getCreatedFromStream()108 public InputStream getCreatedFromStream() { 109 return createdFromStream; 110 } 111 112 /** 113 * Bytes from which this Bitmap was created. {@code null} if this Bitmap was not created from 114 * bytes. 115 * 116 * @return Bytes from which this Bitmap was created. 117 */ getCreatedFromBytes()118 public byte[] getCreatedFromBytes() { 119 return createdFromBytes; 120 } 121 122 /** 123 * Horizontal offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1. 124 * 125 * @return Horizontal offset within {@link #getCreatedFromBitmap()}. 126 */ getCreatedFromX()127 public int getCreatedFromX() { 128 return createdFromX; 129 } 130 131 /** 132 * Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1. 133 * 134 * @return Vertical offset within {@link #getCreatedFromBitmap()} of this Bitmap's content, or -1. 135 */ getCreatedFromY()136 public int getCreatedFromY() { 137 return createdFromY; 138 } 139 140 /** 141 * Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's 142 * content, or -1. 143 * 144 * @return Width from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's 145 * content, or -1. 146 */ getCreatedFromWidth()147 public int getCreatedFromWidth() { 148 return createdFromWidth; 149 } 150 151 /** 152 * Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's 153 * content, or -1. 154 * @return Height from {@link #getCreatedFromX()} within {@link #getCreatedFromBitmap()} of this Bitmap's 155 * content, or -1. 156 */ getCreatedFromHeight()157 public int getCreatedFromHeight() { 158 return createdFromHeight; 159 } 160 161 /** 162 * Color array from which this Bitmap was created. {@code null} if this Bitmap was not created 163 * from a color array. 164 * @return Color array from which this Bitmap was created. 165 */ getCreatedFromColors()166 public int[] getCreatedFromColors() { 167 return createdFromColors; 168 } 169 170 /** 171 * Matrix from which this Bitmap's content was transformed, or {@code null}. 172 * @return Matrix from which this Bitmap's content was transformed, or {@code null}. 173 */ getCreatedFromMatrix()174 public Matrix getCreatedFromMatrix() { 175 return createdFromMatrix; 176 } 177 178 /** 179 * {@code true} if this Bitmap was created with filtering. 180 * @return {@code true} if this Bitmap was created with filtering. 181 */ getCreatedFromFilter()182 public boolean getCreatedFromFilter() { 183 return createdFromFilter; 184 } 185 186 @Implementation compress(Bitmap.CompressFormat format, int quality, OutputStream stream)187 protected boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) { 188 appendDescription(" compressed as " + format + " with quality " + quality); 189 return ImageUtil.writeToStream(realBitmap, format, quality, stream); 190 } 191 192 @Implementation createBitmap(int width, int height, Bitmap.Config config)193 protected static Bitmap createBitmap(int width, int height, Bitmap.Config config) { 194 return createBitmap((DisplayMetrics) null, width, height, config); 195 } 196 197 @Implementation(minSdk = JELLY_BEAN_MR1) createBitmap( DisplayMetrics displayMetrics, int width, int height, Bitmap.Config config, boolean hasAlpha)198 protected static Bitmap createBitmap( 199 DisplayMetrics displayMetrics, 200 int width, 201 int height, 202 Bitmap.Config config, 203 boolean hasAlpha) { 204 return createBitmap((DisplayMetrics) null, width, height, config); 205 } 206 207 @Implementation(minSdk = JELLY_BEAN_MR1) createBitmap( DisplayMetrics displayMetrics, int width, int height, Bitmap.Config config)208 protected static Bitmap createBitmap( 209 DisplayMetrics displayMetrics, int width, int height, Bitmap.Config config) { 210 if (width <= 0 || height <= 0) { 211 throw new IllegalArgumentException("width and height must be > 0"); 212 } 213 Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 214 ShadowBitmap shadowBitmap = Shadow.extract(scaledBitmap); 215 shadowBitmap.setDescription("Bitmap (" + width + " x " + height + ")"); 216 217 shadowBitmap.width = width; 218 shadowBitmap.height = height; 219 shadowBitmap.config = config; 220 shadowBitmap.setMutable(true); 221 if (displayMetrics != null) { 222 shadowBitmap.density = displayMetrics.densityDpi; 223 } 224 shadowBitmap.setPixels(new int[shadowBitmap.getHeight() * shadowBitmap.getWidth()], 0, shadowBitmap.getWidth(), 0, 0, shadowBitmap.getWidth(), shadowBitmap.getHeight()); 225 return scaledBitmap; 226 } 227 228 @Implementation createBitmap(Bitmap src)229 protected static Bitmap createBitmap(Bitmap src) { 230 ShadowBitmap shadowBitmap = Shadow.extract(src); 231 shadowBitmap.appendDescription(" created from Bitmap object"); 232 return src; 233 } 234 235 @Implementation createScaledBitmap( Bitmap src, int dstWidth, int dstHeight, boolean filter)236 protected static Bitmap createScaledBitmap( 237 Bitmap src, int dstWidth, int dstHeight, boolean filter) { 238 if (dstWidth == src.getWidth() && dstHeight == src.getHeight() && !filter) { 239 return src; // Return the original. 240 } 241 242 Bitmap scaledBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 243 ShadowBitmap shadowBitmap = Shadow.extract(scaledBitmap); 244 245 ShadowBitmap shadowSrcBitmap = Shadow.extract(src); 246 shadowBitmap.appendDescription(shadowSrcBitmap.getDescription()); 247 shadowBitmap.appendDescription(" scaled to " + dstWidth + " x " + dstHeight); 248 if (filter) { 249 shadowBitmap.appendDescription(" with filter " + filter); 250 } 251 252 shadowBitmap.createdFromBitmap = src; 253 shadowBitmap.createdFromFilter = filter; 254 shadowBitmap.width = dstWidth; 255 shadowBitmap.height = dstHeight; 256 shadowBitmap.setPixels(new int[shadowBitmap.getHeight() * shadowBitmap.getWidth()], 0, 0, 0, 0, shadowBitmap.getWidth(), shadowBitmap.getHeight()); 257 return scaledBitmap; 258 } 259 260 @Implementation createBitmap(Bitmap src, int x, int y, int width, int height)261 protected static Bitmap createBitmap(Bitmap src, int x, int y, int width, int height) { 262 if (x == 0 && y == 0 && width == src.getWidth() && height == src.getHeight()) { 263 return src; // Return the original. 264 } 265 266 Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 267 ShadowBitmap shadowBitmap = Shadow.extract(newBitmap); 268 269 ShadowBitmap shadowSrcBitmap = Shadow.extract(src); 270 shadowBitmap.appendDescription(shadowSrcBitmap.getDescription()); 271 shadowBitmap.appendDescription(" at (" + x + "," + y); 272 shadowBitmap.appendDescription(" with width " + width + " and height " + height); 273 274 shadowBitmap.createdFromBitmap = src; 275 shadowBitmap.createdFromX = x; 276 shadowBitmap.createdFromY = y; 277 shadowBitmap.createdFromWidth = width; 278 shadowBitmap.createdFromHeight = height; 279 shadowBitmap.width = width; 280 shadowBitmap.height = height; 281 return newBitmap; 282 } 283 284 @Implementation setPixels( int[] pixels, int offset, int stride, int x, int y, int width, int height)285 protected void setPixels( 286 int[] pixels, int offset, int stride, int x, int y, int width, int height) { 287 this.colors = pixels; 288 } 289 290 @Implementation createBitmap( Bitmap src, int x, int y, int width, int height, Matrix matrix, boolean filter)291 protected static Bitmap createBitmap( 292 Bitmap src, int x, int y, int width, int height, Matrix matrix, boolean filter) { 293 if (x == 0 && y == 0 && width == src.getWidth() && height == src.getHeight() && (matrix == null || matrix.isIdentity())) { 294 return src; // Return the original. 295 } 296 297 if (x + width > src.getWidth()) { 298 throw new IllegalArgumentException("x + width must be <= bitmap.width()"); 299 } 300 if (y + height > src.getHeight()) { 301 throw new IllegalArgumentException("y + height must be <= bitmap.height()"); 302 } 303 304 Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 305 ShadowBitmap shadowNewBitmap = Shadow.extract(newBitmap); 306 307 ShadowBitmap shadowSrcBitmap = Shadow.extract(src); 308 shadowNewBitmap.appendDescription(shadowSrcBitmap.getDescription()); 309 shadowNewBitmap.appendDescription(" at (" + x + "," + y + ")"); 310 shadowNewBitmap.appendDescription(" with width " + width + " and height " + height); 311 312 shadowNewBitmap.createdFromBitmap = src; 313 shadowNewBitmap.createdFromX = x; 314 shadowNewBitmap.createdFromY = y; 315 shadowNewBitmap.createdFromWidth = width; 316 shadowNewBitmap.createdFromHeight = height; 317 shadowNewBitmap.createdFromMatrix = matrix; 318 shadowNewBitmap.createdFromFilter = filter; 319 320 if (matrix != null) { 321 ShadowMatrix shadowMatrix = Shadow.extract(matrix); 322 shadowNewBitmap.appendDescription(" using matrix " + shadowMatrix.getDescription()); 323 324 // Adjust width and height by using the matrix. 325 RectF mappedRect = new RectF(); 326 matrix.mapRect(mappedRect, new RectF(0, 0, width, height)); 327 width = Math.round(mappedRect.width()); 328 height = Math.round(mappedRect.height()); 329 } 330 if (filter) { 331 shadowNewBitmap.appendDescription(" with filter"); 332 } 333 334 // updated if matrix is non-null 335 shadowNewBitmap.width = width; 336 shadowNewBitmap.height = height; 337 338 return newBitmap; 339 } 340 341 @Implementation createBitmap(int[] colors, int width, int height, Bitmap.Config config)342 protected static Bitmap createBitmap(int[] colors, int width, int height, Bitmap.Config config) { 343 if (colors.length != width * height) { 344 throw new IllegalArgumentException("array length (" + colors.length + ") did not match width * height (" + (width * height) + ")"); 345 } 346 347 Bitmap newBitmap = Bitmap.createBitmap(width, height, config); 348 ShadowBitmap shadowBitmap = Shadow.extract(newBitmap); 349 350 shadowBitmap.setMutable(false); 351 shadowBitmap.createdFromColors = colors; 352 shadowBitmap.colors = new int[colors.length]; 353 System.arraycopy(colors, 0, shadowBitmap.colors, 0, colors.length); 354 return newBitmap; 355 } 356 357 @Implementation getPixel(int x, int y)358 protected int getPixel(int x, int y) { 359 internalCheckPixelAccess(x, y); 360 if (colors != null) { 361 // Note that getPixel() returns a non-premultiplied ARGB value; if 362 // config is RGB_565, our return value will likely be more precise than 363 // on a physical device, since it needs to map each color component from 364 // 5 or 6 bits to 8 bits. 365 return colors[y * getWidth() + x]; 366 } else { 367 return 0; 368 } 369 } 370 371 @Implementation setPixel(int x, int y, int color)372 protected void setPixel(int x, int y, int color) { 373 if (isRecycled()) { 374 throw new IllegalStateException("Can't call setPixel() on a recycled bitmap"); 375 } else if (!isMutable()) { 376 throw new IllegalStateException("Bitmap is immutable"); 377 } 378 internalCheckPixelAccess(x, y); 379 if (colors == null) { 380 colors = new int[getWidth() * getHeight()]; 381 } 382 colors[y * getWidth() + x] = color; 383 } 384 385 /** 386 * Note that this method will return a RuntimeException unless: - {@code pixels} has the same 387 * length as the number of pixels of the bitmap. - {@code x = 0} - {@code y = 0} - {@code width} 388 * and {@code height} height match the current bitmap's dimensions. 389 */ 390 @Implementation getPixels( int[] pixels, int offset, int stride, int x, int y, int width, int height)391 protected void getPixels( 392 int[] pixels, int offset, int stride, int x, int y, int width, int height) { 393 if (x != 0 || 394 y != 0 || 395 width != getWidth() || 396 height != getHeight() || 397 pixels.length != colors.length) { 398 for (int y0 = 0; y0 < height; y0++) { 399 for (int x0 = 0; x0 < width; x0++) { 400 pixels[offset + y0 * stride + x0] = colors[(y0 + y) * getWidth() + x0 + x]; 401 } 402 } 403 } else { 404 System.arraycopy(colors, 0, pixels, 0, colors.length); 405 } 406 } 407 408 @Implementation getRowBytes()409 protected int getRowBytes() { 410 return getBytesPerPixel(config) * getWidth(); 411 } 412 413 @Implementation getByteCount()414 protected int getByteCount() { 415 return getRowBytes() * getHeight(); 416 } 417 418 @Implementation recycle()419 protected void recycle() { 420 recycled = true; 421 } 422 423 @Implementation isRecycled()424 protected final boolean isRecycled() { 425 return recycled; 426 } 427 428 @Implementation copy(Bitmap.Config config, boolean isMutable)429 protected Bitmap copy(Bitmap.Config config, boolean isMutable) { 430 Bitmap newBitmap = ReflectionHelpers.callConstructor(Bitmap.class); 431 ShadowBitmap shadowBitmap = Shadow.extract(newBitmap); 432 shadowBitmap.createdFromBitmap = realBitmap; 433 shadowBitmap.config = config; 434 shadowBitmap.mutable = isMutable; 435 shadowBitmap.height = getHeight(); 436 shadowBitmap.width = getWidth(); 437 if (colors != null) { 438 shadowBitmap.colors = new int[colors.length]; 439 System.arraycopy(shadowBitmap.colors, 0, colors, 0, colors.length); 440 } 441 return newBitmap; 442 } 443 444 @Implementation(minSdk = KITKAT) getAllocationByteCount()445 protected final int getAllocationByteCount() { 446 return getRowBytes() * getHeight(); 447 } 448 449 @Implementation getConfig()450 protected final Bitmap.Config getConfig() { 451 return config; 452 } 453 454 @Implementation(minSdk = KITKAT) setConfig(Bitmap.Config config)455 protected void setConfig(Bitmap.Config config) { 456 this.config = config; 457 } 458 459 @Implementation isMutable()460 protected final boolean isMutable() { 461 return mutable; 462 } 463 setMutable(boolean mutable)464 public void setMutable(boolean mutable) { 465 this.mutable = mutable; 466 } 467 appendDescription(String s)468 public void appendDescription(String s) { 469 description += s; 470 } 471 setDescription(String s)472 public void setDescription(String s) { 473 description = s; 474 } 475 getDescription()476 public String getDescription() { 477 return description; 478 } 479 480 @Implementation hasAlpha()481 protected final boolean hasAlpha() { 482 return hasAlpha; 483 } 484 485 @Implementation setHasAlpha(boolean hasAlpha)486 protected void setHasAlpha(boolean hasAlpha) { 487 this.hasAlpha = hasAlpha; 488 } 489 490 @Implementation extractAlpha()491 protected Bitmap extractAlpha() { 492 int[] alphaPixels = new int[colors.length]; 493 for (int i = 0; i < alphaPixels.length; i++) { 494 alphaPixels[i] = Color.alpha(colors[i]); 495 } 496 497 return createBitmap(alphaPixels, getWidth(), getHeight(), Bitmap.Config.ALPHA_8); 498 } 499 500 @Implementation(minSdk = JELLY_BEAN_MR1) hasMipMap()501 protected final boolean hasMipMap() { 502 return hasMipMap; 503 } 504 505 @Implementation(minSdk = JELLY_BEAN_MR1) setHasMipMap(boolean hasMipMap)506 protected final void setHasMipMap(boolean hasMipMap) { 507 this.hasMipMap = hasMipMap; 508 } 509 510 @Implementation(minSdk = KITKAT) setWidth(int width)511 protected void setWidth(int width) { 512 this.width = width; 513 } 514 515 @Implementation getWidth()516 protected int getWidth() { 517 return width; 518 } 519 520 @Implementation(minSdk = KITKAT) setHeight(int height)521 protected void setHeight(int height) { 522 this.height = height; 523 } 524 525 @Implementation getHeight()526 protected int getHeight() { 527 return height; 528 } 529 530 @Implementation setDensity(int density)531 protected void setDensity(int density) { 532 this.density = density; 533 } 534 535 @Implementation getDensity()536 protected int getDensity() { 537 return density; 538 } 539 540 @Implementation getGenerationId()541 protected int getGenerationId() { 542 return 0; 543 } 544 545 @Implementation(minSdk = M) createAshmemBitmap()546 protected Bitmap createAshmemBitmap() { 547 return realBitmap; 548 } 549 550 @Implementation eraseColor(int color)551 protected void eraseColor(int color) { 552 if (colors != null) { 553 Arrays.fill(colors, color); 554 } 555 } 556 557 @Implementation writeToParcel(Parcel p, int flags)558 protected void writeToParcel(Parcel p, int flags) { 559 p.writeInt(width); 560 p.writeInt(height); 561 p.writeSerializable(config); 562 p.writeIntArray(colors); 563 } 564 565 @Implementation nativeCreateFromParcel(Parcel p)566 protected static Bitmap nativeCreateFromParcel(Parcel p) { 567 int parceledWidth = p.readInt(); 568 int parceledHeight = p.readInt(); 569 Bitmap.Config parceledConfig = (Bitmap.Config) p.readSerializable(); 570 571 int[] parceledColors = new int[parceledHeight * parceledWidth]; 572 p.readIntArray(parceledColors); 573 574 return createBitmap(parceledColors, parceledWidth, parceledHeight, parceledConfig); 575 } 576 577 @Implementation copyPixelsFromBuffer(Buffer dst)578 protected void copyPixelsFromBuffer(Buffer dst) { 579 if (isRecycled()) { 580 throw new IllegalStateException("Can't call copyPixelsFromBuffer() on a recycled bitmap"); 581 } 582 583 // See the related comment in #copyPixelsToBuffer(Buffer). 584 if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) { 585 throw new RuntimeException("Not implemented: only Bitmaps with " + INTERNAL_BYTES_PER_PIXEL 586 + " bytes per pixel are supported"); 587 } 588 if (!(dst instanceof ByteBuffer)) { 589 throw new RuntimeException("Not implemented: unsupported Buffer subclass"); 590 } 591 592 ByteBuffer byteBuffer = (ByteBuffer) dst; 593 if (byteBuffer.remaining() < colors.length * INTERNAL_BYTES_PER_PIXEL) { 594 throw new RuntimeException("Buffer not large enough for pixels"); 595 } 596 597 for (int i = 0; i < colors.length; i++) { 598 colors[i] = byteBuffer.getInt(); 599 } 600 } 601 602 @Implementation copyPixelsToBuffer(Buffer dst)603 protected void copyPixelsToBuffer(Buffer dst) { 604 // Ensure that the Bitmap uses 4 bytes per pixel, since we always use 4 bytes per pixels 605 // internally. Clients of this API probably expect that the buffer size must be >= 606 // getByteCount(), but if we don't enforce this restriction then for RGB_4444 and other 607 // configs that value would be smaller then the buffer size we actually need. 608 if (getBytesPerPixel(config) != INTERNAL_BYTES_PER_PIXEL) { 609 throw new RuntimeException("Not implemented: only Bitmaps with " + INTERNAL_BYTES_PER_PIXEL 610 + " bytes per pixel are supported"); 611 } 612 613 if (!(dst instanceof ByteBuffer)) { 614 throw new RuntimeException("Not implemented: unsupported Buffer subclass"); 615 } 616 617 ByteBuffer byteBuffer = (ByteBuffer) dst; 618 for (int color : colors) { 619 byteBuffer.putInt(color); 620 } 621 } 622 623 @Implementation(minSdk = KITKAT) reconfigure(int width, int height, Bitmap.Config config)624 protected void reconfigure(int width, int height, Bitmap.Config config) { 625 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && this.config == Bitmap.Config.HARDWARE) { 626 throw new IllegalStateException("native-backed bitmaps may not be reconfigured"); 627 } 628 629 // This should throw if the resulting allocation size is greater than the initial allocation 630 // size of our Bitmap, but we don't keep track of that information reliably, so we're forced to 631 // assume that our original dimensions and config are large enough to fit the new dimensions and 632 // config 633 this.width = width; 634 this.height = height; 635 this.config = config; 636 } 637 638 @Implementation(minSdk = KITKAT) setPremultiplied(boolean isPremultiplied)639 protected void setPremultiplied(boolean isPremultiplied) { 640 this.isPremultiplied = isPremultiplied; 641 } 642 643 @Implementation(minSdk = KITKAT) isPremultiplied()644 protected boolean isPremultiplied() { 645 return isPremultiplied; 646 } 647 648 @Implementation sameAs(Bitmap other)649 protected boolean sameAs(Bitmap other) { 650 if (other == null) { 651 return false; 652 } 653 ShadowBitmap shadowOtherBitmap = Shadow.extract(other); 654 if (this.width != shadowOtherBitmap.width || this.height != shadowOtherBitmap.height) { 655 return false; 656 } 657 if (this.config != null 658 && shadowOtherBitmap.config != null 659 && this.config != shadowOtherBitmap.config) { 660 return false; 661 } 662 if (!Arrays.equals(colors, shadowOtherBitmap.colors)) { 663 return false; 664 } 665 return true; 666 } 667 getRealBitmap()668 public Bitmap getRealBitmap() { 669 return realBitmap; 670 } 671 getBytesPerPixel(Bitmap.Config config)672 public static int getBytesPerPixel(Bitmap.Config config) { 673 if (config == null) { 674 throw new NullPointerException("Bitmap config was null."); 675 } 676 switch (config) { 677 case RGBA_F16: 678 return 8; 679 case ARGB_8888: 680 return 4; 681 case RGB_565: 682 case ARGB_4444: 683 return 2; 684 case ALPHA_8: 685 return 1; 686 default: 687 throw new IllegalArgumentException("Unknown bitmap config: " + config); 688 } 689 } 690 setCreatedFromResId(int resId, String description)691 public void setCreatedFromResId(int resId, String description) { 692 this.createdFromResId = resId; 693 appendDescription(" for resource:" + description); 694 } 695 internalCheckPixelAccess(int x, int y)696 private void internalCheckPixelAccess(int x, int y) { 697 if (x < 0) { 698 throw new IllegalArgumentException("x must be >= 0"); 699 } 700 if (y < 0) { 701 throw new IllegalArgumentException("y must be >= 0"); 702 } 703 if (x >= getWidth()) { 704 throw new IllegalArgumentException("x must be < bitmap.width()"); 705 } 706 if (y >= getHeight()) { 707 throw new IllegalArgumentException("y must be < bitmap.height()"); 708 } 709 } 710 } 711