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.BitmapFactory;
32 import android.graphics.BitmapRegionDecoder;
33 import android.graphics.Color;
34 import android.graphics.ColorSpace;
35 import android.graphics.Gainmap;
36 import android.graphics.ImageDecoder;
37 import android.graphics.Rect;
38 import android.hardware.HardwareBuffer;
39 import android.os.Parcel;
40 
41 import androidx.test.filters.SmallTest;
42 import androidx.test.platform.app.InstrumentationRegistry;
43 
44 import junitparams.JUnitParamsRunner;
45 import junitparams.Parameters;
46 
47 import org.junit.Assert;
48 import org.junit.BeforeClass;
49 import org.junit.Ignore;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 
53 import java.io.ByteArrayOutputStream;
54 import java.io.InputStream;
55 import java.util.function.Function;
56 
57 @SmallTest
58 @RunWith(JUnitParamsRunner.class)
59 public class GainmapTest {
60     private static final float EPSILON = 0.0001f;
61     private static final int TILE_SIZE = 256;
62 
63     private static Context sContext;
64 
65     static final Bitmap sScalingRedA8;
66     static final Bitmap sScalingRed8888;
67 
68     static {
69         sScalingRedA8 = Bitmap.createBitmap(new int[] {
70                 Color.RED,
71                 Color.RED,
72                 Color.RED,
73                 Color.RED
74         }, 4, 1, Bitmap.Config.ARGB_8888);
sScalingRedA8.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] { 0x00000000, 0x40000000, 0x80000000, 0xFF000000 }, 4, 1, Bitmap.Config.ALPHA_8)))75         sScalingRedA8.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] {
76                 0x00000000,
77                 0x40000000,
78                 0x80000000,
79                 0xFF000000
80         }, 4, 1, Bitmap.Config.ALPHA_8)));
81 
82         sScalingRed8888 = Bitmap.createBitmap(new int[] {
83                 Color.RED,
84                 Color.RED,
85                 Color.RED,
86                 Color.RED
87         }, 4, 1, Bitmap.Config.ARGB_8888);
sScalingRed8888.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] { 0xFF000000, 0xFF404040, 0xFF808080, 0xFFFFFFFF }, 4, 1, Bitmap.Config.ARGB_8888)))88         sScalingRed8888.setGainmap(new Gainmap(Bitmap.createBitmap(new int[] {
89                 0xFF000000,
90                 0xFF404040,
91                 0xFF808080,
92                 0xFFFFFFFF
93         }, 4, 1, Bitmap.Config.ARGB_8888)));
94     }
95 
96     @BeforeClass
setupClass()97     public static void setupClass() {
98         sContext = InstrumentationRegistry.getInstrumentation().getContext();
99     }
100 
assertAllAre(float expected, float[] value)101     private static void assertAllAre(float expected, float[] value) {
102         assertEquals(3, value.length);
103         for (int i = 0; i < value.length; i++) {
104             assertEquals("value[" + i + "] didn't match " + expected, expected, value[i], EPSILON);
105         }
106     }
107 
assertAre(float r, float g, float b, float[] value)108     private static void assertAre(float r, float g, float b, float[] value) {
109         assertEquals(3, value.length);
110         assertEquals(r, value[0], EPSILON);
111         assertEquals(g, value[1], EPSILON);
112         assertEquals(b, value[2], EPSILON);
113     }
114 
checkGainmap(Bitmap bitmap)115     private void checkGainmap(Bitmap bitmap) throws Exception {
116         assertNotNull(bitmap);
117         assertTrue("Missing gainmap", bitmap.hasGainmap());
118         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
119             assertEquals(HardwareBuffer.RGBA_8888, bitmap.getHardwareBuffer().getFormat());
120         } else {
121             assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
122         }
123         assertEquals(ColorSpace.Named.SRGB.ordinal(), bitmap.getColorSpace().getId());
124         Gainmap gainmap = bitmap.getGainmap();
125         assertNotNull(gainmap);
126         Bitmap gainmapData = gainmap.getGainmapContents();
127         assertNotNull(gainmapData);
128         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
129             assertEquals(HardwareBuffer.RGBA_8888, gainmapData.getHardwareBuffer().getFormat());
130         } else {
131             assertEquals(Bitmap.Config.ARGB_8888, gainmapData.getConfig());
132         }
133 
134         assertAllAre(0.f, gainmap.getEpsilonSdr());
135         assertAllAre(0.f, gainmap.getEpsilonHdr());
136         assertAllAre(1.f, gainmap.getGamma());
137         assertEquals(1.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
138 
139         assertAllAre(4f, gainmap.getRatioMax());
140         assertAllAre(1.0f, gainmap.getRatioMin());
141         assertEquals(5f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
142     }
143 
checkFountainGainmap(Bitmap bitmap)144     private void checkFountainGainmap(Bitmap bitmap) throws Exception {
145         assertNotNull(bitmap);
146         assertTrue("Missing gainmap", bitmap.hasGainmap());
147         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
148             assertEquals(HardwareBuffer.RGBA_8888, bitmap.getHardwareBuffer().getFormat());
149         } else {
150             assertEquals(Bitmap.Config.ARGB_8888, bitmap.getConfig());
151         }
152         assertEquals(ColorSpace.Named.SRGB.ordinal(), bitmap.getColorSpace().getId());
153         Gainmap gainmap = bitmap.getGainmap();
154         assertNotNull(gainmap);
155         Bitmap gainmapData = gainmap.getGainmapContents();
156         assertNotNull(gainmapData);
157         if (bitmap.getConfig() == Bitmap.Config.HARDWARE) {
158             final int gainmapFormat = gainmapData.getHardwareBuffer().getFormat();
159             if (gainmapFormat != HardwareBuffer.RGBA_8888 && gainmapFormat != HardwareBuffer.R_8) {
160                 fail("Unexpected gainmap format " + gainmapFormat);
161             }
162         } else {
163             assertEquals(Bitmap.Config.ALPHA_8, gainmapData.getConfig());
164         }
165 
166         assertAllAre(0.f, gainmap.getEpsilonSdr());
167         assertAllAre(0.f, gainmap.getEpsilonHdr());
168         assertAllAre(1.f, gainmap.getGamma());
169         assertEquals(1.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
170 
171         assertAllAre(10.63548f, gainmap.getRatioMax());
172         assertAllAre(1.0f, gainmap.getRatioMin());
173         assertEquals(10.63548f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
174     }
175 
176     interface DecoderVariation {
decode(int id)177         Bitmap decode(int id) throws Exception;
178     }
179 
getGainmapDecodeVariations()180     static DecoderVariation[] getGainmapDecodeVariations() {
181         final BitmapFactory.Options hardwareOptions = new BitmapFactory.Options();
182         hardwareOptions.inPreferredConfig = Bitmap.Config.HARDWARE;
183         DecoderVariation[] callables = new DecoderVariation[] {
184                 (id) -> ImageDecoder.decodeBitmap(
185                         ImageDecoder.createSource(sContext.getResources(), id),
186                         (decoder, info, source) -> decoder.setAllocator(
187                                 ImageDecoder.ALLOCATOR_SOFTWARE)),
188 
189                 (id) -> ImageDecoder.decodeBitmap(
190                         ImageDecoder.createSource(sContext.getResources(), id)),
191 
192                 (id) -> ImageDecoder.decodeBitmap(
193                         ImageDecoder.createSource(sContext.getResources(), id),
194                         (decoder, info, source) -> decoder.setTargetSampleSize(2)),
195 
196                 (id) -> BitmapFactory.decodeResource(sContext.getResources(), id),
197 
198                 (id) -> BitmapFactory.decodeResource(sContext.getResources(), id,
199                         hardwareOptions),
200         };
201         return callables;
202     }
203 
204     @Test
205     @Parameters(method = "getGainmapDecodeVariations")
testDecodeGainmap(DecoderVariation provider)206     public void testDecodeGainmap(DecoderVariation provider) throws Exception {
207         checkGainmap(provider.decode(R.raw.gainmap));
208     }
209 
210     @Test
211     @Parameters(method = "getGainmapDecodeVariations")
testDecodeFountainGainmap(DecoderVariation provider)212     public void testDecodeFountainGainmap(DecoderVariation provider) throws Exception {
213         checkFountainGainmap(provider.decode(R.raw.fountain_night));
214     }
215 
216     @Test
testDecodeGainmapBitmapFactoryReuse()217     public void testDecodeGainmapBitmapFactoryReuse() throws Exception {
218         BitmapFactory.Options options = new BitmapFactory.Options();
219         options.inMutable = true;
220         options.inDensity = 160;
221         options.inTargetDensity = 160;
222 
223         Bitmap bitmap = BitmapFactory.decodeResource(sContext.getResources(), R.raw.gainmap,
224                 options);
225         checkGainmap(bitmap);
226         options.inBitmap = bitmap;
227         assertSame(bitmap, BitmapFactory.decodeResource(
228                 sContext.getResources(), R.drawable.baseline_jpeg, options));
229         assertEquals(1280, bitmap.getWidth());
230         assertEquals(960, bitmap.getHeight());
231         assertFalse(bitmap.hasGainmap());
232         assertNull(bitmap.getGainmap());
233         assertSame(bitmap, BitmapFactory.decodeResource(
234                 sContext.getResources(), R.raw.gainmap, options));
235         checkGainmap(bitmap);
236     }
237 
238     @Test
testDecodeGainmapBitmapRegionDecoder()239     public void testDecodeGainmapBitmapRegionDecoder() throws Exception {
240         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
241         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
242         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE), null);
243         checkGainmap(region);
244     }
245 
246     @Test
testDecodeGainmapBitmapRegionDecoderReuse()247     public void testDecodeGainmapBitmapRegionDecoderReuse() throws Exception {
248         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
249         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
250         BitmapFactory.Options options = new BitmapFactory.Options();
251         options.inMutable = true;
252         options.inDensity = 160;
253         options.inTargetDensity = 160;
254         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
255                 options);
256         checkGainmap(region);
257         Bitmap previousGainmap = region.getGainmap().getGainmapContents();
258         options.inBitmap = region;
259 
260         is = sContext.getResources().openRawResource(R.drawable.baseline_jpeg);
261         BitmapRegionDecoder secondDecoder = BitmapRegionDecoder.newInstance(is);
262         assertSame(region, secondDecoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
263                 options));
264         assertFalse(region.hasGainmap());
265         assertNull(region.getGainmap());
266 
267         assertSame(region, decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
268                 options));
269         checkGainmap(region);
270         assertNotSame(previousGainmap, region.getGainmap().getGainmapContents());
271     }
272 
273     @Test
testDecodeGainmapBitmapRegionDecoderReusePastBounds()274     public void testDecodeGainmapBitmapRegionDecoderReusePastBounds() throws Exception {
275         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
276         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
277         BitmapFactory.Options options = new BitmapFactory.Options();
278         options.inMutable = true;
279         options.inDensity = 160;
280         options.inTargetDensity = 160;
281         int offsetX = decoder.getWidth() - (TILE_SIZE / 2);
282         int offsetY = decoder.getHeight() - (TILE_SIZE / 4);
283         Bitmap region = decoder.decodeRegion(new Rect(offsetX, offsetY, offsetX + TILE_SIZE,
284                         offsetY + TILE_SIZE), options);
285         checkGainmap(region);
286         Bitmap gainmap = region.getGainmap().getGainmapContents();
287         // Since there's no re-use bitmap, the resulting bitmap size will be the size of the rect
288         // that overlaps with the image. 1/2 of the X and 3/4ths of the Y are out of bounds
289         assertEquals(TILE_SIZE / 2, region.getWidth());
290         assertEquals(TILE_SIZE / 4, region.getHeight());
291         // The test image has a 1:1 ratio between base & gainmap
292         assertEquals(region.getWidth(), gainmap.getWidth());
293         assertEquals(region.getHeight(), gainmap.getHeight());
294 
295         options.inBitmap = Bitmap.createBitmap(TILE_SIZE, TILE_SIZE, Bitmap.Config.ARGB_8888);
296         region = decoder.decodeRegion(new Rect(offsetX, offsetY, offsetX + TILE_SIZE,
297                 offsetY + TILE_SIZE), options);
298         gainmap = region.getGainmap().getGainmapContents();
299         // Although 1/2 the X and 3/4ths the Y are out of bounds, because there's a re-use
300         // bitmap the resulting decode must exactly match the size given
301         assertEquals(TILE_SIZE, region.getWidth());
302         assertEquals(TILE_SIZE, region.getHeight());
303         // The test image has a 1:1 ratio between base & gainmap
304         assertEquals(region.getWidth(), gainmap.getWidth());
305         assertEquals(region.getHeight(), gainmap.getHeight());
306     }
307 
308     @Test
testDecodeGainmapBitmapRegionDecoderReuseCropped()309     public void testDecodeGainmapBitmapRegionDecoderReuseCropped() throws Exception {
310         InputStream is = sContext.getResources().openRawResource(R.raw.gainmap);
311         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
312         BitmapFactory.Options options = new BitmapFactory.Options();
313         options.inMutable = true;
314         options.inDensity = 160;
315         options.inTargetDensity = 160;
316         options.inBitmap = Bitmap.createBitmap(TILE_SIZE / 2, TILE_SIZE / 2,
317                 Bitmap.Config.ARGB_8888);
318         Bitmap region = decoder.decodeRegion(new Rect(0, 0, TILE_SIZE, TILE_SIZE),
319                 options);
320         checkGainmap(region);
321         Bitmap gainmap = region.getGainmap().getGainmapContents();
322         // Although the rect was entirely in-bounds of the image, the inBitmap is 1/2th the
323         // the specified width/height so make sure the gainmap matches
324         assertEquals(TILE_SIZE / 2, region.getWidth());
325         assertEquals(TILE_SIZE / 2, region.getHeight());
326         // The test image has a 1:1 ratio between base & gainmap
327         assertEquals(region.getWidth(), gainmap.getWidth());
328         assertEquals(region.getHeight(), gainmap.getHeight());
329     }
330 
331     @Test
testDecodeGainmapBitmapRegionDecoderWithInSampleSize()332     public void testDecodeGainmapBitmapRegionDecoderWithInSampleSize() throws Exception {
333         // Use a quite generous threshold because we're dealing with lossy jpeg. This is still
334         // plenty sufficient to catch the difference between RED and GREEN without any risk
335         // of flaking on compression artifacts
336         final int threshold = 20;
337 
338         InputStream is = sContext.getResources().openRawResource(R.raw.grid_gainmap);
339         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(is);
340         BitmapFactory.Options options = new BitmapFactory.Options();
341         options.inMutable = true;
342         options.inDensity = 160;
343         options.inTargetDensity = 160;
344         options.inSampleSize = 4;
345 
346         // The test image is a 1024x1024 grid of 4 colors each 512x512
347         // with a gainmap that's 512x512 grid of 4 colors each 256x256
348         // RED  | GREEN
349         // BLUE | BLACK
350         // So by decoding the center 512x512 of the image we should still get the same set of
351         // 4 colors in the output
352         Rect subset = new Rect(256, 256, 768, 768);
353         Bitmap region = decoder.decodeRegion(subset, options);
354         assertTrue(region.hasGainmap());
355         Bitmap gainmap = region.getGainmap().getGainmapContents();
356 
357         // sampleSize = 4 means we expect an output scaled by 1/4th
358         assertEquals(128, region.getWidth());
359         assertEquals(128, region.getHeight());
360         assertEquals(64, gainmap.getWidth());
361         assertEquals(64, gainmap.getHeight());
362 
363         assertBitmapQuadColor(region, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, threshold);
364         assertBitmapQuadColor(gainmap, Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, threshold);
365     }
366 
367     @Test
testDefaults()368     public void testDefaults() {
369         Gainmap gainmap = new Gainmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8));
370         assertAllAre(1.0f, gainmap.getRatioMin());
371         assertAllAre(2.f, gainmap.getRatioMax());
372         assertAllAre(1.f, gainmap.getGamma());
373         assertAllAre(0.f, gainmap.getEpsilonSdr());
374         assertAllAre(0.f, gainmap.getEpsilonHdr());
375         assertEquals(1.f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
376         assertEquals(2.f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
377     }
378 
379     @Test
testSetGet()380     public void testSetGet() {
381         Gainmap gainmap = new Gainmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8));
382         gainmap.setDisplayRatioForFullHdr(5f);
383         gainmap.setMinDisplayRatioForHdrTransition(3f);
384         gainmap.setGamma(1.1f, 1.2f, 1.3f);
385         gainmap.setRatioMin(2.1f, 2.2f, 2.3f);
386         gainmap.setRatioMax(3.1f, 3.2f, 3.3f);
387         gainmap.setEpsilonSdr(0.1f, 0.2f, 0.3f);
388         gainmap.setEpsilonHdr(0.01f, 0.02f, 0.03f);
389 
390         assertEquals(5f, gainmap.getDisplayRatioForFullHdr(), EPSILON);
391         assertEquals(3f, gainmap.getMinDisplayRatioForHdrTransition(), EPSILON);
392         assertAre(1.1f, 1.2f, 1.3f, gainmap.getGamma());
393         assertAre(2.1f, 2.2f, 2.3f, gainmap.getRatioMin());
394         assertAre(3.1f, 3.2f, 3.3f, gainmap.getRatioMax());
395         assertAre(0.1f, 0.2f, 0.3f, gainmap.getEpsilonSdr());
396         assertAre(0.01f, 0.02f, 0.03f, gainmap.getEpsilonHdr());
397     }
398 
399     @Test
testCopyInfo()400     public void testCopyInfo() {
401         Gainmap original = new Gainmap(Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8));
402         original.setDisplayRatioForFullHdr(5f);
403         original.setMinDisplayRatioForHdrTransition(3f);
404         original.setGamma(1.1f, 1.2f, 1.3f);
405         original.setRatioMin(2.1f, 2.2f, 2.3f);
406         original.setRatioMax(3.1f, 3.2f, 3.3f);
407         original.setEpsilonSdr(0.1f, 0.2f, 0.3f);
408         original.setEpsilonHdr(0.01f, 0.02f, 0.03f);
409 
410         Gainmap copy = new Gainmap(original, Bitmap.createBitmap(5, 5, Bitmap.Config.ALPHA_8));
411         assertEquals(5f, copy.getDisplayRatioForFullHdr(), EPSILON);
412         assertEquals(3f, copy.getMinDisplayRatioForHdrTransition(), EPSILON);
413         assertAre(1.1f, 1.2f, 1.3f, copy.getGamma());
414         assertAre(2.1f, 2.2f, 2.3f, copy.getRatioMin());
415         assertAre(3.1f, 3.2f, 3.3f, copy.getRatioMax());
416         assertAre(0.1f, 0.2f, 0.3f, copy.getEpsilonSdr());
417         assertAre(0.01f, 0.02f, 0.03f, copy.getEpsilonHdr());
418 
419         assertEquals(10, original.getGainmapContents().getWidth());
420         assertEquals(5, copy.getGainmapContents().getWidth());
421     }
422 
423     @Test
testWriteToParcel()424     public void testWriteToParcel() throws Exception {
425         Bitmap bitmap = ImageDecoder.decodeBitmap(
426                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
427                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
428         assertNotNull(bitmap);
429 
430         Gainmap gainmap = bitmap.getGainmap();
431         assertNotNull(gainmap);
432         Bitmap gainmapData = gainmap.getGainmapContents();
433         assertNotNull(gainmapData);
434 
435         Parcel p = Parcel.obtain();
436         gainmap.writeToParcel(p, 0);
437         p.setDataPosition(0);
438 
439         Gainmap unparceledGainmap = Gainmap.CREATOR.createFromParcel(p);
440         assertNotNull(unparceledGainmap);
441         Bitmap unparceledGainmapData = unparceledGainmap.getGainmapContents();
442         assertNotNull(unparceledGainmapData);
443 
444         assertTrue(gainmapData.sameAs(unparceledGainmapData));
445         assertEquals(gainmapData.getConfig(), unparceledGainmapData.getConfig());
446         assertEquals(gainmapData.getColorSpace(), unparceledGainmapData.getColorSpace());
447 
448         assertArrayEquals(gainmap.getEpsilonSdr(), unparceledGainmap.getEpsilonSdr(), 0f);
449         assertArrayEquals(gainmap.getEpsilonHdr(), unparceledGainmap.getEpsilonHdr(), 0f);
450         assertArrayEquals(gainmap.getGamma(), unparceledGainmap.getGamma(), 0f);
451         assertEquals(gainmap.getMinDisplayRatioForHdrTransition(),
452                 unparceledGainmap.getMinDisplayRatioForHdrTransition(), 0f);
453 
454         assertArrayEquals(gainmap.getRatioMax(), unparceledGainmap.getRatioMax(), 0f);
455         assertArrayEquals(gainmap.getRatioMin(), unparceledGainmap.getRatioMin(), 0f);
456         assertEquals(gainmap.getDisplayRatioForFullHdr(),
457                 unparceledGainmap.getDisplayRatioForFullHdr(), 0f);
458         p.recycle();
459     }
460 
461     @Test
testCompress8888()462     public void testCompress8888() throws Exception {
463         ByteArrayOutputStream stream = new ByteArrayOutputStream();
464         assertTrue(sScalingRed8888.compress(Bitmap.CompressFormat.JPEG, 100, stream));
465         byte[] data = stream.toByteArray();
466         Bitmap result = ImageDecoder.decodeBitmap(
467                 ImageDecoder.createSource(data), (decoder, info, src) -> {
468                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
469             });
470         assertTrue(result.hasGainmap());
471         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
472         assertEquals(Bitmap.Config.ARGB_8888, gainmapImage.getConfig());
473         Bitmap sourceImage = sScalingRed8888.getGainmap().getGainmapContents();
474         for (int x = 0; x < 4; x++) {
475             Color expected = sourceImage.getColor(x, 0);
476             Color got = gainmapImage.getColor(x, 0);
477             assertArrayEquals("Differed at x=" + x,
478                     expected.getComponents(), got.getComponents(), 0.05f);
479         }
480     }
481 
482     @Test
testCompressA8ByImageDecoder()483     public void testCompressA8ByImageDecoder() throws Exception {
484         ByteArrayOutputStream stream = new ByteArrayOutputStream();
485         assertTrue(sScalingRedA8.compress(Bitmap.CompressFormat.JPEG, 100, stream));
486         byte[] data = stream.toByteArray();
487         Bitmap result = ImageDecoder.decodeBitmap(
488                 ImageDecoder.createSource(data), (decoder, info, src) -> {
489                 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
490             });
491         assertTrue(result.hasGainmap());
492         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
493         assertEquals(Bitmap.Config.ALPHA_8, gainmapImage.getConfig());
494         Bitmap sourceImage = sScalingRedA8.getGainmap().getGainmapContents();
495         for (int x = 0; x < 4; x++) {
496             Color expected = sourceImage.getColor(x, 0);
497             Color got = gainmapImage.getColor(x, 0);
498             assertArrayEquals("Differed at x=" + x,
499                     expected.getComponents(), got.getComponents(), 0.05f);
500         }
501     }
502 
503     @Test
504     @Ignore("Skip it until BitmapRegionDecoder have Alpha8 gainmap support")
testCompressA8ByBitmapRegionDecoder()505     public void testCompressA8ByBitmapRegionDecoder() throws Exception {
506         ByteArrayOutputStream stream = new ByteArrayOutputStream();
507         assertTrue(sScalingRedA8.compress(Bitmap.CompressFormat.JPEG, 100, stream));
508         byte[] data = stream.toByteArray();
509         BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(data, 0, data.length);
510         Bitmap region = decoder.decodeRegion(new Rect(0, 0, 4, 1), null);
511         assertTrue(region.hasGainmap());
512         Bitmap gainmapImage = region.getGainmap().getGainmapContents();
513         assertEquals(Bitmap.Config.ALPHA_8, gainmapImage.getConfig());
514         Bitmap sourceImage = sScalingRedA8.getGainmap().getGainmapContents();
515         for (int x = 0; x < 4; x++) {
516             Color expected = sourceImage.getColor(x, 0);
517             Color got = gainmapImage.getColor(x, 0);
518             assertArrayEquals("Differed at x=" + x,
519                     expected.getComponents(), got.getComponents(), 0.05f);
520         }
521     }
522 
523     @Test
testCompressA8ByBitmapFactory()524     public void testCompressA8ByBitmapFactory() throws Exception {
525         ByteArrayOutputStream stream = new ByteArrayOutputStream();
526         assertTrue(sScalingRedA8.compress(Bitmap.CompressFormat.JPEG, 100, stream));
527         byte[] data = stream.toByteArray();
528         Bitmap result = BitmapFactory.decodeByteArray(data, 0, data.length);
529         assertTrue(result.hasGainmap());
530         Bitmap gainmapImage = result.getGainmap().getGainmapContents();
531         assertEquals(Bitmap.Config.ALPHA_8, gainmapImage.getConfig());
532         Bitmap sourceImage = sScalingRedA8.getGainmap().getGainmapContents();
533         for (int x = 0; x < 4; x++) {
534             Color expected = sourceImage.getColor(x, 0);
535             Color got = gainmapImage.getColor(x, 0);
536             assertArrayEquals("Differed at x=" + x,
537                     expected.getComponents(), got.getComponents(), 0.05f);
538         }
539     }
540 
541     @Test
testHardwareGainmapCopy()542     public void testHardwareGainmapCopy() throws Exception {
543         Bitmap bitmap = ImageDecoder.decodeBitmap(
544                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
545                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_HARDWARE));
546         assertNotNull(bitmap);
547         assertTrue("Missing gainmap", bitmap.hasGainmap());
548         assertEquals(Bitmap.Config.HARDWARE, bitmap.getConfig());
549 
550         Gainmap gainmap = bitmap.getGainmap();
551         assertNotNull(gainmap);
552         Bitmap gainmapData = gainmap.getGainmapContents();
553         assertNotNull(gainmapData);
554         assertEquals(Bitmap.Config.HARDWARE, gainmapData.getConfig());
555     }
556 
557     @Test
testCopyPreservesGainmap()558     public void testCopyPreservesGainmap() throws Exception {
559         Bitmap bitmap = ImageDecoder.decodeBitmap(
560                 ImageDecoder.createSource(sContext.getResources(), R.raw.gainmap),
561                 (decoder, info, source) -> decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE));
562         assertNotNull(bitmap);
563         assertTrue("Missing gainmap", bitmap.hasGainmap());
564 
565         Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
566         assertNotNull(copy);
567         assertTrue("Missing gainmap", copy.hasGainmap());
568     }
569 
assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight, int bottomLeft, int bottomRight, int threshold)570     private static void assertBitmapQuadColor(Bitmap bitmap, int topLeft, int topRight,
571             int bottomLeft, int bottomRight, int threshold) {
572         Function<Float, Integer> getX = (Float x) -> (int) (bitmap.getWidth() * x);
573         Function<Float, Integer> getY = (Float y) -> (int) (bitmap.getHeight() * y);
574 
575         // Just quickly sample 4 pixels in the various regions.
576         assertBitmapColor("Top left", bitmap, topLeft,
577                 getX.apply(.25f), getY.apply(.25f), threshold);
578         assertBitmapColor("Top right", bitmap, topRight,
579                 getX.apply(.75f), getY.apply(.25f), threshold);
580         assertBitmapColor("Bottom left", bitmap, bottomLeft,
581                 getX.apply(.25f), getY.apply(.75f), threshold);
582         assertBitmapColor("Bottom right", bitmap, bottomRight,
583                 getX.apply(.75f), getY.apply(.75f), threshold);
584 
585         float below = .4f;
586         float above = .6f;
587         assertBitmapColor("Top left II", bitmap, topLeft,
588                 getX.apply(below), getY.apply(below), threshold);
589         assertBitmapColor("Top right II", bitmap, topRight,
590                 getX.apply(above), getY.apply(below), threshold);
591         assertBitmapColor("Bottom left II", bitmap, bottomLeft,
592                 getX.apply(below), getY.apply(above), threshold);
593         assertBitmapColor("Bottom right II", bitmap, bottomRight,
594                 getX.apply(above), getY.apply(above), threshold);
595     }
596 
pixelsAreSame(int ideal, int given, int threshold)597     private static boolean pixelsAreSame(int ideal, int given, int threshold) {
598         int error = Math.abs(Color.red(ideal) - Color.red(given));
599         error += Math.abs(Color.green(ideal) - Color.green(given));
600         error += Math.abs(Color.blue(ideal) - Color.blue(given));
601         return (error < threshold);
602     }
603 
assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y, int threshold)604     private static void assertBitmapColor(String debug, Bitmap bitmap, int color, int x, int y,
605             int threshold) {
606         int pixel = bitmap.getPixel(x, y);
607         if (!pixelsAreSame(color, pixel, threshold)) {
608             Assert.fail(debug + "; expected=" + Integer.toHexString(color) + ", actual="
609                     + Integer.toHexString(pixel));
610         }
611     }
612 
613 }
614