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