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