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.assertNotEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertNull;
23 import static org.junit.Assert.assertSame;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 
27 import android.content.res.Resources;
28 import android.graphics.Bitmap;
29 import android.graphics.BitmapFactory;
30 import android.graphics.ColorSpace;
31 import android.graphics.ImageDecoder;
32 import android.os.Parcel;
33 import android.support.test.InstrumentationRegistry;
34 import android.support.test.filters.RequiresDevice;
35 import android.support.test.filters.SmallTest;
36 import android.support.test.runner.AndroidJUnit4;
37 import android.util.Log;
38 
39 import androidx.annotation.ColorInt;
40 import androidx.annotation.NonNull;
41 
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 
46 import java.io.ByteArrayOutputStream;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.nio.ByteBuffer;
50 import java.nio.IntBuffer;
51 import java.util.Arrays;
52 
53 @SmallTest
54 @RunWith(AndroidJUnit4.class)
55 public class BitmapColorSpaceTest {
56     private static final String LOG_TAG = "BitmapColorSpaceTest";
57 
58     private Resources mResources;
59 
60     @Before
setup()61     public void setup() {
62         mResources = InstrumentationRegistry.getTargetContext().getResources();
63     }
64 
65     @SuppressWarnings("deprecation")
66     @Test
createWithColorSpace()67     public void createWithColorSpace() {
68         Bitmap b;
69         ColorSpace cs;
70         ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
71 
72         // We don't test HARDWARE configs because they are not compatible with mutable bitmaps
73 
74         b = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true, sRGB);
75         cs = b.getColorSpace();
76         assertNotNull(cs);
77         assertSame(sRGB, cs);
78 
79         b = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true,
80                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
81         cs = b.getColorSpace();
82         assertNotNull(cs);
83         assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), cs);
84 
85         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGBA_F16, true, sRGB);
86         cs = b.getColorSpace();
87         assertNotNull(cs);
88         assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
89 
90         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGBA_F16, true,
91                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
92         cs = b.getColorSpace();
93         assertNotNull(cs);
94         assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
95 
96         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565, true, sRGB);
97         cs = b.getColorSpace();
98         assertNotNull(cs);
99         assertSame(sRGB, cs);
100 
101         b = Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565, true,
102                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
103         cs = b.getColorSpace();
104         assertNotNull(cs);
105         assertSame(sRGB, cs);
106 
107         b = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8, true, sRGB);
108         cs = b.getColorSpace();
109         assertNotNull(cs);
110         assertSame(sRGB, cs);
111 
112         b = Bitmap.createBitmap(32, 32, Bitmap.Config.ALPHA_8, true,
113                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
114         cs = b.getColorSpace();
115         assertNotNull(cs);
116         assertSame(sRGB, cs);
117 
118         b = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_4444, true, sRGB);
119         cs = b.getColorSpace();
120         assertNotNull(cs);
121         assertSame(sRGB, cs);
122 
123         b = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_4444, true,
124                 ColorSpace.get(ColorSpace.Named.ADOBE_RGB));
125         cs = b.getColorSpace();
126         assertNotNull(cs);
127         assertSame(sRGB, cs);
128     }
129 
130     @Test
createDefaultColorSpace()131     public void createDefaultColorSpace() {
132         ColorSpace sRGB = ColorSpace.get(ColorSpace.Named.SRGB);
133         Bitmap.Config[] configs = new Bitmap.Config[] {
134                 Bitmap.Config.ALPHA_8, Bitmap.Config.RGB_565, Bitmap.Config.ARGB_8888
135         };
136         for (Bitmap.Config config : configs) {
137             Bitmap bitmap = Bitmap.createBitmap(32, 32, config, true);
138             assertSame(sRGB, bitmap.getColorSpace());
139         }
140     }
141 
142     @Test(expected = IllegalArgumentException.class)
createWithoutColorSpace()143     public void createWithoutColorSpace() {
144         Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true, null);
145     }
146 
147     @Test(expected = IllegalArgumentException.class)
createWithNonRgbColorSpace()148     public void createWithNonRgbColorSpace() {
149         Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888, true,
150                 ColorSpace.get(ColorSpace.Named.CIE_LAB));
151     }
152 
153     @Test
sRGB()154     public void sRGB() {
155         Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot);
156         ColorSpace cs = b.getColorSpace();
157         assertNotNull(cs);
158         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
159 
160         b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
161         cs = b.getColorSpace();
162         assertNotNull(cs);
163         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
164 
165         b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
166         cs = b.getColorSpace();
167         assertNotNull(cs);
168         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
169     }
170 
171     @Test
p3()172     public void p3() {
173         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
174             Bitmap b = BitmapFactory.decodeStream(in);
175             ColorSpace cs = b.getColorSpace();
176             assertNotNull(cs);
177             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
178 
179             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
180             cs = b.getColorSpace();
181             assertNotNull(cs);
182             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
183 
184             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
185             cs = b.getColorSpace();
186             assertNotNull(cs);
187             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
188         } catch (IOException e) {
189             fail();
190         }
191     }
192 
193     @Test
extendedSRGB()194     public void extendedSRGB() {
195         try (InputStream in = mResources.getAssets().open("prophoto-rgba16f.png")) {
196             Bitmap b = BitmapFactory.decodeStream(in);
197             ColorSpace cs = b.getColorSpace();
198             assertNotNull(cs);
199             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
200 
201             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
202             cs = b.getColorSpace();
203             assertNotNull(cs);
204             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
205 
206             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
207             cs = b.getColorSpace();
208             assertNotNull(cs);
209             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
210         } catch (IOException e) {
211             fail();
212         }
213     }
214 
215     @Test
reconfigure()216     public void reconfigure() {
217         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
218             BitmapFactory.Options opts = new BitmapFactory.Options();
219             opts.inMutable = true;
220 
221             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
222             ColorSpace cs = b.getColorSpace();
223             assertNotNull(cs);
224             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
225 
226             b.reconfigure(b.getWidth() / 2, b.getHeight() / 2, Bitmap.Config.RGBA_F16);
227             cs = b.getColorSpace();
228             assertNotNull(cs);
229             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
230 
231             b.reconfigure(b.getWidth(), b.getHeight(), Bitmap.Config.ARGB_8888);
232             cs = b.getColorSpace();
233             assertNotNull(cs);
234             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
235         } catch (IOException e) {
236             fail();
237         }
238     }
239 
240     @Test
reuse()241     public void reuse() {
242         BitmapFactory.Options opts = new BitmapFactory.Options();
243         opts.inMutable = true;
244 
245         Bitmap bitmap1 = null;
246         try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
247             bitmap1 = BitmapFactory.decodeStream(in, null, opts);
248             ColorSpace cs = bitmap1.getColorSpace();
249             assertNotNull(cs);
250             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
251         } catch (IOException e) {
252             fail();
253         }
254 
255         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
256             opts.inBitmap = bitmap1;
257 
258             Bitmap bitmap2 = BitmapFactory.decodeStream(in, null, opts);
259             assertSame(bitmap1, bitmap2);
260             ColorSpace cs = bitmap2.getColorSpace();
261             assertNotNull(cs);
262             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
263         } catch (IOException e) {
264             fail();
265         }
266     }
267 
268     @Test
getPixel()269     public void getPixel() {
270         verifyGetPixel("green-p3.png", 0x75fb4cff, 0xff00ff00);
271         verifyGetPixel("translucent-green-p3.png", 0x3a7d267f, 0x7f00ff00); // 50% translucent
272     }
273 
verifyGetPixel(@onNull String fileName, @ColorInt int rawColor, @ColorInt int srgbColor)274     private void verifyGetPixel(@NonNull String fileName,
275             @ColorInt int rawColor, @ColorInt int srgbColor) {
276         try (InputStream in = mResources.getAssets().open(fileName)) {
277             Bitmap b = BitmapFactory.decodeStream(in);
278             ColorSpace cs = b.getColorSpace();
279             assertNotNull(cs);
280             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
281 
282             verifyGetPixel(b, rawColor, srgbColor);
283 
284             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
285             verifyGetPixel(b, rawColor, srgbColor);
286 
287             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
288             verifyGetPixel(b, rawColor, srgbColor);
289         } catch (IOException e) {
290             fail();
291         }
292     }
293 
verifyGetPixel(@onNull Bitmap b, @ColorInt int rawColor, @ColorInt int srgbColor)294     private static void verifyGetPixel(@NonNull Bitmap b,
295             @ColorInt int rawColor, @ColorInt int srgbColor) {
296         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
297         b.copyPixelsToBuffer(dst);
298         dst.rewind();
299 
300         // Stored as RGBA
301         assertEquals(rawColor, dst.asIntBuffer().get());
302 
303         int srgb = b.getPixel(15, 15);
304         almostEqual(srgbColor, srgb, 3, 15 * b.getWidth() + 15);
305     }
306 
307     @Test
getPixels()308     public void getPixels() {
309         verifyGetPixels("green-p3.png", 0xff00ff00);
310         verifyGetPixels("translucent-green-p3.png", 0x7f00ff00); // 50% translucent
311     }
312 
verifyGetPixels(@onNull String fileName, @ColorInt int expected)313     private void verifyGetPixels(@NonNull String fileName, @ColorInt int expected) {
314         try (InputStream in = mResources.getAssets().open(fileName)) {
315             Bitmap b = BitmapFactory.decodeStream(in);
316             ColorSpace cs = b.getColorSpace();
317             assertNotNull(cs);
318             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
319 
320             verifyGetPixels(b, expected);
321 
322             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
323             verifyGetPixels(b, expected);
324 
325             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
326             verifyGetPixels(b, expected);
327         } catch (IOException e) {
328             fail();
329         }
330     }
331 
verifyGetPixels(@onNull Bitmap b, @ColorInt int expected)332     private static void verifyGetPixels(@NonNull Bitmap b, @ColorInt int expected) {
333         int[] pixels = new int[b.getWidth() * b.getHeight()];
334         b.getPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
335 
336         for (int i = 0; i < pixels.length; i++) {
337             int pixel = pixels[i];
338             almostEqual(expected, pixel, 3, i);
339         }
340     }
341 
342     @Test
setPixel()343     public void setPixel() {
344         verifySetPixel("green-p3.png", 0xffff0000, 0xea3323ff);
345         verifySetPixel("translucent-green-p3.png", 0x7fff0000, 0x7519117f);
346     }
347 
verifySetPixel(@onNull String fileName, @ColorInt int newColor, @ColorInt int expectedColor)348     private void verifySetPixel(@NonNull String fileName,
349             @ColorInt int newColor, @ColorInt int expectedColor) {
350         try (InputStream in = mResources.getAssets().open(fileName)) {
351             BitmapFactory.Options opts = new BitmapFactory.Options();
352             opts.inMutable = true;
353 
354             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
355             ColorSpace cs = b.getColorSpace();
356             assertNotNull(cs);
357             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
358 
359             verifySetPixel(b, newColor, expectedColor);
360 
361             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
362             verifySetPixel(b, newColor, expectedColor);
363 
364             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
365             verifySetPixel(b, newColor, expectedColor);
366         } catch (IOException e) {
367             fail();
368         }
369     }
370 
verifySetPixel(@onNull Bitmap b, @ColorInt int newColor, @ColorInt int expectedColor)371     private static void verifySetPixel(@NonNull Bitmap b,
372             @ColorInt int newColor, @ColorInt int expectedColor) {
373         b.setPixel(0, 0, newColor);
374 
375         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
376         b.copyPixelsToBuffer(dst);
377         dst.rewind();
378         // Stored as RGBA
379         assertEquals(expectedColor, dst.asIntBuffer().get());
380     }
381 
382     @Test
setPixels()383     public void setPixels() {
384         verifySetPixels("green-p3.png", 0xffff0000, 0xea3323ff);
385         verifySetPixels("translucent-green-p3.png", 0x7fff0000, 0x7519117f);
386     }
387 
verifySetPixels(@onNull String fileName, @ColorInt int newColor, @ColorInt int expectedColor)388     private void verifySetPixels(@NonNull String fileName,
389             @ColorInt int newColor, @ColorInt int expectedColor) {
390         try (InputStream in = mResources.getAssets().open(fileName)) {
391             BitmapFactory.Options opts = new BitmapFactory.Options();
392             opts.inMutable = true;
393 
394             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
395             ColorSpace cs = b.getColorSpace();
396             assertNotNull(cs);
397             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
398 
399             verifySetPixels(b, newColor, expectedColor);
400 
401             b = Bitmap.createBitmap(b, 0, 0, b.getWidth() / 2, b.getHeight() / 2);
402             verifySetPixels(b, newColor, expectedColor);
403 
404             b = Bitmap.createScaledBitmap(b, b.getWidth() / 2, b.getHeight() / 2, true);
405             verifySetPixels(b, newColor, expectedColor);
406         } catch (IOException e) {
407             fail();
408         }
409     }
410 
verifySetPixels(@onNull Bitmap b, @ColorInt int newColor, @ColorInt int expectedColor)411     private static void verifySetPixels(@NonNull Bitmap b,
412             @ColorInt int newColor, @ColorInt int expectedColor) {
413         int[] pixels = new int[b.getWidth() * b.getHeight()];
414         Arrays.fill(pixels, newColor);
415         b.setPixels(pixels, 0, b.getWidth(), 0, 0, b.getWidth(), b.getHeight());
416 
417         ByteBuffer dst = ByteBuffer.allocate(b.getByteCount());
418         b.copyPixelsToBuffer(dst);
419         dst.rewind();
420 
421         IntBuffer buffer = dst.asIntBuffer();
422         //noinspection ForLoopReplaceableByForEach
423         for (int i = 0; i < pixels.length; i++) {
424             // Stored as RGBA
425             assertEquals(expectedColor, buffer.get());
426         }
427     }
428 
429     @Test
writeColorSpace()430     public void writeColorSpace() {
431         verifyColorSpaceMarshalling("green-srgb.png", ColorSpace.get(ColorSpace.Named.SRGB));
432         verifyColorSpaceMarshalling("green-p3.png", ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
433         verifyColorSpaceMarshalling("prophoto-rgba16f.png",
434                 ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB));
435 
436         // Special case where the color space will be null in native
437         Bitmap bitmapIn = BitmapFactory.decodeResource(mResources, R.drawable.robot);
438         verifyParcelUnparcel(bitmapIn, ColorSpace.get(ColorSpace.Named.SRGB));
439     }
440 
verifyColorSpaceMarshalling( @onNull String fileName, @NonNull ColorSpace colorSpace)441     private void verifyColorSpaceMarshalling(
442             @NonNull String fileName, @NonNull ColorSpace colorSpace) {
443         try (InputStream in = mResources.getAssets().open(fileName)) {
444             Bitmap bitmapIn = BitmapFactory.decodeStream(in);
445             verifyParcelUnparcel(bitmapIn, colorSpace);
446         } catch (IOException e) {
447             fail();
448         }
449     }
450 
verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected)451     private void verifyParcelUnparcel(Bitmap bitmapIn, ColorSpace expected) {
452         ColorSpace cs = bitmapIn.getColorSpace();
453         assertNotNull(cs);
454         assertSame(expected, cs);
455 
456         Parcel p = Parcel.obtain();
457         bitmapIn.writeToParcel(p, 0);
458         p.setDataPosition(0);
459 
460         Bitmap bitmapOut = Bitmap.CREATOR.createFromParcel(p);
461         cs = bitmapOut.getColorSpace();
462         assertNotNull(cs);
463         assertSame(expected, cs);
464 
465         p.recycle();
466     }
467 
468     @Test
p3rgb565()469     public void p3rgb565() {
470         BitmapFactory.Options opts = new BitmapFactory.Options();
471         opts.inPreferredConfig = Bitmap.Config.RGB_565;
472 
473         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
474             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
475             ColorSpace cs = b.getColorSpace();
476             assertNotNull(cs);
477             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
478         } catch (IOException e) {
479             fail();
480         }
481     }
482 
483     @Test
p3hardware()484     public void p3hardware() {
485         BitmapFactory.Options opts = new BitmapFactory.Options();
486         opts.inPreferredConfig = Bitmap.Config.HARDWARE;
487 
488         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
489             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
490             ColorSpace cs = b.getColorSpace();
491             assertNotNull(cs);
492             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
493         } catch (IOException e) {
494             fail();
495         }
496     }
497 
498     @Test
guessSRGB()499     public void guessSRGB() {
500         BitmapFactory.Options opts = new BitmapFactory.Options();
501         opts.inJustDecodeBounds = true;
502 
503         try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
504             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
505             ColorSpace cs = opts.outColorSpace;
506             assertNull(b);
507             assertNotNull(cs);
508             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
509         } catch (IOException e) {
510             fail();
511         }
512     }
513 
514     @Test
guessProPhotoRGB()515     public void guessProPhotoRGB() {
516         BitmapFactory.Options opts = new BitmapFactory.Options();
517         opts.inJustDecodeBounds = true;
518 
519         try (InputStream in = mResources.getAssets().open("prophoto-rgba16f.png")) {
520             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
521             ColorSpace cs = opts.outColorSpace;
522             assertNull(b);
523             assertNotNull(cs);
524             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
525         } catch (IOException e) {
526             fail();
527         }
528     }
529 
530     @Test
guessP3()531     public void guessP3() {
532         BitmapFactory.Options opts = new BitmapFactory.Options();
533         opts.inJustDecodeBounds = true;
534 
535         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
536             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
537             ColorSpace cs = opts.outColorSpace;
538             assertNull(b);
539             assertNotNull(cs);
540             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
541         } catch (IOException e) {
542             fail();
543         }
544     }
545 
546     @Test
guessAdobeRGB()547     public void guessAdobeRGB() {
548         BitmapFactory.Options opts = new BitmapFactory.Options();
549         opts.inJustDecodeBounds = true;
550 
551         try (InputStream in = mResources.getAssets().open("red-adobergb.png")) {
552             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
553             ColorSpace cs = opts.outColorSpace;
554             assertNull(b);
555             assertNotNull(cs);
556             assertSame(ColorSpace.get(ColorSpace.Named.ADOBE_RGB), cs);
557         } catch (IOException e) {
558             fail();
559         }
560     }
561 
562     @Test
guessUnknown()563     public void guessUnknown() {
564         BitmapFactory.Options opts = new BitmapFactory.Options();
565         opts.inJustDecodeBounds = true;
566 
567         try (InputStream in = mResources.getAssets().open("purple-displayprofile.png")) {
568             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
569             ColorSpace cs = opts.outColorSpace;
570             assertNull(b);
571             assertNotNull(cs);
572             assertEquals("Unknown", cs.getName());
573         } catch (IOException e) {
574             fail();
575         }
576     }
577 
578     @Test
guessCMYK()579     public void guessCMYK() {
580         BitmapFactory.Options opts = new BitmapFactory.Options();
581         opts.inJustDecodeBounds = true;
582 
583         try (InputStream in = mResources.getAssets().open("purple-cmyk.png")) {
584             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
585             ColorSpace cs = opts.outColorSpace;
586             assertNull(b);
587             assertNotNull(cs);
588             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
589         } catch (IOException e) {
590             fail();
591         }
592     }
593 
594     @Test
inColorSpaceP3ToSRGB()595     public void inColorSpaceP3ToSRGB() {
596         BitmapFactory.Options opts = new BitmapFactory.Options();
597         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.SRGB);
598 
599         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
600             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
601             ColorSpace cs = b.getColorSpace();
602             assertNotNull(cs);
603             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
604             assertEquals(opts.inPreferredColorSpace, opts.outColorSpace);
605 
606             verifyGetPixel(b, 0x3ff00ff, 0xff00ff00);
607         } catch (IOException e) {
608             fail();
609         }
610     }
611 
612     @Test
inColorSpaceSRGBToP3()613     public void inColorSpaceSRGBToP3() {
614         BitmapFactory.Options opts = new BitmapFactory.Options();
615         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.DISPLAY_P3);
616 
617         try (InputStream in = mResources.getAssets().open("green-srgb.png")) {
618             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
619             ColorSpace cs = b.getColorSpace();
620             assertNotNull(cs);
621             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
622             assertEquals(opts.inPreferredColorSpace, opts.outColorSpace);
623 
624             verifyGetPixel(b, 0x75fb4cff, 0xff00ff00);
625         } catch (IOException e) {
626             fail();
627         }
628     }
629 
630     @Test
inColorSpaceRGBA16F()631     public void inColorSpaceRGBA16F() {
632         BitmapFactory.Options opts = new BitmapFactory.Options();
633         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
634 
635         try (InputStream in = mResources.getAssets().open("prophoto-rgba16f.png")) {
636             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
637             ColorSpace cs = b.getColorSpace();
638             assertNotNull(cs);
639             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
640             assertNotEquals(opts.inPreferredColorSpace, opts.outColorSpace);
641         } catch (IOException e) {
642             fail();
643         }
644     }
645 
646     @Test
inColorSpace565()647     public void inColorSpace565() {
648         BitmapFactory.Options opts = new BitmapFactory.Options();
649         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.ADOBE_RGB);
650         opts.inPreferredConfig = Bitmap.Config.RGB_565;
651 
652         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
653             Bitmap b = BitmapFactory.decodeStream(in, null, opts);
654             ColorSpace cs = b.getColorSpace();
655             assertNotNull(cs);
656             assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
657             assertNotEquals(opts.inPreferredColorSpace, opts.outColorSpace);
658         } catch (IOException e) {
659             fail();
660         }
661     }
662 
663     @Test(expected = IllegalArgumentException.class)
inColorSpaceNotRGB()664     public void inColorSpaceNotRGB() {
665         BitmapFactory.Options opts = new BitmapFactory.Options();
666         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.CIE_LAB);
667 
668         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
669             BitmapFactory.decodeStream(in, null, opts);
670         } catch (IOException e) {
671             fail();
672         }
673     }
674 
675     @Test(expected = IllegalArgumentException.class)
inColorSpaceNoTransferParameters()676     public void inColorSpaceNoTransferParameters() {
677         BitmapFactory.Options opts = new BitmapFactory.Options();
678         opts.inPreferredColorSpace = ColorSpace.get(ColorSpace.Named.EXTENDED_SRGB);
679 
680         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
681             BitmapFactory.decodeStream(in, null, opts);
682         } catch (IOException e) {
683             fail();
684         }
685     }
686 
687     @Test
copy()688     public void copy() {
689         Bitmap b = BitmapFactory.decodeResource(mResources, R.drawable.robot);
690         Bitmap c = b.copy(Bitmap.Config.ARGB_8888, false);
691         ColorSpace cs = c.getColorSpace();
692         assertNotNull(cs);
693         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
694 
695         c = b.copy(Bitmap.Config.ARGB_8888, true);
696         cs = c.getColorSpace();
697         assertNotNull(cs);
698         assertSame(ColorSpace.get(ColorSpace.Named.SRGB), cs);
699 
700         try (InputStream in = mResources.getAssets().open("green-p3.png")) {
701             b = BitmapFactory.decodeStream(in);
702             c = b.copy(Bitmap.Config.ARGB_8888, false);
703             cs = c.getColorSpace();
704             assertNotNull(cs);
705             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
706 
707             c = b.copy(Bitmap.Config.ARGB_8888, true);
708             cs = c.getColorSpace();
709             assertNotNull(cs);
710             assertSame(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), cs);
711         } catch (IOException e) {
712             fail();
713         }
714 
715         try (InputStream in = mResources.getAssets().open("prophoto-rgba16f.png")) {
716             b = BitmapFactory.decodeStream(in);
717             c = b.copy(Bitmap.Config.RGBA_F16, false);
718             cs = c.getColorSpace();
719             assertNotNull(cs);
720             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
721 
722             c = b.copy(Bitmap.Config.RGBA_F16, true);
723             cs = c.getColorSpace();
724             assertNotNull(cs);
725             assertSame(ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB), cs);
726         } catch (IOException e) {
727             fail();
728         }
729     }
730 
731     @SuppressWarnings("SameParameterValue")
almostEqual(@olorInt int expected, @ColorInt int pixel, int threshold, int index)732     private static void almostEqual(@ColorInt int expected,
733             @ColorInt int pixel, int threshold, int index) {
734         int diffA = Math.abs(expected >>> 24 - pixel >>> 24);
735         int diffR = Math.abs((expected >> 16) & 0xff - (pixel >> 16) & 0xff);
736         int diffG = Math.abs((expected >>  8) & 0xff - (pixel >>  8) & 0xff);
737         int diffB = Math.abs((expected      ) & 0xff - (pixel      ) & 0xff);
738 
739         boolean pass = diffA + diffR + diffG + diffB < threshold;
740         if (!pass) {
741             Log.d(LOG_TAG, "Expected 0x" + Integer.toHexString(expected) +
742                     " but was 0x" + Integer.toHexString(pixel) + " with index " + index);
743         }
744 
745         assertTrue(pass);
746     }
747 
748     @Test
749     public void testEncodeP3() {
750         Bitmap b = null;
751         ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(),
752                 "prophoto-rgba16f.png");
753         try {
754             b = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
755                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
756             });
757             assertNotNull(b);
758             assertEquals(Bitmap.Config.RGBA_F16, b.getConfig());
759         } catch (IOException e) {
760             fail("Failed with " + e);
761         }
762 
763         for (Bitmap.CompressFormat format : new Bitmap.CompressFormat[] {
764                 Bitmap.CompressFormat.JPEG,
765                 Bitmap.CompressFormat.WEBP,
766                 Bitmap.CompressFormat.PNG,
767         }) {
768             ByteArrayOutputStream out = new ByteArrayOutputStream();
769             assertTrue("Failed to encode F16 to " + format, b.compress(format, 100, out));
770 
771             byte[] array = out.toByteArray();
772             src = ImageDecoder.createSource(ByteBuffer.wrap(array));
773 
774             try {
775                 Bitmap b2 = ImageDecoder.decodeBitmap(src);
776                 assertEquals("Wrong color space for " + format,
777                         ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
778             } catch (IOException e) {
779                 fail("Failed with " + e);
780             }
781         }
782     }
783 
784     @Test
testEncodeP3hardware()785     public void testEncodeP3hardware() {
786         Bitmap b = null;
787         ImageDecoder.Source src = ImageDecoder.createSource(mResources.getAssets(),
788                 "green-p3.png");
789         try {
790             b = ImageDecoder.decodeBitmap(src, (decoder, info, s) -> {
791                 decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE);
792             });
793             assertNotNull(b);
794             assertEquals(Bitmap.Config.HARDWARE, b.getConfig());
795             assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b.getColorSpace());
796         } catch (IOException e) {
797             fail("Failed with " + e);
798         }
799 
800         for (Bitmap.CompressFormat format : new Bitmap.CompressFormat[] {
801                 Bitmap.CompressFormat.JPEG,
802                 Bitmap.CompressFormat.WEBP,
803                 Bitmap.CompressFormat.PNG,
804         }) {
805             ByteArrayOutputStream out = new ByteArrayOutputStream();
806             assertTrue("Failed to encode 8888 to " + format, b.compress(format, 100, out));
807 
808             byte[] array = out.toByteArray();
809             src = ImageDecoder.createSource(ByteBuffer.wrap(array));
810 
811             try {
812                 Bitmap b2 = ImageDecoder.decodeBitmap(src);
813                 assertEquals("Wrong color space for " + format,
814                         ColorSpace.get(ColorSpace.Named.DISPLAY_P3), b2.getColorSpace());
815             } catch (IOException e) {
816                 fail("Failed with " + e);
817             }
818         }
819     }
820 
821     @Test
822     @RequiresDevice // SwiftShader does not yet have support for F16 in HARDWARE b/75778024
test16bitHardware()823     public void test16bitHardware() {
824         // Decoding to HARDWARE may use LINEAR_EXTENDED_SRGB or SRGB, depending
825         // on whether F16 is supported in HARDWARE.
826         try (InputStream in = mResources.getAssets().open("prophoto-rgba16f.png")) {
827             BitmapFactory.Options options = new BitmapFactory.Options();
828             options.inPreferredConfig = Bitmap.Config.HARDWARE;
829             Bitmap b = BitmapFactory.decodeStream(in, null, options);
830             assertEquals(Bitmap.Config.HARDWARE, b.getConfig());
831 
832             final ColorSpace cs = b.getColorSpace();
833             if (cs != ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)
834                     && cs != ColorSpace.get(ColorSpace.Named.SRGB)) {
835                 fail("Unexpected color space " + cs);
836             }
837         } catch (Exception e) {
838             fail("Failed with " + e);
839         }
840     }
841 }
842