• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertNotSame;
24 import static org.junit.Assert.assertNull;
25 import static org.junit.Assert.assertSame;
26 import static org.junit.Assert.assertTrue;
27 import static org.junit.Assert.fail;
28 
29 import android.content.Context;
30 import android.graphics.Bitmap;
31 import android.graphics.Bitmap.CompressFormat;
32 import android.graphics.BitmapFactory;
33 import android.graphics.BitmapRegionDecoder;
34 import android.graphics.Canvas;
35 import android.graphics.Color;
36 import android.graphics.ColorSpace;
37 import android.graphics.Gainmap;
38 import android.graphics.ImageDecoder;
39 import android.graphics.Paint;
40 import android.graphics.Rect;
41 import android.hardware.HardwareBuffer;
42 import android.os.Parcel;
43 import android.platform.test.annotations.DisabledOnRavenwood;
44 import android.platform.test.annotations.RequiresFlagsEnabled;
45 import android.platform.test.flag.junit.CheckFlagsRule;
46 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
47 
48 import androidx.test.filters.SmallTest;
49 import androidx.test.platform.app.InstrumentationRegistry;
50 
51 import com.android.graphics.hwui.flags.Flags;
52 
53 import junitparams.JUnitParamsRunner;
54 import junitparams.Parameters;
55 
56 import org.junit.Assert;
57 import org.junit.BeforeClass;
58 import org.junit.Ignore;
59 import org.junit.Rule;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 import java.io.ByteArrayOutputStream;
64 import java.io.InputStream;
65 import java.util.function.Function;
66 
67 @SmallTest
68 @RunWith(JUnitParamsRunner.class)
69 @DisabledOnRavenwood(blockedBy = Gainmap.class)
70 public class GainmapTest {
71     private static final float EPSILON = 0.002f;
72     private static final int TILE_SIZE = 256;
73 
74     private static Context sContext;
75 
76     private static final ColorSpace BT2020_HLG = ColorSpace.get(ColorSpace.Named.BT2020_HLG);
77     private static final ColorSpace SRGB = ColorSpace.get(ColorSpace.Named.SRGB);
78 
79     static final Bitmap sScalingRedA8;
80     static final Bitmap sScalingRed8888;
81     static final Bitmap sScalingRedHLG8888;
82 
83     static final Bitmap sScalingWhite8888;
84 
85     static {
86         sScalingRedA8 = Bitmap.createBitmap(new int[] {
87                 Color.RED,
88                 Color.RED,
89                 Color.RED,
90                 Color.RED
91         }, 4, 1, Bitmap.Config.ARGB_8888);
sScalingRedA8.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] { 0x00000000, 0x40000000, 0x80000000, 0xFF000000 }, 4, 1, Bitmap.Config.ALPHA_8)))92         sScalingRedA8.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] {
93                 0x00000000,
94                 0x40000000,
95                 0x80000000,
96                 0xFF000000
97         }, 4, 1, Bitmap.Config.ALPHA_8)));
98 
99         sScalingRed8888 = Bitmap.createBitmap(new int[] {
100                 Color.RED,
101                 Color.RED,
102                 Color.RED,
103                 Color.RED
104         }, 4, 1, Bitmap.Config.ARGB_8888);
sScalingRed8888.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] { 0xFF000000, 0xFF404040, 0xFF808080, 0xFFFFFFFF }, 4, 1, Bitmap.Config.ARGB_8888)))105         sScalingRed8888.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] {
106                 0xFF000000,
107                 0xFF404040,
108                 0xFF808080,
109                 0xFFFFFFFF
110         }, 4, 1, Bitmap.Config.ARGB_8888)));
111         sScalingRedHLG8888 = Bitmap.createBitmap(new int[] {
112                 Color.RED,
113                 Color.RED,
114                 Color.RED,
115                 Color.RED
116         }, 4, 1, Bitmap.Config.ARGB_8888);
117         sScalingRedHLG8888.setColorSpace(BT2020_HLG);
sScalingRedHLG8888.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] { 0xFF000000, 0xFF404040, 0xFF808080, 0xFFFFFFFF }, 4, 1, Bitmap.Config.ARGB_8888)))118         sScalingRedHLG8888.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] {
119                 0xFF000000,
120                 0xFF404040,
121                 0xFF808080,
122                 0xFFFFFFFF
123         }, 4, 1, Bitmap.Config.ARGB_8888)));
124         if (Flags.isoGainmapApis()) {
125             sScalingRedHLG8888.getGainmap()
126                     .setGainmapDirection(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR);
127             sScalingRedHLG8888.getGainmap().setAlternativeImagePrimaries(SRGB);
128         }
129         sScalingWhite8888 = Bitmap.createBitmap(24, 24, Bitmap.Config.ARGB_8888);
130         Paint paint = new Paint();
131         paint.setColor(Color.WHITE);
132         new Canvas(sScalingWhite8888).drawPaint(paint);
133         Bitmap scalingWhiteGainmap = Bitmap.createBitmap(6, 6, Bitmap.Config.ARGB_8888);
134         new Canvas(scalingWhiteGainmap).drawPaint(paint);
sScalingWhite8888.setGainmap(new Gainmap(scalingWhiteGainmap))135         sScalingWhite8888.setGainmap(new Gainmap(scalingWhiteGainmap));
136     }
137 
138     @Rule
139     public final CheckFlagsRule mCheckFlagsRule =
140             DeviceFlagsValueProvider.createCheckFlagsRule();
141 
142     @BeforeClass
setupClass()143     public static void setupClass() {
144         sContext = InstrumentationRegistry.getInstrumentation().getContext();
145     }
146 
assertAllAre(float expected, float[] value)147     private static void assertAllAre(float expected, float[] value) {
148         assertEquals(3, value.length);
149         for (int i = 0; i < value.length; i++) {
150             assertEquals("value[" + i + "] didn't match " + expected, expected, value[i], EPSILON);
151         }
152     }
153 
assertAre(float r, float g, float b, float[] value)154     private static void assertAre(float r, float g, float b, float[] value) {
155         assertEquals(3, value.length);
156         assertEquals(r, value[0], EPSILON);
157         assertEquals(g, value[1], EPSILON);
158         assertEquals(b, value[2], EPSILON);
159     }
160 
checkGainmap(Bitmap bitmap)161     private void checkGainmap(Bitmap bitmap) throws Exception {
162         assertNotNull(bitmap);
163         assertTrue("Missing gainmap", bitmap.hasGainmap());
164         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
165             assertEquals(HardwareBuffer.RGBA_8888, bitmap.getHardwareBuffer().getFormat());
166         } else {
167             assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
168         }
169         assertEquals(ColorSpace.Named.SRGB.ordinal(), bitmap.getColorSpace().getId());
170         Gainmap gainmap = bitmap.getGainmap();
171         assertNotNull(gainmap);
172         Bitmap gainmapData = gainmap.getGainmapContents();
173         assertNotNull(gainmapData);
174         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
175             assertEquals(HardwareBuffer.RGBA_8888, gainmapData.getHardwareBuffer().getFormat());
176         } else {
177             assertEquals(Bitmap.Config.ARGB_8888, gainmapData.getConfig());
178         }
179 
180         assertAllAre(0.f, gainmap.getEpsilonSdr());
181         assertAllAre(0.f, gainmap.getEpsilonHdr());
182         assertAllAre(1.f, gainmap.getGamma());
183         assertEquals(1.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
184 
185         assertAllAre(4f, gainmap.getRatioMax());
186         assertAllAre(1.0f, gainmap.getRatioMin());
187         assertEquals(5f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
188         if (Flags.isoGainmapApis()) {
189             assertNull(gainmap.getAlternativeImagePrimaries());
190             assertEquals(Gainmap.GAINMAP_DIRECTION_SDR_TO_HDR, gainmap.getGainmapDirection());
191         }
192     }
193 
checkFountainGainmap(Bitmap bitmap)194     private void checkFountainGainmap(Bitmap bitmap) throws Exception {
195         assertNotNull(bitmap);
196         assertTrue("Missing gainmap", bitmap.hasGainmap());
197         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
198             assertEquals(HardwareBuffer.RGBA_8888, bitmap.getHardwareBuffer().getFormat());
199         } else {
200             assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
201         }
202         assertEquals(ColorSpace.Named.SRGB.ordinal(), bitmap.getColorSpace().getId());
203         Gainmap gainmap = bitmap.getGainmap();
204         assertNotNull(gainmap);
205         Bitmap gainmapData = gainmap.getGainmapContents();
206         assertNotNull(gainmapData);
207         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
208             final int gainmapFormat = gainmapData.getHardwareBuffer().getFormat();
209             if (gainmapFormat != HardwareBuffer.RGBA_8888 && gainmapFormat != HardwareBuffer.R_8) {
210                 fail("Unexpected gainmap format " + gainmapFormat);
211             }
212         } else {
213             assertEquals(Bitmap.Config.ALPHA_8, gainmapData.getConfig());
214         }
215 
216         assertAllAre(0.f, gainmap.getEpsilonSdr());
217         assertAllAre(0.f, gainmap.getEpsilonHdr());
218         assertAllAre(1.f, gainmap.getGamma());
219         assertEquals(1.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
220 
221         assertAllAre(10.63548f, gainmap.getRatioMax());
222         assertAllAre(1.0f, gainmap.getRatioMin());
223         assertEquals(10.63548f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
224         if (Flags.isoGainmapApis()) {
225             assertNull(gainmap.getAlternativeImagePrimaries());
226             assertEquals(Gainmap.GAINMAP_DIRECTION_SDR_TO_HDR, gainmap.getGainmapDirection());
227         }
228     }
229 
checkInvalidGaimap(Bitmap bitmap)230     private void checkInvalidGaimap(Bitmap bitmap) throws Exception {
231         assertNotNull(bitmap);
232         assertFalse("Missing gainmap", bitmap.hasGainmap());
233     }
234 
checkIsoGainmap(Bitmap bitmap, boolean isPng)235     private void checkIsoGainmap(Bitmap bitmap, boolean isPng) throws Exception {
236         assertNotNull(bitmap);
237         assertTrue("Missing gainmap", bitmap.hasGainmap());
238         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
239             assertEquals(HardwareBuffer.RGBA_8888, bitmap.getHardwareBuffer().getFormat());
240         } else {
241             assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
242         }
243         assertEquals(ColorSpace.Named.DISPLAY_P3.ordinal(), bitmap.getColorSpace().getId());
244         Gainmap gainmap = bitmap.getGainmap();
245         assertNotNull(gainmap);
246         Bitmap gainmapData = gainmap.getGainmapContents();
247         assertNotNull(gainmapData);
248         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
249             final int gainmapFormat = gainmapData.getHardwareBuffer().getFormat();
250             if (gainmapFormat != HardwareBuffer.RGBA_8888 && gainmapFormat != HardwareBuffer.R_8) {
251                 fail("Unexpected gainmap format " + gainmapFormat);
252             }
253         } else {
254             assertEquals(Bitmap.Config.ALPHA_8, gainmapData.getConfig());
255         }
256 
257         assertArrayEquals("Unexpected min ratios",
258                 new float[]{25.f, 0.5f, 1.f}, gainmap.getRatioMin(), EPSILON);
259         assertArrayEquals("Unexpected max ratios",
260                 new float[]{2.f, 4.f, 8.f}, gainmap.getRatioMax(), EPSILON);
261         assertArrayEquals("Unexpected gammas",
262                 new float[]{0.5f, 1.f, 2.f}, gainmap.getGamma(), EPSILON);
263         assertArrayEquals("Unexpected epsilon SDRs",
264                 new float[]{0.01f, 0.001f, 0.0001f}, gainmap.getEpsilonSdr(), EPSILON);
265         assertArrayEquals("Unexpected epsilon HDRs",
266                 new float[]{0.0001f, 0.001f, 0.01f}, gainmap.getEpsilonHdr(), EPSILON);
267         assertEquals(2.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
268         assertEquals(4.f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
269         if (Flags.isoGainmapApis()) {
270             if (isPng) {
271                 // PNG alpha8 or gray gainmaps don't support alternative image primaries
272                 assertNull(gainmap.getAlternativeImagePrimaries());
273             } else {
274                 if (com.android.graphics.flags.Flags.displayBt2020Colorspace()) {
275                 ColorSpace.Rgb displayBt2020 =
276                         (ColorSpace.Rgb) ColorSpace.get(ColorSpace.Named.DISPLAY_BT2020);
277                 // We only care about the primaries
278                 assertArrayEquals(displayBt2020.getPrimaries(),
279                         ((ColorSpace.Rgb) gainmap.getAlternativeImagePrimaries()).getPrimaries(),
280                         EPSILON);
281                 }
282                 assertEquals(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR, gainmap.getGainmapDirection());
283             }
284 
285         }
286 
287     }
288 
289     interface DecoderVariation {
decode(int id)290         Bitmap decode(int id) throws Exception;
291     }
292 
getGainmapDecodeVariations()293     static DecoderVariation[] getGainmapDecodeVariations() {
294         final BitmapFactory.Options hardwareOptions = new BitmapFactory.Options();
295         hardwareOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
296         DecoderVariation[] callables = new DecoderVariation[] {
297                 (id) -> ImageDecoder.decodeBitmap(
298                         ImageDecoder.createSource(sContext.getResources(), id),
299                         (decoder, info, source) -> decoder.setAllocator(
300                                 ImageDecoder.ALLOCATOR_SOFTWARE)),
301 
302                 (id) -> ImageDecoder.decodeBitmap(
303                         ImageDecoder.createSource(sContext.getResources(), id)),
304 
305                 (id) -> ImageDecoder.decodeBitmap(
306                         ImageDecoder.createSource(sContext.getResources(), id),
307                         (decoder, info, source) -> decoder.setTargetSampleSize(2)),
308 
309                 (id) -> BitmapFactory.decodeResource(sContext.getResources(), id),
310 
311                 (id) -> BitmapFactory.decodeResource(sContext.getResources(), id,
312                         hardwareOptions),
313         };
314         return callables;
315     }
316 
getCompressFormats()317     static CompressFormat[] getCompressFormats() {
318         return new CompressFormat[] {
319                 CompressFormat.JPEG,
320                 CompressFormat.PNG,
321         };
322     }
323 
324     @Test
325     @Parameters(method = "getGainmapDecodeVariations")
testDecodeGainmap(DecoderVariation provider)326     public void testDecodeGainmap(DecoderVariation provider) throws Exception {
327         checkGainmap(provider.decode(R.raw.gainmap));
328     }
329 
330     @Test
331     @Parameters(method = "getGainmapDecodeVariations")
testDecodeFountainGainmap(DecoderVariation provider)332     public void testDecodeFountainGainmap(DecoderVariation provider) throws Exception {
333         checkFountainGainmap(provider.decode(R.raw.fountain_night));
334     }
335 
336     @Test
337     @Parameters(method = "getGainmapDecodeVariations")
testDecodeIsoJpegGainmap(DecoderVariation provider)338     public void testDecodeIsoJpegGainmap(DecoderVariation provider) throws Exception {
339         checkIsoGainmap(provider.decode(R.raw.gainmap_iso21496_1), false);
340     }
341 
342     @Test
343     @Parameters(method = "getGainmapDecodeVariations")
testDecodeIsoPngGainmap(DecoderVariation provider)344     public void testDecodeIsoPngGainmap(DecoderVariation provider) throws Exception {
345         checkIsoGainmap(provider.decode(R.raw.png_gainmap), true);
346     }
347 
348     @Test
349     @Parameters(method = "getGainmapDecodeVariations")
testDecodeInvalidPngGainmaps(DecoderVariation provider)350     public void testDecodeInvalidPngGainmaps(DecoderVariation provider) throws Exception {
351         checkInvalidGaimap(provider.decode(R.raw.gainmap_no_gdat));
352         checkInvalidGaimap(provider.decode(R.raw.gainmap_gdat_no_gmap));
353     }
354 
355     @Test
testDecodeGainmapBitmapFactoryReuse()356     public void testDecodeGainmapBitmapFactoryReuse() throws Exception {
357         BitmapFactory.Options options = new BitmapFactory.Options();
358         options.inMutable = true;
359         options.inDensity = 160;
360         options.inTargetDensity = 160;
361 
362         Bitmap bitmap = BitmapFactory.decodeResource(sContext.getResources(), R.raw.gainmap,
363                 options);
364         checkGainmap(bitmap);
365         options.inBitmap = bitmap;
366         assertSame(bitmap, BitmapFactory.decodeResource(
367                 sContext.getResources(), R.drawable.baseline_jpeg, options));
368         assertEquals(1280, bitmap.getWidth());
369         assertEquals(960, bitmap.getHeight());
370         assertFalse(bitmap.hasGainmap());
371         assertNull(bitmap.getGainmap());
372         assertSame(bitmap, BitmapFactory.decodeResource(
373                 sContext.getResources(), R.raw.gainmap, options));
374         checkGainmap(bitmap);
375     }
376 
377     @Test
testDecodeGainmapBitmapRegionDecoder()378     public void testDecodeGainmapBitmapRegionDecoder() throws Exception {
379         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
380         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
381         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), null);
382         checkGainmap(region);
383     }
384 
385     @Test
testDecodeGainmapBitmapRegionDecoderReuse()386     public void testDecodeGainmapBitmapRegionDecoderReuse() throws Exception {
387         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
388         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
389         BitmapFactory.Options options = new BitmapFactory.Options();
390         options.inMutable = true;
391         options.inDensity = 160;
392         options.inTargetDensity = 160;
393         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
394                 options);
395         checkGainmap(region);
396         Bitmap previousGainmap = region.getGainmap().getGainmapContents();
397         options.inBitmap = region;
398 
399         is = sContext.getResources().openRawResource(R.drawable.baseline_jpeg);
400         BitmapRegionDecoder secondDecoder = BitmapRegionDecoder.newInstance(is);
401         assertSame(region, secondDecoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
402                 options));
403         assertFalse(region.hasGainmap());
404         assertNull(region.getGainmap());
405 
406         assertSame(region, decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
407                 options));
408         checkGainmap(region);
409         assertNotSame(previousGainmap, region.getGainmap().getGainmapContents());
410     }
411 
412     @Test
testDecodeGainmapBitmapRegionDecoderReusePastBounds()413     public void testDecodeGainmapBitmapRegionDecoderReusePastBounds() throws Exception {
414         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
415         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
416         BitmapFactory.Options options = new BitmapFactory.Options();
417         options.inMutable = true;
418         options.inDensity = 160;
419         options.inTargetDensity = 160;
420         int offsetX = decoder.getWidth() - (TILE_SIZE / 2);
421         int offsetY = decoder.getHeight() - (TILE_SIZE / 4);
422         Bitmap region = decoder.decodeRegion(new Rect(offsetX, offsetY, offsetX + TILE_SIZE,
423                         offsetY + TILE_SIZE), options);
424         checkGainmap(region);
425         Bitmap gainmap = region.getGainmap().getGainmapContents();
426         // Since there's no re-use bitmap, the resulting bitmap size will be the size of the rect
427         // that overlaps with the image. 1/2 of the X and 3/4ths of the Y are out of bounds
428         assertEquals(TILE_SIZE / 2, region.getWidth());
429         assertEquals(TILE_SIZE / 4, region.getHeight());
430         // The test image has a 1:1 ratio between base & gainmap
431         assertEquals(region.getWidth(), gainmap.getWidth());
432         assertEquals(region.getHeight(), gainmap.getHeight());
433 
434         options.inBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
435         region = decoder.decodeRegion(new Rect(offsetX, offsetY, offsetX + TILE_SIZE,
436                 offsetY + TILE_SIZE), options);
437         gainmap = region.getGainmap().getGainmapContents();
438         // Although 1/2 the X and 3/4ths the Y are out of bounds, because there's a re-use
439         // bitmap the resulting decode must exactly match the size given
440         assertEquals(TILE_SIZE, region.getWidth());
441         assertEquals(TILE_SIZE, region.getHeight());
442         // The test image has a 1:1 ratio between base & gainmap
443         assertEquals(region.getWidth(), gainmap.getWidth());
444         assertEquals(region.getHeight(), gainmap.getHeight());
445     }
446 
447     @Test
testDecodeGainmapBitmapRegionDecoderReuseCropped()448     public void testDecodeGainmapBitmapRegionDecoderReuseCropped() throws Exception {
449         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
450         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
451         BitmapFactory.Options options = new BitmapFactory.Options();
452         options.inMutable = true;
453         options.inDensity = 160;
454         options.inTargetDensity = 160;
455         options.inBitmap = Bitmap.createBitmap(TILE_SIZE / 2, TILE_SIZE / 2,
456                 Bitmap.Config.ARGB_8888);
457         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
458                 options);
459         checkGainmap(region);
460         Bitmap gainmap = region.getGainmap().getGainmapContents();
461         // Although the rect was entirely in-bounds of the image, the inBitmap is 1/2th the
462         // the specified width/height so make sure the gainmap matches
463         assertEquals(TILE_SIZE / 2, region.getWidth());
464         assertEquals(TILE_SIZE / 2, region.getHeight());
465         // The test image has a 1:1 ratio between base & gainmap
466         assertEquals(region.getWidth(), gainmap.getWidth());
467         assertEquals(region.getHeight(), gainmap.getHeight());
468     }
469 
470     @Test
testDecodeGainmapBitmapRegionDecoderWithInSampleSize()471     public void testDecodeGainmapBitmapRegionDecoderWithInSampleSize() throws Exception {
472         // Use a quite generous threshold because we're dealing with lossy jpeg. This is still
473         // plenty sufficient to catch the difference between RED and GREEN without any risk
474         // of flaking on compression artifacts
475         final int threshold = 20;
476 
477         InputStream is = sContext.getResources().openRawResource(R.raw.grid_gainmap);
478         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
479         BitmapFactory.Options options = new BitmapFactory.Options();
480         options.inMutable = true;
481         options.inDensity = 160;
482         options.inTargetDensity = 160;
483         options.inSampleSize = 4;
484 
485         // The test image is a 1024x1024 grid of 4 colors each 512x512
486         // with a gainmap that's 512x512 grid of 4 colors each 256x256
487         // RED  | GREEN
488         // BLUE | BLACK
489         // So by decoding the center 512x512 of the image we should still get the same set of
490         // 4 colors in the output
491         Rect subset = new Rect(256, 256, 768, 768);
492         Bitmap region = decoder.decodeRegion(subset, options);
493         assertTrue(region.hasGainmap());
494         Bitmap gainmap = region.getGainmap().getGainmapContents();
495 
496         // sampleSize = 4 means we expect an output scaled by 1/4th
497         assertEquals(128, region.getWidth());
498         assertEquals(128, region.getHeight());
499         assertEquals(64, gainmap.getWidth());
500         assertEquals(64, gainmap.getHeight());
501 
502         assertBitmapQuadColor(region, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, threshold);
503         assertBitmapQuadColor(gainmap, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, threshold);
504     }
505 
506     @RequiresFlagsEnabled(Flags.FLAG_RESAMPLE_GAINMAP_REGIONS)
507     @Test
testDecodeGainmapBitmapRegionDecoderWithInSampleSizeDoesNotInset()508     public void testDecodeGainmapBitmapRegionDecoderWithInSampleSizeDoesNotInset()
509             throws Exception {
510         ByteArrayOutputStream stream = new ByteArrayOutputStream();
511         assertTrue(sScalingWhite8888.compress(Bitmap.CompressFormat.JPEG, 100, stream));
512         byte[] data = stream.toByteArray();
513         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(data, 0, data.length);
514         BitmapFactory.Options options = new BitmapFactory.Options();
515         options.inSampleSize = 2;
516         Bitmap region = decoder.decodeRegion(new Rect(0, 0, 18, 18), options);
517         assertTrue(region.hasGainmap());
518         Bitmap gainmapImage = region.getGainmap().getGainmapContents();
519         assertEquals(Bitmap.Config.ARGB_8888, gainmapImage.getConfig());
520         Color expectedColor = Color.valueOf(Color.WHITE);
521         for (int x = 0; x < gainmapImage.getWidth(); x++) {
522             for (int y = 0; y < gainmapImage.getHeight(); y++) {
523                 Color got = gainmapImage.getColor(x, y);
524                 assertArrayEquals("Differed at x=" + x + ", y=" + y,
525                         expectedColor.getComponents(), got.getComponents(), 0.05f);
526             }
527         }
528     }
529 
530     @Test
testDefaults()531     public void testDefaults() {
532         Gainmap gainmap = new Gainmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8));
533         assertAllAre(1.0f, gainmap.getRatioMin());
534         assertAllAre(2.f, gainmap.getRatioMax());
535         assertAllAre(1.f, gainmap.getGamma());
536         assertAllAre(0.f, gainmap.getEpsilonSdr());
537         assertAllAre(0.f, gainmap.getEpsilonHdr());
538         assertEquals(1.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
539         assertEquals(2.f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
540         if (Flags.isoGainmapApis()) {
541             assertNull(gainmap.getAlternativeImagePrimaries());
542             assertEquals(Gainmap.GAINMAP_DIRECTION_SDR_TO_HDR, gainmap.getGainmapDirection());
543         }
544     }
545 
546     @Test
testSetGet()547     public void testSetGet() {
548         Gainmap gainmap = new Gainmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8));
549         gainmap.setDisplayRatioForFullHdr(5f);
550         gainmap.setMinDisplayRatioForHdrTransition(3f);
551         gainmap.setGamma(1.1f, 1.2f, 1.3f);
552         gainmap.setRatioMin(2.1f, 2.2f, 2.3f);
553         gainmap.setRatioMax(3.1f, 3.2f, 3.3f);
554         gainmap.setEpsilonSdr(0.1f, 0.2f, 0.3f);
555         gainmap.setEpsilonHdr(0.01f, 0.02f, 0.03f);
556 
557         if (Flags.isoGainmapApis()) {
558             gainmap.setAlternativeImagePrimaries(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
559             gainmap.setGainmapDirection(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR);
560         }
561 
562         assertEquals(5f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
563         assertEquals(3f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
564         assertAre(1.1f, 1.2f, 1.3f, gainmap.getGamma());
565         assertAre(2.1f, 2.2f, 2.3f, gainmap.getRatioMin());
566         assertAre(3.1f, 3.2f, 3.3f, gainmap.getRatioMax());
567         assertAre(0.1f, 0.2f, 0.3f, gainmap.getEpsilonSdr());
568         assertAre(0.01f, 0.02f, 0.03f, gainmap.getEpsilonHdr());
569         if (Flags.isoGainmapApis()) {
570             assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3),
571                     gainmap.getAlternativeImagePrimaries());
572             assertEquals(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR, gainmap.getGainmapDirection());
573         }
574     }
575 
576     @Test
testCopyInfo()577     public void testCopyInfo() {
578         Gainmap original = new Gainmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8));
579         original.setDisplayRatioForFullHdr(5f);
580         original.setMinDisplayRatioForHdrTransition(3f);
581         original.setGamma(1.1f, 1.2f, 1.3f);
582         original.setRatioMin(2.1f, 2.2f, 2.3f);
583         original.setRatioMax(3.1f, 3.2f, 3.3f);
584         original.setEpsilonSdr(0.1f, 0.2f, 0.3f);
585         original.setEpsilonHdr(0.01f, 0.02f, 0.03f);
586         if (Flags.isoGainmapApis()) {
587             original.setAlternativeImagePrimaries(ColorSpace.get(ColorSpace.Named.DISPLAY_P3));
588             original.setGainmapDirection(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR);
589         }
590 
591         Gainmap copy = new Gainmap(original, Bitmap.createBitmap(5, 5, Bitmap.Config.ALPHA_8));
592         assertEquals(5f, copy.getDisplayRatioForFullHdr(), EPSILON);
593         assertEquals(3f, copy.getMinDisplayRatioForHdrTransition(), EPSILON);
594         assertAre(1.1f, 1.2f, 1.3f, copy.getGamma());
595         assertAre(2.1f, 2.2f, 2.3f, copy.getRatioMin());
596         assertAre(3.1f, 3.2f, 3.3f, copy.getRatioMax());
597         assertAre(0.1f, 0.2f, 0.3f, copy.getEpsilonSdr());
598         assertAre(0.01f, 0.02f, 0.03f, copy.getEpsilonHdr());
599         if (Flags.isoGainmapApis()) {
600             assertEquals(ColorSpace.get(ColorSpace.Named.DISPLAY_P3),
601                     copy.getAlternativeImagePrimaries());
602             assertEquals(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR, copy.getGainmapDirection());
603         }
604 
605         assertEquals(10, original.getGainmapContents().getWidth());
606         assertEquals(5, copy.getGainmapContents().getWidth());
607     }
608 
609     @Test
testWriteToParcel()610     public void testWriteToParcel() throws Exception {
611         Bitmap bitmap = ImageDecoder.decodeBitmap(
612                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
613                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
614         assertNotNull(bitmap);
615 
616         Gainmap gainmap = bitmap.getGainmap();
617         assertNotNull(gainmap);
618         Bitmap gainmapData = gainmap.getGainmapContents();
619         assertNotNull(gainmapData);
620 
621         Parcel p = Parcel.obtain();
622         gainmap.writeToParcel(p, 0);
623         p.setDataPosition(0);
624 
625         Gainmap unparceledGainmap = Gainmap.CREATOR.createFromParcel(p);
626         assertNotNull(unparceledGainmap);
627         Bitmap unparceledGainmapData = unparceledGainmap.getGainmapContents();
628         assertNotNull(unparceledGainmapData);
629 
630         assertTrue(gainmapData.sameAs(unparceledGainmapData));
631         assertEquals(gainmapData.getConfig(), unparceledGainmapData.getConfig());
632         assertEquals(gainmapData.getColorSpace(), unparceledGainmapData.getColorSpace());
633 
634         assertArrayEquals(gainmap.getEpsilonSdr(), unparceledGainmap.getEpsilonSdr(), 0f);
635         assertArrayEquals(gainmap.getEpsilonHdr(), unparceledGainmap.getEpsilonHdr(), 0f);
636         assertArrayEquals(gainmap.getGamma(), unparceledGainmap.getGamma(), 0f);
637         assertEquals(gainmap.getMinDisplayRatioForHdrTransition(),
638                 unparceledGainmap.getMinDisplayRatioForHdrTransition(), 0f);
639 
640         assertArrayEquals(gainmap.getRatioMax(), unparceledGainmap.getRatioMax(), 0f);
641         assertArrayEquals(gainmap.getRatioMin(), unparceledGainmap.getRatioMin(), 0f);
642         assertEquals(gainmap.getDisplayRatioForFullHdr(),
643                 unparceledGainmap.getDisplayRatioForFullHdr(), 0f);
644         if (Flags.isoGainmapApis()) {
645             assertEquals(gainmap.getAlternativeImagePrimaries(),
646                     unparceledGainmap.getAlternativeImagePrimaries());
647             assertEquals(gainmap.getGainmapDirection(), unparceledGainmap.getGainmapDirection());
648         }
649         p.recycle();
650     }
651 
652     @Test
testWriteToParcelInSharedBitmap()653     public void testWriteToParcelInSharedBitmap() throws Exception {
654         Bitmap bitmap = ImageDecoder.decodeBitmap(
655                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
656                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
657         assertNotNull(bitmap);
658 
659         Parcel p = Parcel.obtain();
660         bitmap.asShared().writeToParcel(p, 0);
661         p.setDataPosition(0);
662 
663         Bitmap unparceledBitmap = Bitmap.CREATOR.createFromParcel(p);
664         assertTrue(unparceledBitmap.hasGainmap());
665 
666         final Bitmap expectedContents = bitmap.getGainmap().getGainmapContents();
667         final Bitmap gotContents = unparceledBitmap.getGainmap().getGainmapContents();
668 
669         assertEquals(expectedContents.getWidth(), gotContents.getWidth());
670         assertEquals(expectedContents.getHeight(), gotContents.getHeight());
671         for (int x = 0; x < 4; x++) {
672             for (int y = 0; y < 4; y++) {
673                 Color expected = expectedContents.getColor(x, y);
674                 Color got = gotContents.getColor(x, y);
675                 assertArrayEquals("Differed at x=" + x + ", y=" + y,
676                         expected.getComponents(), got.getComponents(), 0.05f);
677             }
678         }
679     }
680 
681     @Parameters(method = "getCompressFormats")
682     @Test
testCompress8888(CompressFormat format)683     public void testCompress8888(CompressFormat format) throws Exception {
684         ByteArrayOutputStream stream = new ByteArrayOutputStream();
685         assertTrue(sScalingRed8888.compress(format, 100, stream));
686         byte[] data = stream.toByteArray();
687         Bitmap result = ImageDecoder.decodeBitmap(
688                 ImageDecoder.createSource(data), (decoder, info, src) -> {
689                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
690             });
691         assertTrue(result.hasGainmap());
692         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
693         assertEquals(Bitmap.Config.ARGB_8888, gainmapImage.getConfig());
694         Bitmap sourceImage = sScalingRed8888.getGainmap().getGainmapContents();
695         for (int x = 0; x < 4; x++) {
696             Color expected = sourceImage.getColor(x, 0);
697             Color got = gainmapImage.getColor(x, 0);
698             assertArrayEquals("Differed at x=" + x,
699                     expected.getComponents(), got.getComponents(), 0.05f);
700         }
701     }
702 
703     @RequiresFlagsEnabled(Flags.FLAG_ISO_GAINMAP_APIS)
704     @Parameters(method = "getCompressFormats")
705     @Test
testISOCompress8888(CompressFormat format)706     public void testISOCompress8888(CompressFormat format) throws Exception {
707         ByteArrayOutputStream stream = new ByteArrayOutputStream();
708         assertTrue(sScalingRedHLG8888.compress(format, 100, stream));
709         byte[] data = stream.toByteArray();
710         Bitmap result = ImageDecoder.decodeBitmap(
711                 ImageDecoder.createSource(data), (decoder, info, src) -> {
712                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
713             });
714         assertTrue(result.hasGainmap());
715         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
716         assertEquals(Bitmap.Config.ARGB_8888, gainmapImage.getConfig());
717         assertEquals(SRGB, result.getGainmap().getAlternativeImagePrimaries());
718         assertEquals(Gainmap.GAINMAP_DIRECTION_HDR_TO_SDR,
719                 result.getGainmap().getGainmapDirection());
720         Bitmap sourceImage = sScalingRed8888.getGainmap().getGainmapContents();
721         for (int x = 0; x < 4; x++) {
722             Color expected = sourceImage.getColor(x, 0);
723             Color got = gainmapImage.getColor(x, 0);
724             assertArrayEquals("Differed at x=" + x,
725                     expected.getComponents(), got.getComponents(), 0.05f);
726         }
727     }
728 
729     @Parameters(method = "getCompressFormats")
730     @Test
testCompressA8ByImageDecoder(CompressFormat format)731     public void testCompressA8ByImageDecoder(CompressFormat format) throws Exception {
732         ByteArrayOutputStream stream = new ByteArrayOutputStream();
733         assertTrue(sScalingRedA8.compress(format, 100, stream));
734         byte[] data = stream.toByteArray();
735         Bitmap result = ImageDecoder.decodeBitmap(
736                 ImageDecoder.createSource(data), (decoder, info, src) -> {
737                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
738             });
739         assertTrue(result.hasGainmap());
740         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
741         assertEquals(Bitmap.Config.ALPHA_8, gainmapImage.getConfig());
742         Bitmap sourceImage = sScalingRedA8.getGainmap().getGainmapContents();
743         for (int x = 0; x < 4; x++) {
744             Color expected = sourceImage.getColor(x, 0);
745             Color got = gainmapImage.getColor(x, 0);
746             assertArrayEquals("Differed at x=" + x,
747                     expected.getComponents(), got.getComponents(), 0.05f);
748         }
749     }
750 
751     @Parameters(method = "getCompressFormats")
752     @Test
753     @Ignore("Skip it until BitmapRegionDecoder have Alpha8 gainmap support")
testCompressA8ByBitmapRegionDecoder()754     public void testCompressA8ByBitmapRegionDecoder() throws Exception {
755         ByteArrayOutputStream stream = new ByteArrayOutputStream();
756         assertTrue(sScalingRedA8.compress(Bitmap.CompressFormat.JPEG, 100, stream));
757         byte[] data = stream.toByteArray();
758         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(data, 0, data.length);
759         Bitmap region = decoder.decodeRegion(new Rect(0, 0, 4, 1), null);
760         assertTrue(region.hasGainmap());
761         Bitmap gainmapImage = region.getGainmap().getGainmapContents();
762         assertEquals(Bitmap.Config.ALPHA_8, gainmapImage.getConfig());
763         Bitmap sourceImage = sScalingRedA8.getGainmap().getGainmapContents();
764         for (int x = 0; x < 4; x++) {
765             Color expected = sourceImage.getColor(x, 0);
766             Color got = gainmapImage.getColor(x, 0);
767             assertArrayEquals("Differed at x=" + x,
768                     expected.getComponents(), got.getComponents(), 0.05f);
769         }
770     }
771 
772     @Parameters(method = "getCompressFormats")
773     @Test
testCompressA8ByBitmapFactory(CompressFormat format)774     public void testCompressA8ByBitmapFactory(CompressFormat format) throws Exception {
775         ByteArrayOutputStream stream = new ByteArrayOutputStream();
776         assertTrue(sScalingRedA8.compress(format, 100, stream));
777         byte[] data = stream.toByteArray();
778         Bitmap result = BitmapFactory.decodeByteArray(data, 0, data.length);
779         assertTrue(result.hasGainmap());
780         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
781         assertEquals(Bitmap.Config.ALPHA_8, gainmapImage.getConfig());
782         Bitmap sourceImage = sScalingRedA8.getGainmap().getGainmapContents();
783         for (int x = 0; x < 4; x++) {
784             Color expected = sourceImage.getColor(x, 0);
785             Color got = gainmapImage.getColor(x, 0);
786             assertArrayEquals("Differed at x=" + x,
787                     expected.getComponents(), got.getComponents(), 0.05f);
788         }
789     }
790 
791     @Test
testHardwareGainmapCopy()792     public void testHardwareGainmapCopy() throws Exception {
793         Bitmap bitmap = ImageDecoder.decodeBitmap(
794                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
795                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE));
796         assertNotNull(bitmap);
797         assertTrue("Missing gainmap", bitmap.hasGainmap());
798         assertEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
799 
800         Gainmap gainmap = bitmap.getGainmap();
801         assertNotNull(gainmap);
802         Bitmap gainmapData = gainmap.getGainmapContents();
803         assertNotNull(gainmapData);
804         assertEquals(Bitmap.Config.HARDWARE, gainmapData.getConfig());
805     }
806 
807     @Test
testCopyPreservesGainmap()808     public void testCopyPreservesGainmap() throws Exception {
809         Bitmap bitmap = ImageDecoder.decodeBitmap(
810                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
811                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
812         assertNotNull(bitmap);
813         assertTrue("Missing gainmap", bitmap.hasGainmap());
814 
815         Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
816         assertNotNull(copy);
817         assertTrue("Missing gainmap", copy.hasGainmap());
818     }
819 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight, int threshold)820     private static void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
821             int bottomLeft, int bottomRight, int threshold) {
822         Function<Float, Integer> getX = (Float x) -> (int) (bitmap.getWidth() * x);
823         Function<Float, Integer> getY = (Float y) -> (int) (bitmap.getHeight() * y);
824 
825         // Just quickly sample 4 pixels in the various regions.
826         assertBitmapColor("Top left", bitmap, topLeft,
827                 getX.apply(.25f), getY.apply(.25f), threshold);
828         assertBitmapColor("Top right", bitmap, topRight,
829                 getX.apply(.75f), getY.apply(.25f), threshold);
830         assertBitmapColor("Bottom left", bitmap, bottomLeft,
831                 getX.apply(.25f), getY.apply(.75f), threshold);
832         assertBitmapColor("Bottom right", bitmap, bottomRight,
833                 getX.apply(.75f), getY.apply(.75f), threshold);
834 
835         float below = .4f;
836         float above = .6f;
837         assertBitmapColor("Top left II", bitmap, topLeft,
838                 getX.apply(below), getY.apply(below), threshold);
839         assertBitmapColor("Top right II", bitmap, topRight,
840                 getX.apply(above), getY.apply(below), threshold);
841         assertBitmapColor("Bottom left II", bitmap, bottomLeft,
842                 getX.apply(below), getY.apply(above), threshold);
843         assertBitmapColor("Bottom right II", bitmap, bottomRight,
844                 getX.apply(above), getY.apply(above), threshold);
845     }
846 
pixelsAreSame(int ideal, int given, int threshold)847     private static boolean pixelsAreSame(int ideal, int given, int threshold) {
848         int error = Math.abs(Color.red(ideal) - Color.red(given));
849         error += Math.abs(Color.green(ideal) - Color.green(given));
850         error += Math.abs(Color.blue(ideal) - Color.blue(given));
851         return (error < threshold);
852     }
853 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y, int threshold)854     private static void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y,
855             int threshold) {
856         int pixel = bitmap.getPixel(x, y);
857         if (!pixelsAreSame(color, pixel, threshold)) {
858             Assert.fail(debug + "; expected=" + Integer.toHexString(color) + ", actual="
859                     + Integer.toHexString(pixel));
860         }
861     }
862 
863 }
864