1 /*
2  * Copyright (C) 2017 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 
17 package android.graphics.cts;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertNotNull;
21 import static org.junit.Assert.assertNull;
22 import static org.junit.Assert.assertSame;
23 import static org.junit.Assert.assertTrue;
24 import static org.junit.Assert.fail;
25 
26 import android.content.res.Resources;
27 import android.graphics.Bitmap;
28 import android.graphics.BitmapFactory;
29 import android.graphics.Canvas;
30 import android.graphics.Color;
31 import android.graphics.ColorSpace;
32 import android.graphics.ImageDecoder;
33 import android.graphics.Matrix;
34 import android.os.Parcel;
35 import android.util.Log;
36 
37 import androidx.annotation.ColorInt;
38 import androidx.annotation.NonNull;
39 import androidx.test.InstrumentationRegistry;
40 import androidx.test.filters.RequiresDevice;
41 import androidx.test.filters.SmallTest;
42 
43 import com.android.compatibility.common.util.ColorUtils;
44 
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 
49 import java.io.ByteArrayOutputStream;
50 import java.io.IOException;
51 import java.io.InputStream;
52 import java.nio.ByteBuffer;
53 import java.nio.IntBuffer;
54 import java.util.Arrays;
55 
56 import junitparams.JUnitParamsRunner;
57 import junitparams.Parameters;
58 
59 @SmallTest
60 @RunWith(JUnitParamsRunner.class)
61 public class BitmapColorSpaceTest {
62     private static final String LOG_TAG = "BitmapColorSpaceTest";
63 
64     private Resources mResources;
65 
66     @Before
setup()67     public void setup() {
68         mResources = InstrumentationRegistry.getTargetContext().getResources();
69     }
70 
71     @SuppressWarnings("deprecation")
72     @Test
createWithColorSpace()73     public void createWithColorSpace() {
74         // We don't test HARDWARE configs because they are not compatible with mutable bitmaps
75 
76         Bitmap.Config[] configs = new Bitmap.Config[] {
77                 Bitmap.Config.ARGB_8888,
78                 Bitmap.Config.RGB_565,
79                 Bitmap.Config.ARGB_4444,
80                 Bitmap.Config.RGBA_F16,
81                 Bitmap.Config.RGBA_1010102,
82         };
83         // in most cases, createBitmap respects the ColorSpace
84         for (Bitmap.Config config : configs) {
85             for (ColorSpace.Named e : new ColorSpace.Named[] {
86                     ColorSpace.Named.PRO_PHOTO_RGB,
87                     ColorSpace.Named.ADOBE_RGB,
88                     ColorSpace.Named.DISPLAY_P3,
89                     ColorSpace.Named.DCI_P3,
90                     ColorSpace.Named.BT709,
91                     ColorSpace.Named.BT2020,
92             }) {
93                 ColorSpace requested = ColorSpace.get(e);
94                 Bitmap b = Bitmap.createBitmap(32, 32, config, false, requested);
95                 ColorSpace cs = b.getColorSpace();
96                 assertNotNull(cs);
97                 assertSame(requested, cs);
98             }
99 
100             // SRGB and LINEAR_SRGB are special.
101             ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
102             ColorSpace extendedSrgb = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
103             for (ColorSpace requested : new ColorSpace[] {
104                     sRGB,
105                     extendedSrgb,
106             }) {
107                 Bitmap b = Bitmap.createBitmap(32, 32, config, false, requested);
108                 ColorSpace cs = b.getColorSpace();
109                 assertNotNull(cs);
110                 if (config == Bitmap.Config.RGBA_F16) {
111                     assertSame(extendedSrgb, cs);
112                 } else {
113                     assertSame(sRGB, cs);
114                 }
115             }
116 
117             ColorSpace linearRgb = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB);
118             ColorSpace linearExtendedSrgb = ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB);
119             for (ColorSpace requested : new ColorSpace[] {
120                     linearRgb,
121                     linearExtendedSrgb,
122             }) {
123                 Bitmap b = Bitmap.createBitmap(32, 32, config, false, requested);
124                 ColorSpace cs = b.getColorSpace();
125                 assertNotNull(cs);
126                 if (config == Bitmap.Config.RGBA_F16) {
127                     assertSame(linearExtendedSrgb, cs);
128                 } else {
129                     assertSame(linearRgb, cs);
130                 }
131             }
132         }
133     }
134 
135     @Test
createAlpha8ColorSpace()136     public void createAlpha8ColorSpace() {
137         Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8);
138         assertNull(bitmap.getColorSpace());
139 
140         for (ColorSpace cs : BitmapTest.getRgbColorSpaces()) {
141             bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8, true, cs);
142             assertNull(bitmap.getColorSpace());
143         }
144     }
145 
146     @Test
createDefaultColorSpace()147     public void createDefaultColorSpace() {
148         ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
149         Bitmap.Config[] configs = new Bitmap.Config[] {
150                 Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888
151         };
152         for (Bitmap.Config config : configs) {
153             Bitmap bitmap = Bitmap.createBitmap(32, 32, config, true);
154             assertSame(sRGB, bitmap.getColorSpace());
155         }
156     }
157 
158     @Test(expected = IllegalArgumentException.class)
createWithoutColorSpace()159     public void createWithoutColorSpace() {
160         Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true, null);
161     }
162 
163     @Test(expected = IllegalArgumentException.class)
createWithNonRgbColorSpace()164     public void createWithNonRgbColorSpace() {
165         Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true,
166                 ColorSpace.get(ColorSpace.Named.CIE_LAB));
167     }
168 
169     @Test(expected = IllegalArgumentException.class)
createWithNoTransferParameters()170     public void createWithNoTransferParameters() {
171         Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true,
172                 new ColorSpace.Rgb("NoTransferParams",
173                     new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
174                     ColorSpace.ILLUMINANT_D50,
175                     x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f),
176                     0, 1));
177     }
178 
179     @Test
createFromSourceWithColorSpace()180     public void createFromSourceWithColorSpace() {
181         for (ColorSpace rgb : BitmapTest.getRgbColorSpaces()) {
182             Bitmap.Config[] configs = new Bitmap.Config[] {
183                     Bitmap.Config.ARGB_8888,
184                     Bitmap.Config.RGB_565,
185                     Bitmap.Config.ALPHA_8,
186                     Bitmap.Config.ARGB_4444,
187                     Bitmap.Config.RGBA_F16,
188                     Bitmap.Config.RGBA_1010102,
189             };
190             for (Bitmap.Config config : configs) {
191                 Bitmap orig = Bitmap.createBitmap(32, 32, config, false, rgb);
192                 Bitmap cropped = Bitmap.createBitmap(orig, 0, 0, orig.getWidth() / 2,
193                         orig.getHeight() / 2, null, false);
194                 assertSame(orig.getColorSpace(), cropped.getColorSpace());
195                 if (config == Bitmap.Config.ALPHA_8) {
196                     assertNull(cropped.getColorSpace());
197                 }
198 
199                 Matrix m = new Matrix();
200                 m.setRotate(45, orig.getWidth() / 2, orig.getHeight() / 2);
201                 Bitmap rotated = Bitmap.createBitmap(orig, 0, 0, orig.getWidth(),
202                         orig.getHeight(), m, false);
203                 switch (config) {
204                     case ALPHA_8:
205                         assertSame(Bitmap.Config.ARGB_8888, rotated.getConfig());
206                         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), rotated.getColorSpace());
207                         break;
208                     case RGB_565:
209                         assertSame(Bitmap.Config.ARGB_8888, rotated.getConfig());
210                         // Fallthrough.
211                     default:
212                         assertSame("Mismatch with Config " + config,
213                                 orig.getColorSpace(), rotated.getColorSpace());
214                         break;
215                 }
216             }
217         }
218     }
219 
220     @Test
sRGB()221     public void sRGB() {
222         Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot);
223         ColorSpace cs = b.getColorSpace();
224         assertNotNull(cs);
225         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
226 
227         b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
228         cs = b.getColorSpace();
229         assertNotNull(cs);
230         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
231 
232         b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
233         cs = b.getColorSpace();
234         assertNotNull(cs);
235         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
236     }
237 
238     @Test
p3()239     public void p3() {
240         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
241             Bitmap b = BitmapFactory.decodeStream(in);
242             ColorSpace cs = b.getColorSpace();
243             assertNotNull(cs);
244             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
245 
246             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
247             cs = b.getColorSpace();
248             assertNotNull(cs);
249             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
250 
251             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
252             cs = b.getColorSpace();
253             assertNotNull(cs);
254             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
255         } catch (IOException e) {
256             fail();
257         }
258     }
259 
260     @Test
extendedSRGB()261     public void extendedSRGB() {
262         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
263             Bitmap b = BitmapFactory.decodeStream(in);
264             ColorSpace cs = b.getColorSpace();
265             assertNotNull(cs);
266             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
267 
268             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
269             cs = b.getColorSpace();
270             assertNotNull(cs);
271             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
272 
273             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
274             cs = b.getColorSpace();
275             assertNotNull(cs);
276             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
277         } catch (IOException e) {
278             fail();
279         }
280     }
281 
282     @Test
linearSRGB()283     public void linearSRGB() {
284         String assetInLinearSRGB = "grayscale-linearSrgb.png";
285         try (InputStream in = mResources.getAssets().open(assetInLinearSRGB)) {
286             Bitmap b = BitmapFactory.decodeStream(in);
287             ColorSpace cs = b.getColorSpace();
288             assertNotNull(cs);
289             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), cs);
290         } catch (IOException e) {
291             fail();
292         }
293 
294         try (InputStream in = mResources.getAssets().open(assetInLinearSRGB)) {
295             BitmapFactory.Options options = new BitmapFactory.Options();
296             options.inPreferredConfig = Bitmap.Config.RGBA_F16;
297             Bitmap b = BitmapFactory.decodeStream(in, null, options);
298             ColorSpace cs = b.getColorSpace();
299             assertNotNull(cs);
300             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
301         } catch (IOException e) {
302             fail();
303         }
304     }
305 
306     private static class Asset {
307         public final String name;
308         public final ColorSpace colorSpace;
Asset(String name, ColorSpace.Named e)309         Asset(String name, ColorSpace.Named e) {
310             this.name = name;
311             this.colorSpace = ColorSpace.get(e);
312         }
313     };
314 
315     @Test
reconfigure()316     public void reconfigure() {
317         Asset[] assets = new Asset[] {
318                 new Asset("green-p3.png", ColorSpace.Named.DISPLAY_P3),
319                 new Asset("red-adobergb.png", ColorSpace.Named.ADOBE_RGB),
320         };
321         for (Asset asset : assets) {
322             for (Bitmap.Config config : new Bitmap.Config[] {
323                     Bitmap.Config.ARGB_8888,
324                     Bitmap.Config.RGB_565,
325             }) {
326                 try (InputStream in = mResources.getAssets().open(asset.name)) {
327                     BitmapFactory.Options opts = new BitmapFactory.Options();
328                     opts.inMutable = true;
329                     opts.inPreferredConfig = config;
330 
331                     Bitmap b = BitmapFactory.decodeStream(in, null, opts);
332                     ColorSpace cs = b.getColorSpace();
333                     assertNotNull(cs);
334                     assertSame(asset.colorSpace, cs);
335 
336                     b.reconfigure(b.getWidth() / 4, b.getHeight() / 4, Bitmap.Config.RGBA_F16);
337                     cs = b.getColorSpace();
338                     assertNotNull(cs);
339                     assertSame(asset.colorSpace, cs);
340 
341                     b.reconfigure(b.getWidth(), b.getHeight(), config);
342                     cs = b.getColorSpace();
343                     assertNotNull(cs);
344                     assertSame(asset.colorSpace, cs);
345                 } catch (IOException e) {
346                     fail();
347                 }
348             }
349         }
350     }
351 
352     @Test
reuse()353     public void reuse() {
354         BitmapFactory.Options opts = new BitmapFactory.Options();
355         opts.inMutable = true;
356 
357         Bitmap bitmap1 = null;
358         try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
359             bitmap1 = BitmapFactory.decodeStream(in, null, opts);
360             ColorSpace cs = bitmap1.getColorSpace();
361             assertNotNull(cs);
362             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
363         } catch (IOException e) {
364             fail();
365         }
366 
367         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
368             opts.inBitmap = bitmap1;
369 
370             Bitmap bitmap2 = BitmapFactory.decodeStream(in, null, opts);
371             assertSame(bitmap1, bitmap2);
372             ColorSpace cs = bitmap2.getColorSpace();
373             assertNotNull(cs);
374             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
375         } catch (IOException e) {
376             fail();
377         }
378     }
379 
380     @Test
getPixel()381     public void getPixel() {
382         verifyGetPixel("green-p3.png", 0x75fb4cff);
383         verifyGetPixel("translucent-green-p3.png", 0x3a7d267f); // 50% translucent
384     }
385 
verifyGetPixel(@onNull String fileName, @ColorInt int rawColor)386     private void verifyGetPixel(@NonNull String fileName, @ColorInt int rawColor) {
387         try (InputStream in = mResources.getAssets().open(fileName)) {
388             Bitmap b = BitmapFactory.decodeStream(in);
389             ColorSpace cs = b.getColorSpace();
390             assertNotNull(cs);
391             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
392 
393             verifyGetPixel(b, rawColor);
394 
395             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
396             verifyGetPixel(b, rawColor);
397 
398             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
399             verifyGetPixel(b, rawColor);
400         } catch (IOException e) {
401             fail();
402         }
403     }
404 
verifyGetPixel(@onNull Bitmap b, @ColorInt int rawColor)405     private static void verifyGetPixel(@NonNull Bitmap b, @ColorInt int rawColor) {
406         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
407         b.copyPixelsToBuffer(dst);
408         dst.rewind();
409 
410         // Stored as RGBA
411         assertEquals(rawColor, dst.asIntBuffer().get());
412 
413         int srgbColor = convertPremulColorToColorInt(rawColor, b.getColorSpace());
414         int srgb = b.getPixel(15, 15);
415         almostEqual(srgbColor, srgb, 3, 15 * b.getWidth() + 15);
416     }
417 
convertPremulColorToColorInt(int premulColor, ColorSpace premulCS)418     private static int convertPremulColorToColorInt(int premulColor, ColorSpace premulCS) {
419         float alpha = (premulColor & 0xff) / 255.0f;
420         return Color.toArgb(Color.convert((premulColor >>> 24) / 255.0f / alpha,
421                 ((premulColor >> 16) & 0xff) / 255.0f / alpha,
422                 ((premulColor >> 8) & 0xff) / 255.0f / alpha,
423                 alpha, premulCS, ColorSpace.get(ColorSpace.Named.SRGB)));
424     }
425 
426     @Test
getPixels()427     public void getPixels() {
428         verifyGetPixels("green-p3.png");
429         verifyGetPixels("translucent-green-p3.png"); // 50% translucent
430     }
431 
verifyGetPixels(@onNull String fileName)432     private void verifyGetPixels(@NonNull String fileName) {
433         try (InputStream in = mResources.getAssets().open(fileName)) {
434             Bitmap b = BitmapFactory.decodeStream(in);
435             ColorSpace cs = b.getColorSpace();
436             assertNotNull(cs);
437             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
438 
439             ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
440             b.copyPixelsToBuffer(dst);
441             dst.rewind();
442 
443             // Stored as RGBA
444             int expected = convertPremulColorToColorInt(dst.asIntBuffer().get(), b.getColorSpace());
445 
446             verifyGetPixels(b, expected);
447 
448             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
449             verifyGetPixels(b, expected);
450 
451             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
452             verifyGetPixels(b, expected);
453         } catch (IOException e) {
454             fail();
455         }
456     }
457 
verifyGetPixels(@onNull Bitmap b, @ColorInt int expected)458     private static void verifyGetPixels(@NonNull Bitmap b, @ColorInt int expected) {
459         int[] pixels = new int[b.getWidth() * b.getHeight()];
460         b.getPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
461 
462         for (int i = 0; i < pixels.length; i++) {
463             int pixel = pixels[i];
464             almostEqual(expected, pixel, 3, i);
465         }
466     }
467 
468     @Test
setPixel()469     public void setPixel() {
470         verifySetPixel("green-p3.png", 0xffff0000, 0xea3323ff);
471         verifySetPixel("translucent-green-p3.png", 0x7fff0000, 0x7519127f);
472     }
473 
verifySetPixel(@onNull String fileName, @ColorInt int newColor, @ColorInt int expectedColor)474     private void verifySetPixel(@NonNull String fileName,
475             @ColorInt int newColor, @ColorInt int expectedColor) {
476         try (InputStream in = mResources.getAssets().open(fileName)) {
477             BitmapFactory.Options opts = new BitmapFactory.Options();
478             opts.inMutable = true;
479 
480             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
481             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
482             assertTrue(b.isMutable());
483             verifySetPixel(b, newColor, expectedColor);
484 
485             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
486             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
487             assertTrue(b.isMutable());
488             verifySetPixel(b, newColor, expectedColor);
489 
490             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
491             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
492             assertTrue(b.isMutable());
493             verifySetPixel(b, newColor, expectedColor);
494         } catch (IOException e) {
495             fail();
496         }
497     }
498 
verifySetPixel(@onNull Bitmap b, @ColorInt int newColor, @ColorInt int expectedColor)499     private static void verifySetPixel(@NonNull Bitmap b,
500             @ColorInt int newColor, @ColorInt int expectedColor) {
501         assertTrue(b.isMutable());
502         b.setPixel(0, 0, newColor);
503 
504         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
505         b.copyPixelsToBuffer(dst);
506         dst.rewind();
507         // Stored as RGBA
508         ColorUtils.verifyColor(expectedColor, dst.asIntBuffer().get(), 1);
509     }
510 
511     @Test
setPixels()512     public void setPixels() {
513         verifySetPixels("green-p3.png", 0xffff0000, 0xea3323ff);
514         verifySetPixels("translucent-green-p3.png", 0x7fff0000, 0x7519127f);
515     }
516 
verifySetPixels(@onNull String fileName, @ColorInt int newColor, @ColorInt int expectedColor)517     private void verifySetPixels(@NonNull String fileName,
518             @ColorInt int newColor, @ColorInt int expectedColor) {
519         try (InputStream in = mResources.getAssets().open(fileName)) {
520             BitmapFactory.Options opts = new BitmapFactory.Options();
521             opts.inMutable = true;
522 
523             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
524             assertNotNull(b.getColorSpace());
525             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
526 
527             verifySetPixels(b, newColor, expectedColor);
528 
529             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
530             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
531             assertTrue(b.isMutable());
532             verifySetPixels(b, newColor, expectedColor);
533 
534             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
535             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
536             assertTrue(b.isMutable());
537             verifySetPixels(b, newColor, expectedColor);
538         } catch (IOException e) {
539             fail();
540         }
541     }
542 
verifySetPixels(@onNull Bitmap b, @ColorInt int newColor, @ColorInt int expectedColor)543     private static void verifySetPixels(@NonNull Bitmap b,
544             @ColorInt int newColor, @ColorInt int expectedColor) {
545         assertTrue(b.isMutable());
546         int[] pixels = new int[b.getWidth() * b.getHeight()];
547         Arrays.fill(pixels, newColor);
548         b.setPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
549 
550         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
551         b.copyPixelsToBuffer(dst);
552         dst.rewind();
553 
554         IntBuffer buffer = dst.asIntBuffer();
555         //noinspection ForLoopReplaceableByForEach
556         for (int i = 0; i < pixels.length; i++) {
557             // Stored as RGBA
558             ColorUtils.verifyColor(expectedColor, buffer.get(), 1);
559         }
560     }
561 
562     @Test
writeColorSpace()563     public void writeColorSpace() {
564         verifyColorSpaceMarshalling("green-srgb.png", ColorSpace.get(ColorSpace.Named.SRGB));
565         verifyColorSpaceMarshalling("green-p3.png", ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
566         verifyColorSpaceMarshalling("blue-16bit-srgb.png",
567                 ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB));
568 
569         Bitmap bitmapIn = BitmapFactory.decodeResource(mResources, R.drawable.robot);
570         verifyParcelUnparcel(bitmapIn, ColorSpace.get(ColorSpace.Named.SRGB));
571     }
572 
verifyColorSpaceMarshalling( @onNull String fileName, @NonNull ColorSpace colorSpace)573     private void verifyColorSpaceMarshalling(
574             @NonNull String fileName, @NonNull ColorSpace colorSpace) {
575         try (InputStream in = mResources.getAssets().open(fileName)) {
576             Bitmap bitmapIn = BitmapFactory.decodeStream(in);
577             verifyParcelUnparcel(bitmapIn, colorSpace);
578         } catch (IOException e) {
579             fail();
580         }
581     }
582 
verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected)583     private void verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected) {
584         ColorSpace cs = bitmapIn.getColorSpace();
585         assertNotNull(cs);
586         assertSame(expected, cs);
587 
588         Parcel p = Parcel.obtain();
589         bitmapIn.writeToParcel(p, 0);
590         p.setDataPosition(0);
591 
592         Bitmap bitmapOut = Bitmap.CREATOR.createFromParcel(p);
593         cs = bitmapOut.getColorSpace();
594         assertNotNull(cs);
595         assertSame(expected, cs);
596 
597         p.recycle();
598     }
599 
600     @Test
p3rgb565()601     public void p3rgb565() {
602         BitmapFactory.Options opts = new BitmapFactory.Options();
603         opts.inPreferredConfig = Bitmap.Config.RGB_565;
604 
605         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
606             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
607             ColorSpace cs = b.getColorSpace();
608             assertNotNull(cs);
609             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
610         } catch (IOException e) {
611             fail();
612         }
613     }
614 
615     @Test
p3hardware()616     public void p3hardware() {
617         BitmapFactory.Options opts = new BitmapFactory.Options();
618         opts.inPreferredConfig = Bitmap.Config.HARDWARE;
619 
620         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
621             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
622             ColorSpace cs = b.getColorSpace();
623             assertNotNull(cs);
624             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
625         } catch (IOException e) {
626             fail();
627         }
628     }
629 
630     @Test
guessSRGB()631     public void guessSRGB() {
632         BitmapFactory.Options opts = new BitmapFactory.Options();
633         opts.inJustDecodeBounds = true;
634 
635         try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
636             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
637             ColorSpace cs = opts.outColorSpace;
638             assertNull(b);
639             assertNotNull(cs);
640             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
641         } catch (IOException e) {
642             fail();
643         }
644     }
645 
646     @Test
guess16bitUntagged()647     public void guess16bitUntagged() {
648         BitmapFactory.Options opts = new BitmapFactory.Options();
649         opts.inJustDecodeBounds = true;
650 
651         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
652             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
653             ColorSpace cs = opts.outColorSpace;
654             assertNull(b);
655             assertNotNull(cs);
656             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
657         } catch (IOException e) {
658             fail();
659         }
660     }
661 
662     @Test
guessProPhotoRGB()663     public void guessProPhotoRGB() {
664         BitmapFactory.Options opts = new BitmapFactory.Options();
665         opts.inJustDecodeBounds = true;
666 
667         try (InputStream in = mResources.getAssets().open("blue-16bit-prophoto.png")) {
668             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
669             ColorSpace cs = opts.outColorSpace;
670             assertNull(b);
671             assertNotNull(cs);
672             assertSame(ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), cs);
673         } catch (IOException e) {
674             fail();
675         }
676     }
677 
678     @Test
guessP3()679     public void guessP3() {
680         BitmapFactory.Options opts = new BitmapFactory.Options();
681         opts.inJustDecodeBounds = true;
682 
683         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
684             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
685             ColorSpace cs = opts.outColorSpace;
686             assertNull(b);
687             assertNotNull(cs);
688             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
689         } catch (IOException e) {
690             fail();
691         }
692     }
693 
694     @Test
guessAdobeRGB()695     public void guessAdobeRGB() {
696         BitmapFactory.Options opts = new BitmapFactory.Options();
697         opts.inJustDecodeBounds = true;
698 
699         try (InputStream in = mResources.getAssets().open("red-adobergb.png")) {
700             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
701             ColorSpace cs = opts.outColorSpace;
702             assertNull(b);
703             assertNotNull(cs);
704             assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), cs);
705         } catch (IOException e) {
706             fail();
707         }
708     }
709 
710     @Test
guessUnknown()711     public void guessUnknown() {
712         BitmapFactory.Options opts = new BitmapFactory.Options();
713         opts.inJustDecodeBounds = true;
714 
715         try (InputStream in = mResources.getAssets().open("purple-displayprofile.png")) {
716             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
717             ColorSpace cs = opts.outColorSpace;
718             assertNull(b);
719             assertNotNull(cs);
720             assertEquals("Unknown", cs.getName());
721         } catch (IOException e) {
722             fail();
723         }
724     }
725 
726     @Test
guessCMYK()727     public void guessCMYK() {
728         BitmapFactory.Options opts = new BitmapFactory.Options();
729         opts.inJustDecodeBounds = true;
730 
731         try (InputStream in = mResources.getAssets().open("purple-cmyk.png")) {
732             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
733             ColorSpace cs = opts.outColorSpace;
734             assertNull(b);
735             assertNotNull(cs);
736             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
737         } catch (IOException e) {
738             fail();
739         }
740     }
741 
742     @Test
inColorSpaceP3ToSRGB()743     public void inColorSpaceP3ToSRGB() {
744         BitmapFactory.Options opts = new BitmapFactory.Options();
745         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
746 
747         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
748             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
749             ColorSpace cs = b.getColorSpace();
750             assertNotNull(cs);
751             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
752             assertEquals(opts.inPreferredColorSpace, opts.outColorSpace);
753 
754             verifyGetPixel(b, 0x2ff00ff);
755         } catch (IOException e) {
756             fail();
757         }
758     }
759 
760     @Test
inColorSpaceSRGBToP3()761     public void inColorSpaceSRGBToP3() {
762         BitmapFactory.Options opts = new BitmapFactory.Options();
763         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
764 
765         try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
766             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
767             ColorSpace cs = b.getColorSpace();
768             assertNotNull(cs);
769             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
770             assertEquals(opts.inPreferredColorSpace, opts.outColorSpace);
771 
772             verifyGetPixel(b, 0x75fb4cff);
773         } catch (IOException e) {
774             fail();
775         }
776     }
777 
778     @Test
inColorSpaceWith16BitSrc()779     public void inColorSpaceWith16BitSrc() {
780         BitmapFactory.Options opts = new BitmapFactory.Options();
781         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
782 
783         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
784             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
785             ColorSpace cs = b.getColorSpace();
786             assertNotNull(cs);
787             assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), cs);
788             assertSame(opts.inPreferredColorSpace, opts.outColorSpace);
789         } catch (IOException e) {
790             fail();
791         }
792     }
793 
794     @Test
inColorSpaceWith16BitDst()795     public void inColorSpaceWith16BitDst() {
796         BitmapFactory.Options opts = new BitmapFactory.Options();
797         opts.inPreferredConfig = Bitmap.Config.RGBA_F16;
798 
799         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
800             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
801             ColorSpace cs = b.getColorSpace();
802             assertNotNull(cs);
803             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
804         } catch (IOException e) {
805             fail();
806         }
807     }
808 
809     @Test
inColorSpaceWith16BitSrcAndDst()810     public void inColorSpaceWith16BitSrcAndDst() {
811         BitmapFactory.Options opts = new BitmapFactory.Options();
812         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
813         opts.inPreferredConfig = Bitmap.Config.RGBA_F16;
814 
815         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
816             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
817             ColorSpace cs = b.getColorSpace();
818             assertNotNull(cs);
819             assertSame(opts.inPreferredColorSpace, cs);
820             assertSame(opts.inPreferredColorSpace, opts.outColorSpace);
821         } catch (IOException e) {
822             fail();
823         }
824     }
825 
826     @Test
inColorSpaceWith16BitWithDecreasedGamut()827     public void inColorSpaceWith16BitWithDecreasedGamut() {
828         final String asset = "blue-16bit-prophoto.png";
829         BitmapFactory.Options opts = new BitmapFactory.Options();
830         opts.inJustDecodeBounds = true;
831         try (InputStream in = mResources.getAssets().open(asset)) {
832             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
833             assertNull(b);
834             assertEquals(ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB), opts.outColorSpace);
835             assertEquals(Bitmap.Config.RGBA_F16, opts.outConfig);
836         } catch (IOException e) {
837             fail();
838         }
839 
840         opts.inJustDecodeBounds = false;
841         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
842 
843         try (InputStream in = mResources.getAssets().open(asset)) {
844             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
845             ColorSpace cs = b.getColorSpace();
846             assertNotNull(cs);
847             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
848         } catch (IOException e) {
849             fail();
850         }
851     }
852 
853     @Test
inColorSpace565()854     public void inColorSpace565() {
855         BitmapFactory.Options opts = new BitmapFactory.Options();
856         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
857         opts.inPreferredConfig = Bitmap.Config.RGB_565;
858 
859         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
860             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
861             ColorSpace cs = b.getColorSpace();
862             assertNotNull(cs);
863             assertSame(opts.inPreferredColorSpace, cs);
864             assertSame(opts.inPreferredColorSpace, opts.outColorSpace);
865         } catch (IOException e) {
866             fail();
867         }
868     }
869 
870     @Test(expected = IllegalArgumentException.class)
inColorSpaceNotRGB()871     public void inColorSpaceNotRGB() {
872         BitmapFactory.Options opts = new BitmapFactory.Options();
873         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.CIE_LAB);
874 
875         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
876             BitmapFactory.decodeStream(in, null, opts);
877         } catch (IOException e) {
878             fail();
879         }
880     }
881 
882     @Test(expected = IllegalArgumentException.class)
inColorSpaceNoTransferParameters()883     public void inColorSpaceNoTransferParameters() {
884         BitmapFactory.Options opts = new BitmapFactory.Options();
885         opts.inPreferredColorSpace = new ColorSpace.Rgb("NoTransferParams",
886                 new float[]{ 0.640f, 0.330f, 0.300f, 0.600f, 0.150f, 0.060f },
887                 ColorSpace.ILLUMINANT_D50,
888                 x -> Math.pow(x, 1.0f / 2.2f), x -> Math.pow(x, 2.2f),
889                 0, 1);
890 
891         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
892             BitmapFactory.decodeStream(in, null, opts);
893         } catch (IOException e) {
894             fail();
895         }
896     }
897 
898     @Test
copyF16()899     public void copyF16() {
900         // Copying from (LINEAR_)SRGB to RGBA_F16 results in (LINEAR_)EXTENDED_SRGB.
901         ColorSpace[] srcCS = new ColorSpace[] { ColorSpace.get(ColorSpace.Named.SRGB),
902             ColorSpace.get(ColorSpace.Named.LINEAR_SRGB) };
903         ColorSpace[] dstCS = new ColorSpace[] { ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB),
904             ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB) };
905 
906         for (int i = 0; i < srcCS.length; ++i) {
907             for (Bitmap.Config config : new Bitmap.Config[] { Bitmap.Config.ARGB_8888,
908                     Bitmap.Config.RGB_565 }) {
909                 Bitmap b = Bitmap.createBitmap(10, 10, config, false, srcCS[i]);
910                 assertSame(srcCS[i], b.getColorSpace());
911 
912                 for (boolean mutable : new boolean[] { true, false }) {
913                     Bitmap copy = b.copy(Bitmap.Config.RGBA_F16, mutable);
914                     assertSame(dstCS[i], copy.getColorSpace());
915                 }
916             }
917         }
918 
919         // The same is true for the reverse
920         for (int i = 0; i < srcCS.length; ++i) {
921             Bitmap b = Bitmap.createBitmap(10, 10, Bitmap.Config.RGBA_F16, false, dstCS[i]);
922             assertSame(dstCS[i], b.getColorSpace());
923             for (Bitmap.Config config : new Bitmap.Config[] { Bitmap.Config.ARGB_8888,
924                     Bitmap.Config.RGB_565 }) {
925                 for (boolean mutable : new boolean[] { true, false }) {
926                     Bitmap copy = b.copy(config, mutable);
927                     assertSame(srcCS[i], copy.getColorSpace());
928                 }
929             }
930         }
931     }
932 
933     @Test
copyAlpha8()934     public void copyAlpha8() {
935         for (Bitmap.Config srcConfig : new Bitmap.Config[] {
936                 Bitmap.Config.ALPHA_8,
937                 Bitmap.Config.RGB_565,
938                 Bitmap.Config.ARGB_8888,
939                 Bitmap.Config.RGBA_F16,
940         }) {
941             Bitmap b = Bitmap.createBitmap(1, 1, srcConfig);
942             assertNotNull(b);
943             if (srcConfig == Bitmap.Config.ALPHA_8) {
944                 assertNull(b.getColorSpace());
945             } else {
946                 assertNotNull(b.getColorSpace());
947             }
948 
949             Bitmap copy = b.copy(Bitmap.Config.ALPHA_8, false);
950             assertNotNull(copy);
951             assertNull(copy.getColorSpace());
952 
953             Bitmap copy2 = copy.copy(srcConfig, false);
954             switch (srcConfig) {
955                 case RGBA_F16:
956                     assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB),
957                             copy2.getColorSpace());
958                     break;
959                 case ALPHA_8:
960                     assertNull(b.getColorSpace());
961                     break;
962                 default:
963                     assertSame("Copied from ALPHA_8 to " + srcConfig,
964                             ColorSpace.get(ColorSpace.Named.SRGB), copy2.getColorSpace());
965             }
966         }
967     }
968 
969     @Test
copyHardwareToAlpha8()970     public void copyHardwareToAlpha8() {
971         BitmapFactory.Options options = new BitmapFactory.Options();
972         options.inPreferredConfig = Bitmap.Config.HARDWARE;
973         Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot, options);
974         assertSame(Bitmap.Config.HARDWARE, b.getConfig());
975         assertNotNull(b.getColorSpace());
976 
977         Bitmap copy = b.copy(Bitmap.Config.ALPHA_8, false);
978         assertNull(copy.getColorSpace());
979     }
980 
981     @Test
copy()982     public void copy() {
983         Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot);
984         Bitmap c;
985         ColorSpace cs;
986         boolean[] trueFalse = new boolean[] { true, false };
987 
988         for (boolean mutable : trueFalse) {
989             c = b.copy(Bitmap.Config.ARGB_8888, mutable);
990             cs = c.getColorSpace();
991             assertNotNull(cs);
992             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
993         }
994 
995         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
996             b = BitmapFactory.decodeStream(in);
997             c = b.copy(Bitmap.Config.ARGB_8888, false);
998             cs = c.getColorSpace();
999             assertNotNull(cs);
1000             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
1001 
1002             c = b.copy(Bitmap.Config.ARGB_8888, true);
1003             cs = c.getColorSpace();
1004             assertNotNull(cs);
1005             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
1006         } catch (IOException e) {
1007             fail();
1008         }
1009 
1010         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
1011             b = BitmapFactory.decodeStream(in);
1012             c = b.copy(Bitmap.Config.RGBA_F16, false);
1013             cs = c.getColorSpace();
1014             assertNotNull(cs);
1015             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
1016 
1017             c = b.copy(Bitmap.Config.RGBA_F16, true);
1018             cs = c.getColorSpace();
1019             assertNotNull(cs);
1020             assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), cs);
1021         } catch (IOException e) {
1022             fail();
1023         }
1024     }
1025 
1026     @SuppressWarnings("SameParameterValue")
almostEqual(@olorInt int expected, @ColorInt int pixel, int threshold, int index)1027     private static void almostEqual(@ColorInt int expected,
1028             @ColorInt int pixel, int threshold, int index) {
1029         int diffA = Math.abs((expected >>> 24) - (pixel >>> 24));
1030         int diffR = Math.abs(((expected >> 16) & 0xff) - ((pixel >> 16) & 0xff));
1031         int diffG = Math.abs(((expected >>  8) & 0xff) - ((pixel >>  8) & 0xff));
1032         int diffB = Math.abs((expected & 0xff) - (pixel & 0xff));
1033 
1034         boolean pass = diffA + diffR + diffG + diffB <= threshold;
1035         if (!pass) {
1036             Log.d(LOG_TAG, "Expected 0x" + Integer.toHexString(expected) +
1037                     " but was 0x" + Integer.toHexString(pixel) + " with index " + index);
1038         }
1039 
1040         assertTrue(pass);
1041     }
1042 
compressFormatsAndColorSpaces()1043     private Object[] compressFormatsAndColorSpaces() {
1044         return Utils.crossProduct(Bitmap.CompressFormat.values(),
1045                 BitmapTest.getRgbColorSpaces().toArray());
1046     }
1047 
1048     @Test
1049     @Parameters(method = "compressFormatsAndColorSpaces")
testEncodeColorSpace(Bitmap.CompressFormat format, ColorSpace colorSpace)1050     public void testEncodeColorSpace(Bitmap.CompressFormat format, ColorSpace colorSpace) {
1051         Bitmap b = null;
1052         ColorSpace decodedColorSpace = null;
1053         ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(),
1054                 "blue-16bit-srgb.png");
1055         try {
1056             b = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
1057                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
1058                 decoder.setTargetColorSpace(colorSpace);
1059             });
1060             assertNotNull(b);
1061             assertEquals(Bitmap.Config.RGBA_F16, b.getConfig());
1062             decodedColorSpace = b.getColorSpace();
1063 
1064             // Requesting a ColorSpace with an EXTENDED variant will use the EXTENDED one because
1065             // the image is 16-bit.
1066             if (colorSpace == ColorSpace.get(ColorSpace.Named.SRGB)) {
1067                 assertSame(ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB), decodedColorSpace);
1068             } else if (colorSpace == ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)) {
1069                 assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB),
1070                           decodedColorSpace);
1071             } else {
1072                 assertSame(colorSpace, decodedColorSpace);
1073             }
1074         } catch (IOException e) {
1075             fail("Failed with " + e);
1076         }
1077 
1078         ByteArrayOutputStream out = new ByteArrayOutputStream();
1079         assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out));
1080 
1081         byte[] array = out.toByteArray();
1082         src = ImageDecoder.createSource(ByteBuffer.wrap(array));
1083 
1084         try {
1085             Bitmap b2 = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
1086                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
1087             });
1088             ColorSpace encodedColorSpace = b2.getColorSpace();
1089             if (format == Bitmap.CompressFormat.PNG) {
1090                 assertEquals(Bitmap.Config.RGBA_F16, b2.getConfig());
1091                 assertSame(decodedColorSpace, encodedColorSpace);
1092             } else {
1093                 // Compressing to the other formats does not support creating a compressed version
1094                 // that we will decode to F16.
1095                 assertEquals(Bitmap.Config.ARGB_8888, b2.getConfig());
1096 
1097                 // Decoding an EXTENDED variant to 8888 results in the non-extended variant.
1098                 if (decodedColorSpace == ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)) {
1099                     assertSame(ColorSpace.get(ColorSpace.Named.SRGB), encodedColorSpace);
1100                 } else if (decodedColorSpace
1101                         == ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)) {
1102                     assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_SRGB), encodedColorSpace);
1103                 } else {
1104                     assertSame(decodedColorSpace, encodedColorSpace);
1105                 }
1106             }
1107         } catch (IOException e) {
1108             fail("Failed with " + e);
1109         }
1110     }
1111 
1112     @Test
testEncodeP3hardware()1113     public void testEncodeP3hardware() {
1114         Bitmap b = null;
1115         ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(),
1116                 "green-p3.png");
1117         try {
1118             b = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
1119                 decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);
1120             });
1121             assertNotNull(b);
1122             assertEquals(Bitmap.Config.HARDWARE, b.getConfig());
1123             assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
1124         } catch (IOException e) {
1125             fail("Failed with " + e);
1126         }
1127 
1128         for (Bitmap.CompressFormat format : Bitmap.CompressFormat.values()) {
1129             ByteArrayOutputStream out = new ByteArrayOutputStream();
1130             assertTrue("Failed to encode 8888 to " + format, b.compress(format, 100, out));
1131 
1132             byte[] array = out.toByteArray();
1133             src = ImageDecoder.createSource(ByteBuffer.wrap(array));
1134 
1135             try {
1136                 Bitmap b2 = ImageDecoder.decodeBitmap(src);
1137                 assertEquals("Wrong color space for " + format,
1138                         ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
1139             } catch (IOException e) {
1140                 fail("Failed with " + e);
1141             }
1142         }
1143     }
1144 
1145     @Test
1146     @RequiresDevice // SwiftShader does not yet have support for F16 in HARDWARE b/75778024
test16bitHardware()1147     public void test16bitHardware() {
1148         // Decoding to HARDWARE may use EXTENDED_SRGB or SRGB, depending
1149         // on whether F16 is supported in HARDWARE.
1150         try (InputStream in = mResources.getAssets().open("blue-16bit-srgb.png")) {
1151             BitmapFactory.Options options = new BitmapFactory.Options();
1152             options.inPreferredConfig = Bitmap.Config.HARDWARE;
1153             Bitmap b = BitmapFactory.decodeStream(in, null, options);
1154             assertEquals(Bitmap.Config.HARDWARE, b.getConfig());
1155 
1156             final ColorSpace cs = b.getColorSpace();
1157             if (cs != ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB)
1158                     && cs != ColorSpace.get(ColorSpace.Named.SRGB)) {
1159                 fail("Unexpected color space " + cs);
1160             }
1161         } catch (Exception e) {
1162             fail("Failed with " + e);
1163         }
1164     }
1165 
1166     @Test
testProPhoto()1167     public void testProPhoto() throws IOException {
1168         ColorSpace extendedSrgb = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
1169         Color blue = Color.valueOf(0, 0, 1, 1, ColorSpace.get(ColorSpace.Named.PRO_PHOTO_RGB));
1170         Color expected = blue.convert(extendedSrgb);
1171         try (InputStream in = mResources.getAssets().open("blue-16bit-prophoto.png")) {
1172             Bitmap src = BitmapFactory.decodeStream(in, null, null);
1173 
1174             Bitmap dst = Bitmap.createBitmap(src.getWidth(), src.getHeight(),
1175                     Bitmap.Config.RGBA_F16, true, extendedSrgb);
1176             Canvas c = new Canvas(dst);
1177             c.drawBitmap(src, 0, 0, null);
1178             ColorUtils.verifyColor("PRO_PHOTO image did not convert properly", expected,
1179                     dst.getColor(0, 0), .001f);
1180         }
1181     }
1182 
1183     @Test
testGrayscaleProfile()1184     public void testGrayscaleProfile() throws IOException {
1185         ImageDecoder.Source source = ImageDecoder.createSource(mResources.getAssets(),
1186                 "gimp-d65-grayscale.jpg");
1187         Bitmap bm = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> {
1188             decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
1189         });
1190         ColorSpace cs = bm.getColorSpace();
1191         assertNotNull(cs);
1192         assertTrue(cs instanceof ColorSpace.Rgb);
1193         ColorSpace.Rgb rgbCs = (ColorSpace.Rgb) cs;
1194 
1195         // A gray color space uses a special primaries array of all 1s.
1196         float[] primaries = rgbCs.getPrimaries();
1197         assertNotNull(primaries);
1198         assertEquals(6, primaries.length);
1199         for (float primary : primaries) {
1200             assertEquals(0, Float.compare(primary, 1.0f));
1201         }
1202 
1203         // A gray color space will have all zeroes in the transform
1204         // and inverse transform, except for the diagonal.
1205         for (float[] transform : new float[][]{rgbCs.getTransform(), rgbCs.getInverseTransform()}) {
1206             assertNotNull(transform);
1207             assertEquals(9, transform.length);
1208             for (int index : new int[] { 1, 2, 3, 5, 6, 7 }) {
1209                 assertEquals(0, Float.compare(0.0f, transform[index]));
1210             }
1211         }
1212 
1213         // When creating another Bitmap with the same ColorSpace, the two
1214         // ColorSpaces should be equal.
1215         Bitmap otherBm = Bitmap.createBitmap(null, 100, 100, Bitmap.Config.ARGB_8888, true, cs);
1216         assertEquals(cs, otherBm.getColorSpace());
1217 
1218         // Same for a scaled bitmap.
1219         Bitmap scaledBm = Bitmap.createScaledBitmap(bm, bm.getWidth() / 4, bm.getHeight() / 4,
1220                 true);
1221         assertEquals(cs, scaledBm.getColorSpace());
1222 
1223         // A previous ColorSpace bug resulted in a Bitmap created like scaledBm
1224         // having all black pixels. Verify that the Bitmap contains colors other
1225         // than black and white.
1226         boolean foundOtherColor = false;
1227         final int width = scaledBm.getWidth();
1228         final int height = scaledBm.getHeight();
1229         int[] pixels = new int[width * height];
1230         scaledBm.getPixels(pixels, 0, width, 0, 0, width, height);
1231         for (int pixel : pixels) {
1232             if (pixel != Color.BLACK && pixel != Color.WHITE) {
1233                 foundOtherColor = true;
1234                 break;
1235             }
1236         }
1237         assertTrue(foundOtherColor);
1238     }
1239 }
1240