Home | History | Annotate | Download | only in shadows
      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    */
     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    */
     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    */
     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    */
     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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    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    */
    182   public boolean getCreatedFromFilter() {
    183     return createdFromFilter;
    184   }
    185 
    186   @Implementation
    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
    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)
    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)
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    409   protected int getRowBytes() {
    410     return getBytesPerPixel(config) * getWidth();
    411   }
    412 
    413   @Implementation
    414   protected int getByteCount() {
    415     return getRowBytes() * getHeight();
    416   }
    417 
    418   @Implementation
    419   protected void recycle() {
    420     recycled = true;
    421   }
    422 
    423   @Implementation
    424   protected final boolean isRecycled() {
    425     return recycled;
    426   }
    427 
    428   @Implementation
    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)
    445   protected final int getAllocationByteCount() {
    446     return getRowBytes() * getHeight();
    447   }
    448 
    449   @Implementation
    450   protected final Bitmap.Config getConfig() {
    451     return config;
    452   }
    453 
    454   @Implementation(minSdk = KITKAT)
    455   protected void setConfig(Bitmap.Config config) {
    456     this.config = config;
    457   }
    458 
    459   @Implementation
    460   protected final boolean isMutable() {
    461     return mutable;
    462   }
    463 
    464   public void setMutable(boolean mutable) {
    465     this.mutable = mutable;
    466   }
    467 
    468   public void appendDescription(String s) {
    469     description += s;
    470   }
    471 
    472   public void setDescription(String s) {
    473     description = s;
    474   }
    475 
    476   public String getDescription() {
    477     return description;
    478   }
    479 
    480   @Implementation
    481   protected final boolean hasAlpha() {
    482     return hasAlpha;
    483   }
    484 
    485   @Implementation
    486   protected void setHasAlpha(boolean hasAlpha) {
    487     this.hasAlpha = hasAlpha;
    488   }
    489 
    490   @Implementation
    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)
    501   protected final boolean hasMipMap() {
    502     return hasMipMap;
    503   }
    504 
    505   @Implementation(minSdk = JELLY_BEAN_MR1)
    506   protected final void setHasMipMap(boolean hasMipMap) {
    507     this.hasMipMap = hasMipMap;
    508   }
    509 
    510   @Implementation(minSdk = KITKAT)
    511   protected void setWidth(int width) {
    512     this.width = width;
    513   }
    514 
    515   @Implementation
    516   protected int getWidth() {
    517     return width;
    518   }
    519 
    520   @Implementation(minSdk = KITKAT)
    521   protected void setHeight(int height) {
    522     this.height = height;
    523   }
    524 
    525   @Implementation
    526   protected int getHeight() {
    527     return height;
    528   }
    529 
    530   @Implementation
    531   protected void setDensity(int density) {
    532     this.density = density;
    533   }
    534 
    535   @Implementation
    536   protected int getDensity() {
    537     return density;
    538   }
    539 
    540   @Implementation
    541   protected int getGenerationId() {
    542     return 0;
    543   }
    544 
    545   @Implementation(minSdk = M)
    546   protected Bitmap createAshmemBitmap() {
    547     return realBitmap;
    548   }
    549 
    550   @Implementation
    551   protected void eraseColor(int color) {
    552     if (colors != null) {
    553       Arrays.fill(colors, color);
    554     }
    555   }
    556 
    557   @Implementation
    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
    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
    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
    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)
    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)
    639   protected void setPremultiplied(boolean isPremultiplied) {
    640     this.isPremultiplied = isPremultiplied;
    641   }
    642 
    643   @Implementation(minSdk = KITKAT)
    644   protected boolean isPremultiplied() {
    645     return isPremultiplied;
    646   }
    647 
    648   @Implementation
    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 
    668   public Bitmap getRealBitmap() {
    669     return realBitmap;
    670   }
    671 
    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 
    691   public void setCreatedFromResId(int resId, String description) {
    692     this.createdFromResId = resId;
    693     appendDescription(" for resource:" + description);
    694   }
    695 
    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