• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.robolectric.integrationtests.nativegraphics;
17 
18 import static android.os.Build.VERSION_CODES.O;
19 import static android.os.Build.VERSION_CODES.Q;
20 import static com.google.common.truth.Truth.assertThat;
21 import static org.junit.Assert.assertEquals;
22 import static org.junit.Assert.assertFalse;
23 import static org.junit.Assert.assertNotNull;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertSame;
26 import static org.junit.Assert.assertThrows;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assert.fail;
29 
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.Bitmap.CompressFormat;
33 import android.graphics.Bitmap.Config;
34 import android.graphics.BitmapFactory;
35 import android.graphics.BitmapFactory.Options;
36 import android.graphics.Canvas;
37 import android.graphics.Color;
38 import android.graphics.ColorSpace;
39 import android.graphics.ColorSpace.Named;
40 import android.graphics.Paint;
41 import android.hardware.HardwareBuffer;
42 import android.os.Parcel;
43 import android.os.StrictMode;
44 import android.util.DisplayMetrics;
45 import java.io.ByteArrayInputStream;
46 import java.io.ByteArrayOutputStream;
47 import java.nio.ByteBuffer;
48 import java.nio.CharBuffer;
49 import java.util.ArrayList;
50 import java.util.List;
51 import java.util.Objects;
52 import org.junit.Before;
53 import org.junit.Ignore;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.robolectric.RobolectricTestRunner;
57 import org.robolectric.RuntimeEnvironment;
58 import org.robolectric.shadow.api.Shadow;
59 import org.robolectric.shadows.ShadowBitmap;
60 import org.robolectric.shadows.ShadowNativeBitmap;
61 
62 @org.robolectric.annotation.Config(minSdk = O)
63 @RunWith(RobolectricTestRunner.class)
64 public class ShadowNativeBitmapTest {
65   // small alpha values cause color values to be pre-multiplied down, losing accuracy
66   private static final int PREMUL_COLOR = Color.argb(2, 255, 254, 253);
67   private static final int PREMUL_ROUNDED_COLOR = Color.argb(2, 255, 255, 255);
68   private static final int PREMUL_STORED_COLOR = Color.argb(2, 2, 2, 2);
69 
70   private static final BitmapFactory.Options HARDWARE_OPTIONS = createHardwareBitmapOptions();
71 
72   private Resources res;
73   private Bitmap bitmap;
74   private BitmapFactory.Options options;
75 
getRgbColorSpaces()76   public static List<ColorSpace> getRgbColorSpaces() {
77     List<ColorSpace> rgbColorSpaces = new ArrayList<ColorSpace>();
78     for (ColorSpace.Named e : ColorSpace.Named.values()) {
79       ColorSpace cs = ColorSpace.get(e);
80       if (cs.getModel() != ColorSpace.Model.RGB) {
81         continue;
82       }
83       if (((ColorSpace.Rgb) cs).getTransferParameters() == null) {
84         continue;
85       }
86       rgbColorSpaces.add(cs);
87     }
88     return rgbColorSpaces;
89   }
90 
91   @Before
setup()92   public void setup() {
93     res = RuntimeEnvironment.getApplication().getResources();
94     options = new BitmapFactory.Options();
95     options.inScaled = false;
96     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
97   }
98 
99   @Test
testCompressRecycled()100   public void testCompressRecycled() {
101     bitmap.recycle();
102     assertThrows(IllegalStateException.class, () -> bitmap.compress(CompressFormat.JPEG, 0, null));
103   }
104 
105   @Test
testCompressNullStream()106   public void testCompressNullStream() {
107     assertThrows(NullPointerException.class, () -> bitmap.compress(CompressFormat.JPEG, 0, null));
108   }
109 
110   @Test
testCompressQualityTooLow()111   public void testCompressQualityTooLow() {
112     assertThrows(
113         IllegalArgumentException.class,
114         () -> bitmap.compress(CompressFormat.JPEG, -1, new ByteArrayOutputStream()));
115   }
116 
117   @Test
testCompressQualityTooHigh()118   public void testCompressQualityTooHigh() {
119     assertThrows(
120         IllegalArgumentException.class,
121         () -> bitmap.compress(CompressFormat.JPEG, 101, new ByteArrayOutputStream()));
122   }
123 
124   @Test
testCopyRecycled()125   public void testCopyRecycled() {
126     bitmap.recycle();
127     assertThrows(IllegalStateException.class, () -> bitmap.copy(Config.RGB_565, false));
128   }
129 
130   @Test
testCopyConfigs()131   public void testCopyConfigs() {
132     Config[] supportedConfigs =
133         new Config[] {
134           Config.ALPHA_8, Config.RGB_565, Config.ARGB_8888, Config.RGBA_F16,
135         };
136     for (Config src : supportedConfigs) {
137       for (Config dst : supportedConfigs) {
138         Bitmap srcBitmap = Bitmap.createBitmap(1, 1, src);
139         srcBitmap.eraseColor(Color.WHITE);
140         Bitmap dstBitmap = srcBitmap.copy(dst, false);
141         assertNotNull("Should support copying from " + src + " to " + dst, dstBitmap);
142         if (Config.ALPHA_8 == dst || Config.ALPHA_8 == src) {
143           // Color will be opaque but color information will be lost.
144           assertEquals(
145               "Color should be black when copying from " + src + " to " + dst,
146               Color.BLACK,
147               dstBitmap.getPixel(0, 0));
148         } else {
149           assertEquals(
150               "Color should be preserved when copying from " + src + " to " + dst,
151               Color.WHITE,
152               dstBitmap.getPixel(0, 0));
153         }
154       }
155     }
156   }
157 
158   @Test
testCopyMutableHwBitmap()159   public void testCopyMutableHwBitmap() {
160     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
161     assertThrows(IllegalArgumentException.class, () -> bitmap.copy(Config.HARDWARE, true));
162   }
163 
164   @Test
testCopyPixelsToBufferUnsupportedBufferClass()165   public void testCopyPixelsToBufferUnsupportedBufferClass() {
166     final int pixSize = bitmap.getRowBytes() * bitmap.getHeight();
167 
168     assertThrows(
169         RuntimeException.class, () -> bitmap.copyPixelsToBuffer(CharBuffer.allocate(pixSize)));
170   }
171 
172   @Test
testCopyPixelsToBufferBufferTooSmall()173   public void testCopyPixelsToBufferBufferTooSmall() {
174     final int pixSize = bitmap.getRowBytes() * bitmap.getHeight();
175     final int tooSmall = pixSize / 2;
176 
177     assertThrows(
178         RuntimeException.class, () -> bitmap.copyPixelsToBuffer(ByteBuffer.allocate(tooSmall)));
179   }
180 
181   @Test
testCreateBitmap1()182   public void testCreateBitmap1() {
183     int[] colors = createColors(100);
184     Bitmap bitmap = Bitmap.createBitmap(colors, 10, 10, Config.RGB_565);
185     assertFalse(bitmap.isMutable());
186     Bitmap ret = Bitmap.createBitmap(bitmap);
187     assertNotNull(ret);
188     assertFalse(ret.isMutable());
189     assertEquals(10, ret.getWidth());
190     assertEquals(10, ret.getHeight());
191     assertEquals(Config.RGB_565, ret.getConfig());
192   }
193 
194   @Test
testCreateBitmapNegativeX()195   public void testCreateBitmapNegativeX() {
196     assertThrows(
197         IllegalArgumentException.class, () -> Bitmap.createBitmap(bitmap, -100, 50, 50, 200));
198   }
199 
200   @Test
testCreateBitmapNegativeXY()201   public void testCreateBitmapNegativeXY() {
202     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
203 
204     // abnormal case: x and/or y less than 0
205     assertThrows(
206         IllegalArgumentException.class,
207         () -> Bitmap.createBitmap(bitmap, -1, -1, 10, 10, null, false));
208   }
209 
210   @Test
testCreateBitmapNegativeWidthHeight()211   public void testCreateBitmapNegativeWidthHeight() {
212     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
213 
214     // abnormal case: width and/or height less than 0
215     assertThrows(
216         IllegalArgumentException.class,
217         () -> Bitmap.createBitmap(bitmap, 1, 1, -10, -10, null, false));
218   }
219 
220   @Test
testCreateBitmapXRegionTooWide()221   public void testCreateBitmapXRegionTooWide() {
222     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
223 
224     // abnormal case: (x + width) bigger than source bitmap's width
225     assertThrows(
226         IllegalArgumentException.class,
227         () -> Bitmap.createBitmap(bitmap, 10, 10, 95, 50, null, false));
228   }
229 
230   @Test
testCreateBitmapYRegionTooTall()231   public void testCreateBitmapYRegionTooTall() {
232     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
233 
234     // abnormal case: (y + height) bigger than source bitmap's height
235     assertThrows(
236         IllegalArgumentException.class,
237         () -> Bitmap.createBitmap(bitmap, 10, 10, 50, 95, null, false));
238   }
239 
240   @Test
testCreateMutableBitmapWithHardwareConfig()241   public void testCreateMutableBitmapWithHardwareConfig() {
242     assertThrows(
243         IllegalArgumentException.class, () -> Bitmap.createBitmap(100, 100, Config.HARDWARE));
244   }
245 
246   @Test
testCreateBitmap4()247   public void testCreateBitmap4() {
248     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
249     assertNotNull(ret);
250     assertTrue(ret.isMutable());
251     assertEquals(100, ret.getWidth());
252     assertEquals(200, ret.getHeight());
253     assertEquals(Config.RGB_565, ret.getConfig());
254   }
255 
256   @Test
testCreateBitmapFromColorsNegativeWidthHeight()257   public void testCreateBitmapFromColorsNegativeWidthHeight() {
258     int[] colors = createColors(100);
259 
260     // abnormal case: width and/or height less than 0
261     assertThrows(
262         IllegalArgumentException.class,
263         () -> Bitmap.createBitmap(colors, 0, 100, -1, 100, Config.RGB_565));
264   }
265 
266   @Test
testCreateBitmapFromColorsIllegalStride()267   public void testCreateBitmapFromColorsIllegalStride() {
268     int[] colors = createColors(100);
269 
270     // abnormal case: stride less than width and bigger than -width
271     assertThrows(
272         IllegalArgumentException.class,
273         () -> Bitmap.createBitmap(colors, 10, 10, 100, 100, Config.RGB_565));
274   }
275 
276   @Test
testCreateBitmapFromColorsNegativeOffset()277   public void testCreateBitmapFromColorsNegativeOffset() {
278     int[] colors = createColors(100);
279 
280     // abnormal case: offset less than 0
281     assertThrows(
282         ArrayIndexOutOfBoundsException.class,
283         () -> Bitmap.createBitmap(colors, -10, 100, 100, 100, Config.RGB_565));
284   }
285 
286   @Test
testCreateBitmapFromColorsOffsetTooLarge()287   public void testCreateBitmapFromColorsOffsetTooLarge() {
288     int[] colors = createColors(100);
289 
290     // abnormal case: (offset + width) bigger than colors' length
291     assertThrows(
292         ArrayIndexOutOfBoundsException.class,
293         () -> Bitmap.createBitmap(colors, 10, 100, 100, 100, Config.RGB_565));
294   }
295 
296   @Test
testCreateBitmapFromColorsScalnlineTooLarge()297   public void testCreateBitmapFromColorsScalnlineTooLarge() {
298     int[] colors = createColors(100);
299 
300     // abnormal case: (lastScanline + width) bigger than colors' length
301     assertThrows(
302         ArrayIndexOutOfBoundsException.class,
303         () -> Bitmap.createBitmap(colors, 10, 100, 50, 100, Config.RGB_565));
304   }
305 
306   @Test
testCreateBitmap6()307   public void testCreateBitmap6() {
308     int[] colors = createColors(100);
309 
310     // normal case
311     Bitmap ret = Bitmap.createBitmap(colors, 5, 10, 10, 5, Config.RGB_565);
312     assertNotNull(ret);
313     assertFalse(ret.isMutable());
314     assertEquals(10, ret.getWidth());
315     assertEquals(5, ret.getHeight());
316     assertEquals(Config.RGB_565, ret.getConfig());
317   }
318 
319   @Test
testCreateBitmap_displayMetrics_mutable()320   public void testCreateBitmap_displayMetrics_mutable() {
321     DisplayMetrics metrics = RuntimeEnvironment.getApplication().getResources().getDisplayMetrics();
322 
323     Bitmap bitmap;
324     bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888);
325     assertTrue(bitmap.isMutable());
326     assertEquals(metrics.densityDpi, bitmap.getDensity());
327 
328     bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888);
329     assertTrue(bitmap.isMutable());
330     assertEquals(metrics.densityDpi, bitmap.getDensity());
331 
332     bitmap = Bitmap.createBitmap(metrics, 10, 10, Config.ARGB_8888, true);
333     assertTrue(bitmap.isMutable());
334     assertEquals(metrics.densityDpi, bitmap.getDensity());
335 
336     bitmap =
337         Bitmap.createBitmap(
338             metrics, 10, 10, Config.ARGB_8888, true, ColorSpace.get(ColorSpace.Named.SRGB));
339 
340     assertTrue(bitmap.isMutable());
341     assertEquals(metrics.densityDpi, bitmap.getDensity());
342 
343     int[] colors = createColors(100);
344     bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888);
345     assertNotNull(bitmap);
346     assertFalse(bitmap.isMutable());
347 
348     bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888);
349     assertNotNull(bitmap);
350     assertFalse(bitmap.isMutable());
351   }
352 
353   @Test
testCreateBitmap_noDisplayMetrics_mutable()354   public void testCreateBitmap_noDisplayMetrics_mutable() {
355     Bitmap bitmap;
356     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
357     assertTrue(bitmap.isMutable());
358 
359     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true);
360     assertTrue(bitmap.isMutable());
361 
362     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888, true, ColorSpace.get(Named.SRGB));
363     assertTrue(bitmap.isMutable());
364   }
365 
366   @Test
testCreateBitmap_displayMetrics_immutable()367   public void testCreateBitmap_displayMetrics_immutable() {
368     DisplayMetrics metrics = RuntimeEnvironment.getApplication().getResources().getDisplayMetrics();
369     int[] colors = createColors(100);
370 
371     Bitmap bitmap;
372     bitmap = Bitmap.createBitmap(metrics, colors, 0, 10, 10, 10, Config.ARGB_8888);
373     assertFalse(bitmap.isMutable());
374     assertEquals(metrics.densityDpi, bitmap.getDensity());
375 
376     bitmap = Bitmap.createBitmap(metrics, colors, 10, 10, Config.ARGB_8888);
377     assertFalse(bitmap.isMutable());
378     assertEquals(metrics.densityDpi, bitmap.getDensity());
379   }
380 
381   @Test
testCreateBitmap_noDisplayMetrics_immutable()382   public void testCreateBitmap_noDisplayMetrics_immutable() {
383     int[] colors = createColors(100);
384     Bitmap bitmap;
385     bitmap = Bitmap.createBitmap(colors, 0, 10, 10, 10, Config.ARGB_8888);
386     assertFalse(bitmap.isMutable());
387 
388     bitmap = Bitmap.createBitmap(colors, 10, 10, Config.ARGB_8888);
389     assertFalse(bitmap.isMutable());
390   }
391 
392   @SuppressWarnings("UnusedVariable")
393   @org.robolectric.annotation.Config(minSdk = Q)
394   @Test
testWrapHardwareBufferWithInvalidUsageFails()395   public void testWrapHardwareBufferWithInvalidUsageFails() {
396     assertThrows(
397         IllegalArgumentException.class,
398         () -> {
399           try (HardwareBuffer hwBuffer =
400               HardwareBuffer.create(
401                   512, 512, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_CPU_WRITE_RARELY)) {
402             Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.SRGB));
403           }
404         });
405   }
406 
407   @SuppressWarnings("UnusedVariable")
408   @org.robolectric.annotation.Config(minSdk = Q)
409   @Test
testWrapHardwareBufferWithRgbBufferButNonRgbColorSpaceFails()410   public void testWrapHardwareBufferWithRgbBufferButNonRgbColorSpaceFails() {
411     assertThrows(
412         IllegalArgumentException.class,
413         () -> {
414           try (HardwareBuffer hwBuffer =
415               HardwareBuffer.create(
416                   512, 512, HardwareBuffer.RGBA_8888, 1, HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE)) {
417             Bitmap bitmap = Bitmap.wrapHardwareBuffer(hwBuffer, ColorSpace.get(Named.CIE_LAB));
418           }
419         });
420   }
421 
422   @Test
testGenerationId()423   public void testGenerationId() {
424     Bitmap bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
425     int genId = bitmap.getGenerationId();
426     assertEquals("not expected to change", genId, bitmap.getGenerationId());
427     bitmap.setDensity(bitmap.getDensity() + 4);
428     assertEquals("not expected to change", genId, bitmap.getGenerationId());
429     bitmap.getPixel(0, 0);
430     assertEquals("not expected to change", genId, bitmap.getGenerationId());
431 
432     int beforeGenId = bitmap.getGenerationId();
433     bitmap.eraseColor(Color.WHITE);
434     int afterGenId = bitmap.getGenerationId();
435     assertTrue("expected to increase", afterGenId > beforeGenId);
436 
437     beforeGenId = bitmap.getGenerationId();
438     bitmap.setPixel(4, 4, Color.BLUE);
439     afterGenId = bitmap.getGenerationId();
440     assertTrue("expected to increase again", afterGenId > beforeGenId);
441   }
442 
443   @Test
testDescribeContents()444   public void testDescribeContents() {
445     assertEquals(0, bitmap.describeContents());
446   }
447 
448   @Test
testEraseColorOnRecycled()449   public void testEraseColorOnRecycled() {
450     bitmap.recycle();
451 
452     assertThrows(IllegalStateException.class, () -> bitmap.eraseColor(0));
453   }
454 
455   @org.robolectric.annotation.Config(minSdk = Q)
456   @Test
testEraseColorLongOnRecycled()457   public void testEraseColorLongOnRecycled() {
458     bitmap.recycle();
459 
460     assertThrows(IllegalStateException.class, () -> bitmap.eraseColor(Color.pack(0)));
461   }
462 
463   @Test
testEraseColorOnImmutable()464   public void testEraseColorOnImmutable() {
465     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
466 
467     // abnormal case: bitmap is immutable
468     assertThrows(IllegalStateException.class, () -> bitmap.eraseColor(0));
469   }
470 
471   @org.robolectric.annotation.Config(minSdk = Q)
472   @Test
testEraseColorLongOnImmutable()473   public void testEraseColorLongOnImmutable() {
474     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
475 
476     // abnormal case: bitmap is immutable
477     assertThrows(IllegalStateException.class, () -> bitmap.eraseColor(Color.pack(0)));
478   }
479 
480   @Test
testEraseColor()481   public void testEraseColor() {
482     // normal case
483     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
484     bitmap.eraseColor(0xffff0000);
485     assertEquals(0xffff0000, bitmap.getPixel(10, 10));
486     assertEquals(0xffff0000, bitmap.getPixel(50, 50));
487   }
488 
489   @org.robolectric.annotation.Config(minSdk = Q)
490   @Test
testGetColorOOB()491   public void testGetColorOOB() {
492     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
493     assertThrows(IllegalArgumentException.class, () -> bitmap.getColor(-1, 0));
494   }
495 
496   @org.robolectric.annotation.Config(minSdk = Q)
497   @Test
testGetColorOOB2()498   public void testGetColorOOB2() {
499     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
500     assertThrows(IllegalArgumentException.class, () -> bitmap.getColor(5, -10));
501   }
502 
503   @org.robolectric.annotation.Config(minSdk = Q)
504   @Test
testGetColorOOB3()505   public void testGetColorOOB3() {
506     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
507     assertThrows(IllegalArgumentException.class, () -> bitmap.getColor(100, 10));
508   }
509 
510   @org.robolectric.annotation.Config(minSdk = Q)
511   @Test
testGetColorOOB4()512   public void testGetColorOOB4() {
513     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
514     assertThrows(IllegalArgumentException.class, () -> bitmap.getColor(99, 1000));
515   }
516 
517   @Test
518   @org.robolectric.annotation.Config(minSdk = Q)
testGetColorRecycled()519   public void testGetColorRecycled() {
520     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
521     bitmap.recycle();
522     assertThrows(IllegalStateException.class, () -> bitmap.getColor(0, 0));
523   }
524 
clamp(float f)525   private static float clamp(float f) {
526     return clamp(f, 0.0f, 1.0f);
527   }
528 
clamp(float f, float min, float max)529   private static float clamp(float f, float min, float max) {
530     return Math.min(Math.max(f, min), max);
531   }
532 
533   @org.robolectric.annotation.Config(minSdk = Q)
534   @Test
testGetColor()535   public void testGetColor() {
536     final ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
537     List<ColorSpace> rgbColorSpaces = getRgbColorSpaces();
538     for (Config config : new Config[] {Config.ARGB_8888, Config.RGBA_F16, Config.RGB_565}) {
539       for (ColorSpace bitmapColorSpace : rgbColorSpaces) {
540         bitmap = Bitmap.createBitmap(1, 1, config, /*hasAlpha*/ false, bitmapColorSpace);
541         bitmapColorSpace = bitmap.getColorSpace();
542         for (ColorSpace eraseColorSpace : rgbColorSpaces) {
543           for (long wideGamutLong :
544               new long[] {
545                 Color.pack(1.0f, 0.0f, 0.0f, 1.0f, eraseColorSpace),
546                 Color.pack(0.0f, 1.0f, 0.0f, 1.0f, eraseColorSpace),
547                 Color.pack(0.0f, 0.0f, 1.0f, 1.0f, eraseColorSpace)
548               }) {
549             bitmap.eraseColor(wideGamutLong);
550 
551             Color result = bitmap.getColor(0, 0);
552             if (bitmap.getColorSpace().equals(sRGB)) {
553               assertEquals(bitmap.getPixel(0, 0), result.toArgb());
554             }
555             if (eraseColorSpace.equals(bitmapColorSpace)) {
556               final Color wideGamutColor = Color.valueOf(wideGamutLong);
557               ColorUtils.verifyColor(
558                   "Erasing to Bitmap's ColorSpace " + bitmapColorSpace,
559                   wideGamutColor,
560                   result,
561                   .001f);
562 
563             } else {
564               Color convertedColor = Color.valueOf(Color.convert(wideGamutLong, bitmapColorSpace));
565               if (bitmap.getConfig() != Config.RGBA_F16) {
566                 // It's possible that we have to clip to fit into the Config.
567                 convertedColor =
568                     Color.valueOf(
569                         clamp(convertedColor.red()),
570                         clamp(convertedColor.green()),
571                         clamp(convertedColor.blue()),
572                         convertedColor.alpha(),
573                         bitmapColorSpace);
574               }
575               ColorUtils.verifyColor(
576                   "Bitmap(Config: "
577                       + bitmap.getConfig()
578                       + ", ColorSpace: "
579                       + bitmapColorSpace
580                       + ") erasing to "
581                       + Color.valueOf(wideGamutLong),
582                   convertedColor,
583                   result,
584                   .03f);
585             }
586           }
587         }
588       }
589     }
590   }
591 
592   private static class ARGB {
593     public final float alpha;
594     public final float red;
595     public final float green;
596     public final float blue;
597 
ARGB(float alpha, float red, float green, float blue)598     ARGB(float alpha, float red, float green, float blue) {
599       this.alpha = alpha;
600       this.red = red;
601       this.green = green;
602       this.blue = blue;
603     }
604   }
605 
606   @org.robolectric.annotation.Config(minSdk = Q)
607   @Test
testEraseColorLong()608   public void testEraseColorLong() {
609     List<ColorSpace> rgbColorSpaces = getRgbColorSpaces();
610     for (Config config : new Config[] {Config.ARGB_8888, Config.RGB_565, Config.RGBA_F16}) {
611       bitmap = Bitmap.createBitmap(100, 100, config);
612       // pack SRGB colors into ColorLongs.
613       for (int color :
614           new int[] {
615             Color.RED, Color.BLUE, Color.GREEN, Color.BLACK, Color.WHITE, Color.TRANSPARENT
616           }) {
617         if (config.equals(Config.RGB_565) && Float.compare(Color.alpha(color), 1.0f) != 0) {
618           // 565 doesn't support alpha.
619           continue;
620         }
621         bitmap.eraseColor(Color.pack(color));
622         // The Bitmap is either SRGB or SRGBLinear (F16). getPixel(), which retrieves the
623         // color in SRGB, should match exactly.
624         ColorUtils.verifyColor(
625             "Config " + config + " mismatch at 10, 10 ", color, bitmap.getPixel(10, 10), 0);
626         ColorUtils.verifyColor(
627             "Config " + config + " mismatch at 50, 50 ", color, bitmap.getPixel(50, 50), 0);
628       }
629 
630       // Use arbitrary colors in various ColorSpaces. getPixel() should approximately match
631       // the SRGB version of the color.
632       for (ARGB color :
633           new ARGB[] {
634             new ARGB(1.0f, .5f, .5f, .5f),
635             new ARGB(1.0f, .3f, .6f, .9f),
636             new ARGB(0.5f, .2f, .8f, .7f)
637           }) {
638         if (config.equals(Config.RGB_565) && Float.compare(color.alpha, 1.0f) != 0) {
639           continue;
640         }
641         int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue);
642         for (ColorSpace cs : rgbColorSpaces) {
643           long longColor = Color.convert(srgbColor, cs);
644           bitmap.eraseColor(longColor);
645           // These tolerances were chosen by trial and error. It is expected that
646           // some conversions do not round-trip perfectly.
647           int tolerance = 1;
648           if (config.equals(Config.RGB_565)) {
649             tolerance = 4;
650           } else if (cs.equals(ColorSpace.get(ColorSpace.Named.SMPTE_C))) {
651             tolerance = 3;
652           }
653 
654           ColorUtils.verifyColor(
655               "Config " + config + ", ColorSpace " + cs + ", mismatch at 10, 10 ",
656               srgbColor,
657               bitmap.getPixel(10, 10),
658               tolerance);
659           ColorUtils.verifyColor(
660               "Config " + config + ", ColorSpace " + cs + ", mismatch at 50, 50 ",
661               srgbColor,
662               bitmap.getPixel(50, 50),
663               tolerance);
664         }
665       }
666     }
667   }
668 
669   @org.robolectric.annotation.Config(minSdk = Q)
670   @Test
testEraseColorOnP3()671   public void testEraseColorOnP3() {
672     // Use a ColorLong with a different ColorSpace than the Bitmap. getPixel() should
673     // approximately match the SRGB version of the color.
674     bitmap =
675         Bitmap.createBitmap(
676             100, 100, Config.ARGB_8888, true, ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
677     int srgbColor = Color.argb(.5f, .3f, .6f, .7f);
678     long acesColor = Color.convert(srgbColor, ColorSpace.get(ColorSpace.Named.ACES));
679     bitmap.eraseColor(acesColor);
680     ColorUtils.verifyColor("Mismatch at 15, 15", srgbColor, bitmap.getPixel(15, 15), 1);
681   }
682 
683   @org.robolectric.annotation.Config(minSdk = Q)
684   @Test
testEraseColorXYZ()685   public void testEraseColorXYZ() {
686     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
687     assertThrows(
688         IllegalArgumentException.class,
689         () ->
690             bitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_XYZ))));
691   }
692 
693   @org.robolectric.annotation.Config(minSdk = Q)
694   @Test
testEraseColorLAB()695   public void testEraseColorLAB() {
696     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
697     assertThrows(
698         IllegalArgumentException.class,
699         () ->
700             bitmap.eraseColor(Color.convert(Color.BLUE, ColorSpace.get(ColorSpace.Named.CIE_LAB))));
701   }
702 
703   @org.robolectric.annotation.Config(minSdk = Q)
704   @Test
testEraseColorUnknown()705   public void testEraseColorUnknown() {
706     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
707     assertThrows(IllegalArgumentException.class, () -> bitmap.eraseColor(-1L));
708   }
709 
710   @Test
testExtractAlphaFromRecycled()711   public void testExtractAlphaFromRecycled() {
712     bitmap.recycle();
713 
714     assertThrows(IllegalStateException.class, () -> bitmap.extractAlpha());
715   }
716 
717   @Test
testExtractAlpha()718   public void testExtractAlpha() {
719     // normal case
720     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
721     Bitmap ret = bitmap.extractAlpha();
722     assertNotNull(ret);
723     int source = bitmap.getPixel(10, 20);
724     int result = ret.getPixel(10, 20);
725     assertEquals(Color.alpha(source), Color.alpha(result));
726     assertEquals(0xFF, Color.alpha(result));
727   }
728 
729   @Test
testExtractAlphaWithPaintAndOffsetFromRecycled()730   public void testExtractAlphaWithPaintAndOffsetFromRecycled() {
731     bitmap.recycle();
732 
733     assertThrows(
734         IllegalStateException.class, () -> bitmap.extractAlpha(new Paint(), new int[] {0, 1}));
735   }
736 
737   @Test
testGetAllocationByteCount()738   public void testGetAllocationByteCount() {
739     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8);
740     int alloc = bitmap.getAllocationByteCount();
741     assertEquals(bitmap.getByteCount(), alloc);
742 
743     // reconfigure same size
744     bitmap.reconfigure(50, 100, Bitmap.Config.ARGB_8888);
745     assertEquals(bitmap.getByteCount(), alloc);
746     assertEquals(bitmap.getAllocationByteCount(), alloc);
747 
748     // reconfigure different size
749     bitmap.reconfigure(10, 10, Bitmap.Config.ALPHA_8);
750     assertEquals(100, bitmap.getByteCount());
751     assertEquals(bitmap.getAllocationByteCount(), alloc);
752   }
753 
754   @Test
testGetHeight()755   public void testGetHeight() {
756     assertEquals(31, bitmap.getHeight());
757     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
758     assertEquals(200, bitmap.getHeight());
759   }
760 
761   @Test
testGetNinePatchChunk()762   public void testGetNinePatchChunk() {
763     assertNull(bitmap.getNinePatchChunk());
764   }
765 
766   @Test
testGetPixelFromRecycled()767   public void testGetPixelFromRecycled() {
768     bitmap.recycle();
769 
770     assertThrows(IllegalStateException.class, () -> bitmap.getPixel(10, 16));
771   }
772 
773   @Test
testGetPixelXTooLarge()774   public void testGetPixelXTooLarge() {
775     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
776 
777     // abnormal case: x bigger than the source bitmap's width
778     assertThrows(IllegalArgumentException.class, () -> bitmap.getPixel(200, 16));
779   }
780 
781   @Test
testGetPixelYTooLarge()782   public void testGetPixelYTooLarge() {
783     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
784 
785     // abnormal case: y bigger than the source bitmap's height
786     assertThrows(IllegalArgumentException.class, () -> bitmap.getPixel(10, 300));
787   }
788 
789   @Test
testGetPixel()790   public void testGetPixel() {
791     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
792 
793     // normal case 565
794     bitmap.setPixel(10, 16, 0xFF << 24);
795     assertEquals(0xFF << 24, bitmap.getPixel(10, 16));
796 
797     // normal case A_8
798     bitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8);
799     bitmap.setPixel(5, 5, 0xFFFFFFFF);
800     assertEquals(0xFF000000, bitmap.getPixel(5, 5));
801     bitmap.setPixel(5, 5, 0xA8A8A8A8);
802     assertEquals(0xA8000000, bitmap.getPixel(5, 5));
803     bitmap.setPixel(5, 5, 0x00000000);
804     assertEquals(0x00000000, bitmap.getPixel(5, 5));
805     bitmap.setPixel(5, 5, 0x1F000000);
806     assertEquals(0x1F000000, bitmap.getPixel(5, 5));
807   }
808 
809   @Test
testGetRowBytes()810   public void testGetRowBytes() {
811     Bitmap bm0 = Bitmap.createBitmap(100, 200, Bitmap.Config.ALPHA_8);
812     Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
813     Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
814     Bitmap bm3 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_4444);
815 
816     assertEquals(100, bm0.getRowBytes());
817     assertEquals(400, bm1.getRowBytes());
818     assertEquals(200, bm2.getRowBytes());
819     // Attempting to create a 4444 bitmap actually creates an 8888 bitmap.
820     assertEquals(400, bm3.getRowBytes());
821   }
822 
823   @Test
testGetWidth()824   public void testGetWidth() {
825     assertEquals(31, bitmap.getWidth());
826     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
827     assertEquals(100, bitmap.getWidth());
828   }
829 
830   @Test
testHasAlpha()831   public void testHasAlpha() {
832     assertFalse(bitmap.hasAlpha());
833     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
834     assertTrue(bitmap.hasAlpha());
835   }
836 
837   @Test
testIsMutable()838   public void testIsMutable() {
839     assertFalse(bitmap.isMutable());
840     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
841     assertTrue(bitmap.isMutable());
842   }
843 
844   @Test
testIsRecycled()845   public void testIsRecycled() {
846     assertFalse(bitmap.isRecycled());
847     bitmap.recycle();
848     assertTrue(bitmap.isRecycled());
849   }
850 
851   @Test
testReconfigure()852   public void testReconfigure() {
853     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
854     int alloc = bitmap.getAllocationByteCount();
855 
856     // test shrinking
857     bitmap.reconfigure(50, 100, Bitmap.Config.ALPHA_8);
858     assertEquals(bitmap.getAllocationByteCount(), alloc);
859     assertEquals(bitmap.getByteCount() * 8, alloc);
860   }
861 
862   @Test
testReconfigureExpanding()863   public void testReconfigureExpanding() {
864     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
865     assertThrows(
866         IllegalArgumentException.class,
867         () -> bitmap.reconfigure(101, 201, Bitmap.Config.ARGB_8888));
868   }
869 
870   @Test
testReconfigureMutable()871   public void testReconfigureMutable() {
872     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
873     assertThrows(
874         IllegalStateException.class, () -> bitmap.reconfigure(1, 1, Bitmap.Config.ALPHA_8));
875   }
876 
877   // Used by testAlphaAndPremul.
878   private static final Config[] CONFIGS =
879       new Config[] {Config.ALPHA_8, Config.ARGB_4444, Config.ARGB_8888, Config.RGB_565};
880 
881   // test that reconfigure, setHasAlpha, and setPremultiplied behave as expected with
882   // respect to alpha and premultiplied.
883   @Test
testAlphaAndPremul()884   public void testAlphaAndPremul() {
885     boolean[] falseTrue = new boolean[] {false, true};
886     for (Config fromConfig : CONFIGS) {
887       for (Config toConfig : CONFIGS) {
888         for (boolean hasAlpha : falseTrue) {
889           for (boolean isPremul : falseTrue) {
890             Bitmap bitmap = Bitmap.createBitmap(10, 10, fromConfig);
891 
892             // 4444 is deprecated, and will convert to 8888. No need to
893             // attempt a reconfigure, which will be tested when fromConfig
894             // is 8888.
895             if (fromConfig == Config.ARGB_4444) {
896               assertEquals(Config.ARGB_8888, bitmap.getConfig());
897               break;
898             }
899 
900             bitmap.setHasAlpha(hasAlpha);
901             bitmap.setPremultiplied(isPremul);
902 
903             verifyAlphaAndPremul(bitmap, hasAlpha, isPremul, false);
904 
905             // reconfigure to a smaller size so the function will still succeed when
906             // going to a Config that requires more bits.
907             bitmap.reconfigure(1, 1, toConfig);
908             if (toConfig == Config.ARGB_4444) {
909               assertEquals(Config.ARGB_8888, bitmap.getConfig());
910             } else {
911               assertEquals(toConfig, bitmap.getConfig());
912             }
913 
914             // Check that the alpha and premultiplied state has not changed (unless
915             // we expected it to).
916             verifyAlphaAndPremul(bitmap, hasAlpha, isPremul, fromConfig == Config.RGB_565);
917           }
918         }
919       }
920     }
921   }
922 
923   /**
924    * Assert that bitmap returns the appropriate values for hasAlpha() and isPremultiplied().
925    *
926    * @param bitmap Bitmap to check.
927    * @param expectedAlpha Expected return value from bitmap.hasAlpha(). Note that this is based on
928    *     what was set, but may be different from the actual return value depending on the Config and
929    *     convertedFrom565.
930    * @param expectedPremul Expected return value from bitmap.isPremultiplied(). Similar to
931    *     expectedAlpha, this is based on what was set, but may be different from the actual return
932    *     value depending on the Config.
933    * @param convertedFrom565 Whether bitmap was converted to its current Config by being
934    *     reconfigured from RGB_565. If true, and bitmap is now a Config that supports alpha,
935    *     hasAlpha() is expected to be true even if expectedAlpha is false.
936    */
937   @SuppressWarnings("MissingCasesInEnumSwitch")
verifyAlphaAndPremul( Bitmap bitmap, boolean expectedAlpha, boolean expectedPremul, boolean convertedFrom565)938   private void verifyAlphaAndPremul(
939       Bitmap bitmap, boolean expectedAlpha, boolean expectedPremul, boolean convertedFrom565) {
940     switch (bitmap.getConfig()) {
941       case ARGB_4444:
942         // This shouldn't happen, since we don't allow creating or converting
943         // to 4444.
944         assertFalse(true);
945         break;
946       case RGB_565:
947         assertFalse(bitmap.hasAlpha());
948         assertFalse(bitmap.isPremultiplied());
949         break;
950       case ALPHA_8:
951         // ALPHA_8 behaves mostly the same as 8888, except for premultiplied. Fall through.
952       case ARGB_8888:
953         // Since 565 is necessarily opaque, we revert to hasAlpha when switching to a type
954         // that can have alpha.
955         if (convertedFrom565) {
956           assertTrue(bitmap.hasAlpha());
957         } else {
958           assertEquals(expectedAlpha, bitmap.hasAlpha());
959         }
960 
961         if (bitmap.hasAlpha()) {
962           // ALPHA_8's premultiplied status is undefined.
963           if (bitmap.getConfig() != Config.ALPHA_8) {
964             assertEquals(expectedPremul, bitmap.isPremultiplied());
965           }
966         } else {
967           // Opaque bitmap is never considered premultiplied.
968           assertFalse(bitmap.isPremultiplied());
969         }
970         break;
971     }
972   }
973 
974   @org.robolectric.annotation.Config(minSdk = Q)
975   @Test
testSetColorSpace()976   public void testSetColorSpace() {
977     // Use arbitrary colors and assign to various ColorSpaces.
978     for (ARGB color :
979         new ARGB[] {
980           new ARGB(1.0f, .5f, .5f, .5f),
981           new ARGB(1.0f, .3f, .6f, .9f),
982           new ARGB(0.5f, .2f, .8f, .7f)
983         }) {
984 
985       int srgbColor = Color.argb(color.alpha, color.red, color.green, color.blue);
986       for (ColorSpace cs : getRgbColorSpaces()) {
987         for (Config config :
988             new Config[] {
989               // F16 is tested elsewhere, since it defaults to EXTENDED_SRGB, and
990               // many of these calls to setColorSpace would reduce the range, resulting
991               // in an Exception.
992               Config.ARGB_8888, Config.RGB_565,
993             }) {
994           bitmap = Bitmap.createBitmap(10, 10, config);
995           bitmap.eraseColor(srgbColor);
996           bitmap.setColorSpace(cs);
997           ColorSpace actual = bitmap.getColorSpace();
998           if (Objects.equals(cs, ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB))) {
999             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), actual);
1000           } else if (Objects.equals(cs, ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB))) {
1001             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), actual);
1002           } else {
1003             assertSame(cs, actual);
1004           }
1005 
1006           // This tolerance was chosen by trial and error. It is expected that
1007           // some conversions do not round-trip perfectly.
1008           int tolerance = 2;
1009           Color c = Color.valueOf(color.red, color.green, color.blue, color.alpha, cs);
1010           ColorUtils.verifyColor(
1011               "Mismatch after setting the colorSpace to " + cs.getName(),
1012               c.convert(bitmap.getColorSpace()),
1013               bitmap.getColor(5, 5),
1014               tolerance);
1015         }
1016       }
1017     }
1018   }
1019 
1020   @org.robolectric.annotation.Config(minSdk = Q)
1021   @Test
testSetColorSpaceRecycled()1022   public void testSetColorSpaceRecycled() {
1023     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
1024     bitmap.recycle();
1025     assertThrows(
1026         IllegalStateException.class, () -> bitmap.setColorSpace(ColorSpace.get(Named.DISPLAY_P3)));
1027   }
1028 
1029   @org.robolectric.annotation.Config(minSdk = Q)
1030   @Test
testSetColorSpaceNull()1031   public void testSetColorSpaceNull() {
1032     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
1033     assertThrows(IllegalArgumentException.class, () -> bitmap.setColorSpace(null));
1034   }
1035 
1036   @org.robolectric.annotation.Config(minSdk = Q)
1037   @Test
testSetColorSpaceXYZ()1038   public void testSetColorSpaceXYZ() {
1039     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
1040     assertThrows(
1041         IllegalArgumentException.class, () -> bitmap.setColorSpace(ColorSpace.get(Named.CIE_XYZ)));
1042   }
1043 
1044   @org.robolectric.annotation.Config(minSdk = Q)
1045   @Test
testSetColorSpaceNoTransferParameters()1046   public void testSetColorSpaceNoTransferParameters() {
1047     bitmap = Bitmap.createBitmap(10, 10, Config.ARGB_8888);
1048     ColorSpace cs =
1049         new ColorSpace.Rgb(
1050             "NoTransferParams",
1051             new float[] {0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f},
1052             ColorSpace.ILLUMINANT_D50,
1053             x -> Math.pow(x, 1.0f / 2.2f),
1054             x -> Math.pow(x, 2.2f),
1055             0,
1056             1);
1057     assertThrows(IllegalArgumentException.class, () -> bitmap.setColorSpace(cs));
1058   }
1059 
1060   @org.robolectric.annotation.Config(minSdk = Q)
1061   @Test
testSetColorSpaceAlpha8()1062   public void testSetColorSpaceAlpha8() {
1063     bitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8);
1064     assertNull(bitmap.getColorSpace());
1065     assertThrows(
1066         IllegalArgumentException.class,
1067         () -> bitmap.setColorSpace(ColorSpace.get(ColorSpace.Named.SRGB)));
1068   }
1069 
1070   @org.robolectric.annotation.Config(minSdk = Q)
1071   @Test
testSetColorSpaceReducedRange()1072   public void testSetColorSpaceReducedRange() {
1073     ColorSpace aces = ColorSpace.get(Named.ACES);
1074     bitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, aces);
1075     try {
1076       bitmap.setColorSpace(ColorSpace.get(Named.SRGB));
1077       fail("Expected IllegalArgumentException!");
1078     } catch (IllegalArgumentException e) {
1079       assertSame(aces, bitmap.getColorSpace());
1080     }
1081   }
1082 
1083   @org.robolectric.annotation.Config(minSdk = Q)
1084   @Test
testSetColorSpaceNotReducedRange()1085   public void testSetColorSpaceNotReducedRange() {
1086     ColorSpace extended = ColorSpace.get(Named.EXTENDED_SRGB);
1087     bitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, extended);
1088     bitmap.setColorSpace(ColorSpace.get(Named.SRGB));
1089     assertSame(bitmap.getColorSpace(), extended);
1090   }
1091 
1092   @org.robolectric.annotation.Config(minSdk = Q)
1093   @Test
testSetColorSpaceNotReducedRangeLinear()1094   public void testSetColorSpaceNotReducedRangeLinear() {
1095     ColorSpace linearExtended = ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
1096     bitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, linearExtended);
1097     bitmap.setColorSpace(ColorSpace.get(Named.LINEAR_SRGB));
1098     assertSame(bitmap.getColorSpace(), linearExtended);
1099   }
1100 
1101   @org.robolectric.annotation.Config(minSdk = Q)
1102   @Test
testSetColorSpaceIncreasedRange()1103   public void testSetColorSpaceIncreasedRange() {
1104     bitmap = Bitmap.createBitmap(10, 10, Config.RGBA_F16, true, ColorSpace.get(Named.DISPLAY_P3));
1105     ColorSpace linearExtended = ColorSpace.get(Named.LINEAR_EXTENDED_SRGB);
1106     bitmap.setColorSpace(linearExtended);
1107     assertSame(bitmap.getColorSpace(), linearExtended);
1108   }
1109 
1110   @Test
testSetConfig()1111   public void testSetConfig() {
1112     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
1113     int alloc = bitmap.getAllocationByteCount();
1114 
1115     // test shrinking
1116     bitmap.setConfig(Bitmap.Config.ALPHA_8);
1117     assertEquals(bitmap.getAllocationByteCount(), alloc);
1118     assertEquals(bitmap.getByteCount() * 2, alloc);
1119   }
1120 
1121   @Test
testSetConfigExpanding()1122   public void testSetConfigExpanding() {
1123     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
1124     // test expanding
1125     assertThrows(IllegalArgumentException.class, () -> bitmap.setConfig(Bitmap.Config.ARGB_8888));
1126   }
1127 
1128   @Test
testSetConfigMutable()1129   public void testSetConfigMutable() {
1130     // test mutable
1131     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
1132     assertThrows(IllegalStateException.class, () -> bitmap.setConfig(Bitmap.Config.ALPHA_8));
1133   }
1134 
1135   @Test
testSetHeight()1136   public void testSetHeight() {
1137     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
1138     int alloc = bitmap.getAllocationByteCount();
1139 
1140     // test shrinking
1141     bitmap.setHeight(100);
1142     assertEquals(bitmap.getAllocationByteCount(), alloc);
1143     assertEquals(bitmap.getByteCount() * 2, alloc);
1144   }
1145 
1146   @Test
testSetHeightExpanding()1147   public void testSetHeightExpanding() {
1148     // test expanding
1149     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
1150     assertThrows(IllegalArgumentException.class, () -> bitmap.setHeight(201));
1151   }
1152 
1153   @Test
testSetHeightMutable()1154   public void testSetHeightMutable() {
1155     // test mutable
1156     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
1157     assertThrows(IllegalStateException.class, () -> bitmap.setHeight(1));
1158   }
1159 
1160   @Test
testSetPixelOnRecycled()1161   public void testSetPixelOnRecycled() {
1162     int color = 0xff << 24;
1163 
1164     bitmap.recycle();
1165     assertThrows(IllegalStateException.class, () -> bitmap.setPixel(10, 16, color));
1166   }
1167 
1168   @Test
testSetPixelOnImmutable()1169   public void testSetPixelOnImmutable() {
1170     int color = 0xff << 24;
1171     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
1172 
1173     assertThrows(IllegalStateException.class, () -> bitmap.setPixel(10, 16, color));
1174   }
1175 
1176   @Test
testSetPixelXIsTooLarge()1177   public void testSetPixelXIsTooLarge() {
1178     int color = 0xff << 24;
1179     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
1180 
1181     // abnormal case: x bigger than the source bitmap's width
1182     assertThrows(IllegalArgumentException.class, () -> bitmap.setPixel(200, 16, color));
1183   }
1184 
1185   @Test
testSetPixelYIsTooLarge()1186   public void testSetPixelYIsTooLarge() {
1187     int color = 0xff << 24;
1188     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
1189 
1190     // abnormal case: y bigger than the source bitmap's height
1191     assertThrows(IllegalArgumentException.class, () -> bitmap.setPixel(10, 300, color));
1192   }
1193 
1194   @Test
testSetPixel()1195   public void testSetPixel() {
1196     int color = 0xff << 24;
1197     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
1198 
1199     // normal case
1200     bitmap.setPixel(10, 16, color);
1201     assertEquals(color, bitmap.getPixel(10, 16));
1202   }
1203 
1204   @Test
testSetPixelsOnRecycled()1205   public void testSetPixelsOnRecycled() {
1206     int[] colors = createColors(100);
1207 
1208     bitmap.recycle();
1209     assertThrows(IllegalStateException.class, () -> bitmap.setPixels(colors, 0, 0, 0, 0, 0, 0));
1210   }
1211 
1212   @Test
testSetPixelsOnImmutable()1213   public void testSetPixelsOnImmutable() {
1214     int[] colors = createColors(100);
1215     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
1216 
1217     assertThrows(IllegalStateException.class, () -> bitmap.setPixels(colors, 0, 0, 0, 0, 0, 0));
1218   }
1219 
1220   @Test
testSetPixelsXYNegative()1221   public void testSetPixelsXYNegative() {
1222     int[] colors = createColors(100);
1223     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1224 
1225     // abnormal case: x and/or y less than 0
1226     assertThrows(
1227         IllegalArgumentException.class, () -> bitmap.setPixels(colors, 0, 0, -1, -1, 200, 16));
1228   }
1229 
1230   @Test
testSetPixelsWidthHeightNegative()1231   public void testSetPixelsWidthHeightNegative() {
1232     int[] colors = createColors(100);
1233     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1234 
1235     // abnormal case: width and/or height less than 0
1236     assertThrows(
1237         IllegalArgumentException.class, () -> bitmap.setPixels(colors, 0, 0, 0, 0, -1, -1));
1238   }
1239 
1240   @Test
testSetPixelsXTooHigh()1241   public void testSetPixelsXTooHigh() {
1242     int[] colors = createColors(100);
1243     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1244 
1245     // abnormal case: (x + width) bigger than the source bitmap's width
1246     assertThrows(
1247         IllegalArgumentException.class, () -> bitmap.setPixels(colors, 0, 0, 10, 10, 95, 50));
1248   }
1249 
1250   @Test
testSetPixelsYTooHigh()1251   public void testSetPixelsYTooHigh() {
1252     int[] colors = createColors(100);
1253     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1254 
1255     // abnormal case: (y + height) bigger than the source bitmap's height
1256     assertThrows(
1257         IllegalArgumentException.class, () -> bitmap.setPixels(colors, 0, 0, 10, 10, 50, 95));
1258   }
1259 
1260   @Test
testSetPixelsStrideIllegal()1261   public void testSetPixelsStrideIllegal() {
1262     int[] colors = createColors(100);
1263     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1264 
1265     // abnormal case: stride less than width and bigger than -width
1266     assertThrows(
1267         IllegalArgumentException.class, () -> bitmap.setPixels(colors, 0, 10, 10, 10, 50, 50));
1268   }
1269 
1270   @Test
testSetPixelsOffsetNegative()1271   public void testSetPixelsOffsetNegative() {
1272     int[] colors = createColors(100);
1273     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1274 
1275     // abnormal case: offset less than 0
1276     assertThrows(
1277         ArrayIndexOutOfBoundsException.class,
1278         () -> bitmap.setPixels(colors, -1, 50, 10, 10, 50, 50));
1279   }
1280 
1281   @Test
testSetPixelsOffsetTooBig()1282   public void testSetPixelsOffsetTooBig() {
1283     int[] colors = createColors(100);
1284     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1285 
1286     // abnormal case: (offset + width) bigger than the length of colors
1287     assertThrows(
1288         ArrayIndexOutOfBoundsException.class,
1289         () -> bitmap.setPixels(colors, 60, 50, 10, 10, 50, 50));
1290   }
1291 
1292   @Test
testSetPixelsLastScanlineNegative()1293   public void testSetPixelsLastScanlineNegative() {
1294     int[] colors = createColors(100);
1295     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1296 
1297     // abnormal case: lastScanline less than 0
1298     assertThrows(
1299         ArrayIndexOutOfBoundsException.class,
1300         () -> bitmap.setPixels(colors, 10, -50, 10, 10, 50, 50));
1301   }
1302 
1303   @Test
testSetPixelsLastScanlineTooBig()1304   public void testSetPixelsLastScanlineTooBig() {
1305     int[] colors = createColors(100);
1306     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1307 
1308     // abnormal case: (lastScanline + width) bigger than the length of colors
1309     assertThrows(
1310         ArrayIndexOutOfBoundsException.class,
1311         () -> bitmap.setPixels(colors, 10, 50, 10, 10, 50, 50));
1312   }
1313 
1314   @Test
testSetPixels()1315   public void testSetPixels() {
1316     int[] colors = createColors(100 * 100);
1317     bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
1318     bitmap.setPixels(colors, 0, 100, 0, 0, 100, 100);
1319     int[] ret = new int[100 * 100];
1320     bitmap.getPixels(ret, 0, 100, 0, 0, 100, 100);
1321 
1322     for (int i = 0; i < 10000; i++) {
1323       assertEquals(ret[i], colors[i]);
1324     }
1325   }
1326 
verifyPremultipliedBitmapConfig(Config config, boolean expectedPremul)1327   private void verifyPremultipliedBitmapConfig(Config config, boolean expectedPremul) {
1328     Bitmap bitmap = Bitmap.createBitmap(1, 1, config);
1329     bitmap.setPremultiplied(true);
1330     bitmap.setPixel(0, 0, Color.TRANSPARENT);
1331     assertTrue(bitmap.isPremultiplied() == expectedPremul);
1332 
1333     bitmap.setHasAlpha(false);
1334     assertFalse(bitmap.isPremultiplied());
1335   }
1336 
1337   @Test
testSetPremultipliedSimple()1338   public void testSetPremultipliedSimple() {
1339     verifyPremultipliedBitmapConfig(Bitmap.Config.ALPHA_8, true);
1340     verifyPremultipliedBitmapConfig(Bitmap.Config.RGB_565, false);
1341     verifyPremultipliedBitmapConfig(Bitmap.Config.ARGB_4444, true);
1342     verifyPremultipliedBitmapConfig(Bitmap.Config.ARGB_8888, true);
1343   }
1344 
1345   @Test
testSetPremultipliedData()1346   public void testSetPremultipliedData() {
1347     // with premul, will store 2,2,2,2, so it doesn't get value correct
1348     Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
1349     bitmap.setPixel(0, 0, PREMUL_COLOR);
1350     assertEquals(bitmap.getPixel(0, 0), PREMUL_ROUNDED_COLOR);
1351 
1352     // read premultiplied value directly
1353     bitmap.setPremultiplied(false);
1354     assertEquals(bitmap.getPixel(0, 0), PREMUL_STORED_COLOR);
1355 
1356     // value can now be stored/read correctly
1357     bitmap.setPixel(0, 0, PREMUL_COLOR);
1358     assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR);
1359 
1360     // verify with array methods
1361     int[] testArray = new int[] {PREMUL_COLOR};
1362     bitmap.setPixels(testArray, 0, 1, 0, 0, 1, 1);
1363     bitmap.getPixels(testArray, 0, 1, 0, 0, 1, 1);
1364     assertEquals(bitmap.getPixel(0, 0), PREMUL_COLOR);
1365   }
1366 
1367   @Test
testPremultipliedCanvas()1368   public void testPremultipliedCanvas() {
1369     Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
1370     bitmap.setHasAlpha(true);
1371     bitmap.setPremultiplied(false);
1372     assertFalse(bitmap.isPremultiplied());
1373 
1374     Canvas c = new Canvas();
1375     assertThrows(RuntimeException.class, () -> c.drawBitmap(bitmap, 0, 0, null));
1376   }
1377 
1378   @Test
testSetWidth()1379   public void testSetWidth() {
1380     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
1381     int alloc = bitmap.getAllocationByteCount();
1382 
1383     // test shrinking
1384     bitmap.setWidth(50);
1385     assertEquals(bitmap.getAllocationByteCount(), alloc);
1386     assertEquals(bitmap.getByteCount() * 2, alloc);
1387   }
1388 
1389   @Test
testSetWidthExpanding()1390   public void testSetWidthExpanding() {
1391     // test expanding
1392     bitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
1393 
1394     assertThrows(IllegalArgumentException.class, () -> bitmap.setWidth(101));
1395   }
1396 
1397   @Test
testSetWidthMutable()1398   public void testSetWidthMutable() {
1399     // test mutable
1400     bitmap = BitmapFactory.decodeResource(res, R.drawable.start, options);
1401 
1402     assertThrows(IllegalStateException.class, () -> bitmap.setWidth(1));
1403   }
1404 
1405   @Test
testWriteToParcelRecycled()1406   public void testWriteToParcelRecycled() {
1407     bitmap.recycle();
1408 
1409     assertThrows(IllegalStateException.class, () -> bitmap.writeToParcel(null, 0));
1410   }
1411 
1412   @Test
testGetScaledHeight1()1413   public void testGetScaledHeight1() {
1414     int dummyDensity = 5;
1415     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
1416     int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), dummyDensity);
1417     assertNotNull(ret);
1418     assertEquals(scaledHeight, ret.getScaledHeight(dummyDensity));
1419   }
1420 
1421   @Test
testGetScaledHeight2()1422   public void testGetScaledHeight2() {
1423     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
1424     DisplayMetrics metrics = RuntimeEnvironment.getApplication().getResources().getDisplayMetrics();
1425     int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), metrics.densityDpi);
1426     assertEquals(scaledHeight, ret.getScaledHeight(metrics));
1427   }
1428 
1429   @Test
testGetScaledHeight3()1430   public void testGetScaledHeight3() {
1431     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
1432     Bitmap mMutableBitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
1433     Canvas mCanvas = new Canvas(mMutableBitmap);
1434     // set Density
1435     mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH);
1436     int scaledHeight = scaleFromDensity(ret.getHeight(), ret.getDensity(), mCanvas.getDensity());
1437     assertEquals(scaledHeight, ret.getScaledHeight(mCanvas));
1438   }
1439 
1440   @Test
testGetScaledWidth1()1441   public void testGetScaledWidth1() {
1442     int dummyDensity = 5;
1443     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
1444     int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), dummyDensity);
1445     assertNotNull(ret);
1446     assertEquals(scaledWidth, ret.getScaledWidth(dummyDensity));
1447   }
1448 
1449   @Test
testGetScaledWidth2()1450   public void testGetScaledWidth2() {
1451     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
1452     DisplayMetrics metrics = RuntimeEnvironment.getApplication().getResources().getDisplayMetrics();
1453     int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), metrics.densityDpi);
1454     assertEquals(scaledWidth, ret.getScaledWidth(metrics));
1455   }
1456 
1457   @Test
testGetScaledWidth3()1458   public void testGetScaledWidth3() {
1459     Bitmap ret = Bitmap.createBitmap(100, 200, Config.RGB_565);
1460     Bitmap mMutableBitmap = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
1461     Canvas mCanvas = new Canvas(mMutableBitmap);
1462     // set Density
1463     mCanvas.setDensity(DisplayMetrics.DENSITY_HIGH);
1464     int scaledWidth = scaleFromDensity(ret.getWidth(), ret.getDensity(), mCanvas.getDensity());
1465     assertEquals(scaledWidth, ret.getScaledWidth(mCanvas));
1466   }
1467 
1468   @Test
testSameAs_simpleSuccess()1469   public void testSameAs_simpleSuccess() {
1470     Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1471     Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1472     bitmap1.eraseColor(Color.BLACK);
1473     bitmap2.eraseColor(Color.BLACK);
1474     assertTrue(bitmap1.sameAs(bitmap2));
1475     assertTrue(bitmap2.sameAs(bitmap1));
1476   }
1477 
1478   @Test
testSameAs_simpleFail()1479   public void testSameAs_simpleFail() {
1480     Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1481     Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1482     bitmap1.eraseColor(Color.BLACK);
1483     bitmap2.eraseColor(Color.BLACK);
1484     bitmap2.setPixel(20, 10, Color.WHITE);
1485     assertFalse(bitmap1.sameAs(bitmap2));
1486     assertFalse(bitmap2.sameAs(bitmap1));
1487   }
1488 
1489   @Test
testSameAs_reconfigure()1490   public void testSameAs_reconfigure() {
1491     Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1492     Bitmap bitmap2 = Bitmap.createBitmap(150, 150, Config.ARGB_8888);
1493     bitmap2.reconfigure(100, 100, Config.ARGB_8888); // now same size, so should be same
1494     bitmap1.eraseColor(Color.BLACK);
1495     bitmap2.eraseColor(Color.BLACK);
1496     assertTrue(bitmap1.sameAs(bitmap2));
1497     assertTrue(bitmap2.sameAs(bitmap1));
1498   }
1499 
1500   @Test
testSameAs_config()1501   public void testSameAs_config() {
1502     Bitmap bitmap1 = Bitmap.createBitmap(100, 200, Config.RGB_565);
1503     Bitmap bitmap2 = Bitmap.createBitmap(100, 200, Config.ARGB_8888);
1504 
1505     // both bitmaps can represent black perfectly
1506     bitmap1.eraseColor(Color.BLACK);
1507     bitmap2.eraseColor(Color.BLACK);
1508 
1509     // but not same due to config
1510     assertFalse(bitmap1.sameAs(bitmap2));
1511     assertFalse(bitmap2.sameAs(bitmap1));
1512   }
1513 
1514   @Test
testSameAs_width()1515   public void testSameAs_width() {
1516     Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1517     Bitmap bitmap2 = Bitmap.createBitmap(101, 100, Config.ARGB_8888);
1518     bitmap1.eraseColor(Color.BLACK);
1519     bitmap2.eraseColor(Color.BLACK);
1520     assertFalse(bitmap1.sameAs(bitmap2));
1521     assertFalse(bitmap2.sameAs(bitmap1));
1522   }
1523 
1524   @Test
testSameAs_height()1525   public void testSameAs_height() {
1526     Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1527     Bitmap bitmap2 = Bitmap.createBitmap(102, 100, Config.ARGB_8888);
1528     bitmap1.eraseColor(Color.BLACK);
1529     bitmap2.eraseColor(Color.BLACK);
1530     assertFalse(bitmap1.sameAs(bitmap2));
1531     assertFalse(bitmap2.sameAs(bitmap1));
1532   }
1533 
1534   @Test
testSameAs_opaque()1535   public void testSameAs_opaque() {
1536     Bitmap bitmap1 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1537     Bitmap bitmap2 = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1538     bitmap1.eraseColor(Color.BLACK);
1539     bitmap2.eraseColor(Color.BLACK);
1540     bitmap1.setHasAlpha(true);
1541     bitmap2.setHasAlpha(false);
1542     assertFalse(bitmap1.sameAs(bitmap2));
1543     assertFalse(bitmap2.sameAs(bitmap1));
1544   }
1545 
1546   @Test
testSameAs_hardware()1547   public void testSameAs_hardware() {
1548     Bitmap bitmap1 = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1549     Bitmap bitmap2 = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1550     Bitmap bitmap3 = BitmapFactory.decodeResource(res, R.drawable.robot);
1551     Bitmap bitmap4 = BitmapFactory.decodeResource(res, R.drawable.start, HARDWARE_OPTIONS);
1552     assertTrue(bitmap1.sameAs(bitmap2));
1553     assertTrue(bitmap2.sameAs(bitmap1));
1554     assertFalse(bitmap1.sameAs(bitmap3));
1555     assertFalse(bitmap1.sameAs(bitmap4));
1556   }
1557 
1558   @Test
testHardwareSetWidth()1559   public void testHardwareSetWidth() {
1560     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1561     assertThrows(IllegalStateException.class, () -> bitmap.setWidth(30));
1562   }
1563 
1564   @Test
testHardwareSetHeight()1565   public void testHardwareSetHeight() {
1566     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1567     assertThrows(IllegalStateException.class, () -> bitmap.setHeight(30));
1568   }
1569 
1570   @Test
testHardwareSetConfig()1571   public void testHardwareSetConfig() {
1572     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1573     assertThrows(IllegalStateException.class, () -> bitmap.setConfig(Config.ARGB_8888));
1574   }
1575 
1576   @Test
testHardwareReconfigure()1577   public void testHardwareReconfigure() {
1578     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1579     assertThrows(IllegalStateException.class, () -> bitmap.reconfigure(30, 30, Config.ARGB_8888));
1580   }
1581 
1582   @Test
testHardwareSetPixels()1583   public void testHardwareSetPixels() {
1584     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1585     assertThrows(
1586         IllegalStateException.class, () -> bitmap.setPixels(new int[10], 0, 1, 0, 0, 1, 1));
1587   }
1588 
1589   @Test
testHardwareSetPixel()1590   public void testHardwareSetPixel() {
1591     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1592     assertThrows(IllegalStateException.class, () -> bitmap.setPixel(1, 1, 0));
1593   }
1594 
1595   @Test
testHardwareEraseColor()1596   public void testHardwareEraseColor() {
1597     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1598     assertThrows(IllegalStateException.class, () -> bitmap.eraseColor(0));
1599   }
1600 
1601   @org.robolectric.annotation.Config(minSdk = Q)
1602   @Test
testHardwareEraseColorLong()1603   public void testHardwareEraseColorLong() {
1604     Bitmap bitmap = BitmapFactory.decodeResource(res, R.drawable.robot, HARDWARE_OPTIONS);
1605     assertThrows(IllegalStateException.class, () -> bitmap.eraseColor(Color.pack(0)));
1606   }
1607 
1608   @Test
testUseMetadataAfterRecycle()1609   public void testUseMetadataAfterRecycle() {
1610     Bitmap bitmap = Bitmap.createBitmap(10, 20, Config.RGB_565);
1611     bitmap.recycle();
1612     assertEquals(10, bitmap.getWidth());
1613     assertEquals(20, bitmap.getHeight());
1614     assertEquals(Config.RGB_565, bitmap.getConfig());
1615   }
1616 
1617   @Test
1618   @Ignore("TODO(b/hoisie): re-enable when HW bitmaps are better supported")
testCreateScaledFromHWInStrictMode()1619   public void testCreateScaledFromHWInStrictMode() {
1620     strictModeTest(
1621         () -> {
1622           Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1623           Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
1624           Bitmap.createScaledBitmap(hwBitmap, 200, 200, false);
1625         });
1626   }
1627 
1628   @Test
testCompressInStrictMode()1629   public void testCompressInStrictMode() {
1630     strictModeTest(
1631         () -> {
1632           Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1633           bitmap.compress(CompressFormat.JPEG, 90, new ByteArrayOutputStream());
1634         });
1635   }
1636 
1637   @Test
legacyShadowAPIs_throwException()1638   public void legacyShadowAPIs_throwException() {
1639     Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1640     ShadowBitmap shadowBitmap = Shadow.extract(bitmap);
1641     assertThrows(UnsupportedOperationException.class, () -> shadowBitmap.setDescription("hello"));
1642   }
1643 
1644   @Ignore("TODO(b/hoisie): re-enable when HW bitmaps are better supported")
1645   @Test
testParcelHWInStrictMode()1646   public void testParcelHWInStrictMode() {
1647     strictModeTest(
1648         () -> {
1649           bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1650           Bitmap hwBitmap = bitmap.copy(Config.HARDWARE, false);
1651           hwBitmap.writeToParcel(Parcel.obtain(), 0);
1652         });
1653   }
1654 
1655   @Test
getCreatedFromResId()1656   public void getCreatedFromResId() {
1657     assertThat(((ShadowNativeBitmap) Shadow.extract(bitmap)).getCreatedFromResId())
1658         .isEqualTo(R.drawable.start);
1659   }
1660 
1661   @Test
testWriteToParcel()1662   public void testWriteToParcel() {
1663     Parcel p = Parcel.obtain();
1664     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1665     bitmap.eraseColor(Color.GREEN);
1666     bitmap.writeToParcel(p, 0);
1667     p.setDataPosition(0);
1668     Bitmap fromParcel = Bitmap.CREATOR.createFromParcel(p);
1669     assertTrue(bitmap.sameAs(fromParcel));
1670     assertThat(fromParcel.isMutable()).isTrue();
1671     p.recycle();
1672   }
1673 
1674   @Test
testWriteImmutableToParcel()1675   public void testWriteImmutableToParcel() {
1676     Parcel p = Parcel.obtain();
1677     bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
1678     bitmap.eraseColor(Color.GREEN);
1679     Bitmap immutable = bitmap.copy(Config.ARGB_8888, /*isMutable=*/ false);
1680     assertThat(immutable.isMutable()).isFalse();
1681     immutable.writeToParcel(p, 0);
1682     p.setDataPosition(0);
1683     Bitmap fromParcel = Bitmap.CREATOR.createFromParcel(p);
1684     assertTrue(immutable.sameAs(fromParcel));
1685     assertThat(fromParcel.isMutable()).isFalse();
1686     p.recycle();
1687   }
1688 
1689   @Test
createBitmap_colorSpace_customColorSpace()1690   public void createBitmap_colorSpace_customColorSpace() {
1691     Bitmap bitmap =
1692         Bitmap.createBitmap(
1693             100, 100, Bitmap.Config.ARGB_8888, true, ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
1694 
1695     assertThat(bitmap.getColorSpace()).isEqualTo(ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
1696   }
1697 
1698   @Test
compress_thenDecodeStream_sameAs()1699   public void compress_thenDecodeStream_sameAs() {
1700     Bitmap bitmap = Bitmap.createBitmap(/* width= */ 10, /* height= */ 10, Bitmap.Config.ARGB_8888);
1701     ByteArrayOutputStream outStream = new ByteArrayOutputStream();
1702     bitmap.compress(CompressFormat.PNG, /* quality= */ 100, outStream);
1703     byte[] outBytes = outStream.toByteArray();
1704     ByteArrayInputStream inStream = new ByteArrayInputStream(outBytes);
1705     BitmapFactory.Options options = new Options();
1706     Bitmap bitmap2 = BitmapFactory.decodeStream(inStream, null, options);
1707     assertThat(bitmap.sameAs(bitmap2)).isTrue();
1708   }
1709 
1710   @Test
parcelRoundTripWithoutColorSpace_isSuccessful()1711   public void parcelRoundTripWithoutColorSpace_isSuccessful() {
1712     // Importantly, ALPHA_8 doesn't have an associated color space.
1713     Bitmap orig = Bitmap.createBitmap(/* width= */ 314, /* height= */ 159, Bitmap.Config.ALPHA_8);
1714 
1715     Parcel parcel = Parcel.obtain();
1716     parcel.writeParcelable(orig, /* parcelableFlags= */ 0);
1717     parcel.setDataPosition(0);
1718     Bitmap copy = parcel.readParcelable(Bitmap.class.getClassLoader());
1719 
1720     assertThat(copy.sameAs(orig)).isTrue();
1721   }
1722 
strictModeTest(Runnable runnable)1723   private void strictModeTest(Runnable runnable) {
1724     StrictMode.ThreadPolicy originalPolicy = StrictMode.getThreadPolicy();
1725     StrictMode.setThreadPolicy(
1726         new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls().penaltyDeath().build());
1727     try {
1728       runnable.run();
1729       fail("Shouldn't reach it");
1730     } catch (RuntimeException expected) {
1731       // expect to receive StrictModeViolation
1732     } finally {
1733       StrictMode.setThreadPolicy(originalPolicy);
1734     }
1735   }
1736 
1737   static final int ANDROID_BITMAP_FORMAT_RGBA_8888 = 1;
1738 
scaleFromDensity(int size, int sdensity, int tdensity)1739   private static int scaleFromDensity(int size, int sdensity, int tdensity) {
1740     if (sdensity == Bitmap.DENSITY_NONE || sdensity == tdensity) {
1741       return size;
1742     }
1743 
1744     // Scale by tdensity / sdensity, rounding up.
1745     return ((size * tdensity) + (sdensity >> 1)) / sdensity;
1746   }
1747 
createColors(int size)1748   private static int[] createColors(int size) {
1749     int[] colors = new int[size];
1750 
1751     for (int i = 0; i < size; i++) {
1752       colors[i] = (0xFF << 24) | (i << 16) | (i << 8) | i;
1753     }
1754 
1755     return colors;
1756   }
1757 
createHardwareBitmapOptions()1758   private static BitmapFactory.Options createHardwareBitmapOptions() {
1759     BitmapFactory.Options options = new BitmapFactory.Options();
1760     options.inPreferredConfig = Config.HARDWARE;
1761     return options;
1762   }
1763 }
1764