• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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