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.uirendering.cts.testclasses;
18 
19 import static junit.framework.Assert.assertFalse;
20 
21 import static org.testng.Assert.assertEquals;
22 import static org.testng.Assert.assertTrue;
23 
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapShader;
26 import android.graphics.Canvas;
27 import android.graphics.Color;
28 import android.graphics.ColorSpace;
29 import android.graphics.Gainmap;
30 import android.graphics.HardwareBufferRenderer;
31 import android.graphics.Matrix;
32 import android.graphics.Paint;
33 import android.graphics.Picture;
34 import android.graphics.RecordingCanvas;
35 import android.graphics.RenderNode;
36 import android.graphics.Shader;
37 import android.hardware.HardwareBuffer;
38 import android.platform.test.annotations.RequiresFlagsEnabled;
39 import android.platform.test.flag.junit.CheckFlagsRule;
40 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
41 import android.uirendering.cts.bitmapverifiers.ColorVerifier;
42 import android.uirendering.cts.util.BitmapAsserter;
43 import android.uirendering.cts.util.BitmapDumper;
44 
45 import androidx.annotation.ColorLong;
46 import androidx.test.filters.SmallTest;
47 import androidx.test.runner.AndroidJUnit4;
48 
49 import com.android.graphics.hwui.flags.Flags;
50 
51 import org.junit.Rule;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.testng.Assert;
55 
56 import java.util.concurrent.CountDownLatch;
57 import java.util.concurrent.TimeUnit;
58 import java.util.function.Consumer;
59 
60 @SmallTest
61 @RunWith(AndroidJUnit4.class)
62 public class GainmapTests {
63 
64     @Rule
65     public final CheckFlagsRule mCheckFlagsRule =
66             DeviceFlagsValueProvider.createCheckFlagsRule();
67 
68 
69     private static final ColorSpace BT2020_HLG = ColorSpace.get(ColorSpace.Named.BT2020_HLG);
70     private static final ColorSpace BT2020_PQ = ColorSpace.get(ColorSpace.Named.BT2020_PQ);
71     private static final ColorSpace SRGB = ColorSpace.get(ColorSpace.Named.SRGB);
72 
73     // A 10x6 base image with a 5x3 (so 1/2 res) gainmap that boosts the center 3 pixels
74     // by 0x40, 0x80, and 0xff respectively
75     private static final Bitmap sTestImage;
76     static {
77         Bitmap base = Bitmap.createBitmap(10, 6, Bitmap.Config.ARGB_8888);
78         base.eraseColor(Color.WHITE);
79 
80         Bitmap gainmapImage = Bitmap.createBitmap(5, 3, Bitmap.Config.ARGB_8888);
81         gainmapImage.eraseColor(0);
82         gainmapImage.setPixel(1, 1, 0xFF404040);
83         gainmapImage.setPixel(2, 1, 0xFF808080);
84         gainmapImage.setPixel(3, 1, 0xFFFFFFFF);
85 
86         Gainmap gainmap = new Gainmap(gainmapImage);
87         base.setGainmap(gainmap);
88         sTestImage = base;
89     }
90 
91     private static final Picture sTestPicture;
92     static {
93         sTestPicture = new Picture();
94         Canvas canvas = sTestPicture.beginRecording(sTestImage.getWidth(), sTestImage.getHeight());
canvas.drawBitmap(sTestImage, 0, 0, null)95         canvas.drawBitmap(sTestImage, 0, 0, null);
sTestPicture.endRecording()96         sTestPicture.endRecording();
97     }
98 
99     private static final Gainmap sNoOpGainmap;
100     static {
101         sNoOpGainmap = new Gainmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8));
102         sNoOpGainmap.setRatioMin(1f, 1f, 1f);
103         sNoOpGainmap.setRatioMax(1f, 1f, 1f);
104     }
105 
assertChannels(Color result, @ColorLong long expected, float delta)106     private static void assertChannels(Color result, @ColorLong long expected, float delta) {
107         ColorSpace.Connector connector = ColorSpace.connect(Color.colorSpace(expected),
108                 result.getColorSpace());
109         float[] mapped = connector.transform(Color.red(expected), Color.green(expected),
110                 Color.blue(expected));
111         Assert.assertEquals(result.red(), mapped[0], delta, "red channel mismatch");
112         Assert.assertEquals(result.green(), mapped[1], delta, "green channel mismatch");
113         Assert.assertEquals(result.blue(), mapped[2], delta, "blue channel mismatch");
114     }
115 
116     @ColorLong
mapWhiteWithGain(Gainmap gainmap, double gain)117     private static long mapWhiteWithGain(Gainmap gainmap, double gain) {
118         double logRatioMin = Math.log(gainmap.getRatioMin()[0]);
119         double logRatioMax = Math.log(gainmap.getRatioMax()[0]);
120         double epsilonSdr = gainmap.getEpsilonSdr()[0];
121         double epsilonHdr = gainmap.getEpsilonHdr()[0];
122         double L = (logRatioMin * (1 - gain)) + (logRatioMax * gain);
123         float D = (float) ((1.0 + epsilonSdr) * Math.exp(L) - epsilonHdr);
124         return Color.pack(D, D, D, 1.f, ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB));
125     }
126 
assertTestImageResult(Bitmap result)127     private void assertTestImageResult(Bitmap result) {
128         assertTestImageResult(result, sTestImage.getGainmap());
129     }
130 
toleranceForResult(Bitmap result)131     private static float toleranceForResult(Bitmap result) {
132         // 8888 precision ain't so great
133         if (result.getConfig() == Bitmap.Config.ARGB_8888) {
134             // PQ math on GLES2.0 is very poor
135             if (result.getColorSpace().getId() == ColorSpace.Named.BT2020_PQ.ordinal()) {
136                 return 0.06f;
137             }
138             return 0.02f;
139         }
140         return 0.002f;
141     }
142 
assertTestImageResult(Bitmap result, Gainmap gainmap)143     private void assertTestImageResult(Bitmap result, Gainmap gainmap) {
144         try {
145             // 8888 precision ain't so great
146             final float delta = toleranceForResult(result);
147             assertChannels(result.getColor(0, 0), Color.pack(Color.WHITE), delta);
148             assertChannels(result.getColor(2, 2),
149                     mapWhiteWithGain(gainmap, 0x40 / 255.f), delta);
150             assertChannels(result.getColor(4, 2),
151                     mapWhiteWithGain(gainmap, 0x80 / 255.f), delta);
152             assertChannels(result.getColor(6, 2),
153                     mapWhiteWithGain(gainmap, 0xFF / 255.f), delta);
154         } catch (Throwable t) {
155             BitmapDumper.dumpBitmap(result);
156             throw t;
157         }
158     }
159 
renderTestImageWithHardware(ColorSpace dest)160     private static Bitmap renderTestImageWithHardware(ColorSpace dest) {
161         return renderTestImageWithHardware(dest, false);
162     }
163 
renderWithHardware(ColorSpace dest, Consumer<RecordingCanvas> func)164     private static Bitmap renderWithHardware(ColorSpace dest, Consumer<RecordingCanvas> func) {
165         HardwareBuffer buffer = HardwareBuffer.create(sTestImage.getWidth(), sTestImage.getHeight(),
166                 HardwareBuffer.RGBA_8888,
167                 1, HardwareBuffer.USAGE_GPU_COLOR_OUTPUT | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE);
168         HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer);
169         RenderNode content = new RenderNode("gainmap");
170         content.setPosition(0, 0, sTestImage.getWidth(), sTestImage.getHeight());
171         RecordingCanvas canvas = content.beginRecording();
172         func.accept(canvas);
173         content.endRecording();
174         renderer.setContentRoot(content);
175         CountDownLatch latch = new CountDownLatch(1);
176         renderer.obtainRenderRequest().setColorSpace(dest).draw(Runnable::run, result -> {
177             result.getFence().awaitForever();
178             latch.countDown();
179         });
180         try {
181             Assert.assertTrue(latch.await(5, TimeUnit.SECONDS));
182         } catch (InterruptedException ex) {
183             Assert.fail(ex.getMessage());
184         }
185         return Bitmap.wrapHardwareBuffer(buffer, dest).copy(Bitmap.Config.ARGB_8888, false);
186     }
187 
renderTestImageWithHardware(ColorSpace dest, boolean usePicture)188     private static Bitmap renderTestImageWithHardware(ColorSpace dest, boolean usePicture) {
189         return renderWithHardware(dest, canvas -> {
190             if (usePicture) {
191                 canvas.drawPicture(sTestPicture);
192             } else {
193                 canvas.drawBitmap(sTestImage, 0, 0, null);
194             }
195         });
196     }
197 
198     @Test
199     public void gainmapToHlgSoftware() {
200         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
201         Canvas canvas = new Canvas(result);
202         canvas.drawBitmap(sTestImage, 0f, 0f, null);
203         assertTestImageResult(result);
204     }
205 
206     @Test
207     public void gainmapToPqSoftware() {
208         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_PQ);
209         Canvas canvas = new Canvas(result);
210         canvas.drawBitmap(sTestImage, 0f, 0f, null);
211         assertTestImageResult(result);
212     }
213 
214     @Test
215     public void gainmapToSrgbSoftware() {
216         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, SRGB);
217         Canvas canvas = new Canvas(result);
218         canvas.drawBitmap(sTestImage, 0f, 0f, null);
219         assertTestImageResult(result, sNoOpGainmap);
220     }
221 
222     @Test
223     public void gainmapToHlgPictureSoftware() {
224         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
225         Canvas canvas = new Canvas(result);
226         canvas.drawPicture(sTestPicture);
227         assertTestImageResult(result);
228     }
229 
230     @Test
231     public void gainmapToPqPictureSoftware() {
232         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
233         Canvas canvas = new Canvas(result);
234         canvas.drawPicture(sTestPicture);
235         assertTestImageResult(result);
236     }
237 
238     @Test
239     public void gainmapToSrgbPictureSoftware() {
240         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, SRGB);
241         Canvas canvas = new Canvas(result);
242         canvas.drawPicture(sTestPicture);
243         assertTestImageResult(result, sNoOpGainmap);
244     }
245 
246     @Test
247     public void gainmapToHlgHardware() throws Exception {
248         Bitmap result = renderTestImageWithHardware(BT2020_HLG);
249         assertTestImageResult(result);
250     }
251 
252     @Test
253     public void gainmapToPqHardware() {
254         Bitmap result = renderTestImageWithHardware(BT2020_PQ);
255         assertTestImageResult(result);
256     }
257 
258     @Test
259     public void gainmapToSrgbHardware() {
260         Bitmap result = renderTestImageWithHardware(SRGB);
261         assertTestImageResult(result, sNoOpGainmap);
262     }
263 
264     @Test
265     public void gainmapToHlgPictureHardware() throws Exception {
266         Bitmap result = renderTestImageWithHardware(BT2020_HLG, true);
267         assertTestImageResult(result);
268     }
269 
270     @Test
271     public void gainmapToPqPictureHardware() {
272         Bitmap result = renderTestImageWithHardware(BT2020_PQ, true);
273         assertTestImageResult(result);
274     }
275 
276     @Test
277     public void gainmapToSrgbPictureHardware() {
278         Bitmap result = renderTestImageWithHardware(SRGB, true);
279         assertTestImageResult(result, sNoOpGainmap);
280     }
281 
282     @Test
283     public void bitmapShaderSupportHLG() {
284         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
285         Canvas canvas = new Canvas(result);
286         Paint paint = new Paint();
287         paint.setFlags(0);
288         BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
289                 Shader.TileMode.CLAMP);
290         paint.setShader(shader);
291         canvas.drawPaint(paint);
292         assertTestImageResult(result);
293     }
294 
295     @Test
296     public void bitmapShaderSupportHLGHardware() {
297         Bitmap result = renderWithHardware(BT2020_HLG, canvas -> {
298             Paint paint = new Paint();
299             paint.setFlags(0);
300             BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
301                     Shader.TileMode.CLAMP);
302             paint.setShader(shader);
303             canvas.drawPaint(paint);
304         });
305         assertTestImageResult(result);
306     }
307 
308     @Test
309     public void bitmapShaderSupportSrgbHardware() {
310         Bitmap result = renderWithHardware(SRGB, canvas -> {
311             Paint paint = new Paint();
312             paint.setFlags(0);
313             BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
314                     Shader.TileMode.CLAMP);
315             paint.setShader(shader);
316             canvas.drawPaint(paint);
317         });
318         assertTestImageResult(result, sNoOpGainmap);
319     }
320 
321     @RequiresFlagsEnabled(Flags.FLAG_GAINMAP_ANIMATIONS)
322     @Test
323     public void bitmapShaderOverrideGainmapToNoOpHLG() {
324         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
325         Canvas canvas = new Canvas(result);
326         Paint paint = new Paint();
327         paint.setFlags(0);
328         BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
329                 Shader.TileMode.CLAMP);
330         shader.setOverrideGainmap(sNoOpGainmap);
331         paint.setShader(shader);
332         canvas.drawPaint(paint);
333         BitmapAsserter.assertBitmapIsVerified(result, new ColorVerifier(Color.WHITE, 3),
334                 "");
335     }
336 
337     @RequiresFlagsEnabled(Flags.FLAG_GAINMAP_ANIMATIONS)
338     @Test
339     public void bitmapShaderOverrideGainmapTo4xHLG() {
340         Gainmap override = new Gainmap(sTestImage.getGainmap().getGainmapContents());
341         override.setRatioMax(4.0f, 4.0f, 4.0f);
342         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
343         Canvas canvas = new Canvas(result);
344         Paint paint = new Paint();
345         paint.setFlags(0);
346         BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
347                 Shader.TileMode.CLAMP);
348         shader.setOverrideGainmap(override);
349         paint.setShader(shader);
350         canvas.drawPaint(paint);
351         assertTestImageResult(result, override);
352     }
353 
354     @RequiresFlagsEnabled(Flags.FLAG_GAINMAP_ANIMATIONS)
355     @Test
356     public void bitmapShaderOverrideGainmapModifyAfterSetHLG() {
357         Gainmap override = new Gainmap(sTestImage.getGainmap().getGainmapContents());
358         override.setRatioMax(4.0f, 4.0f, 4.0f);
359         Gainmap initialOverride = new Gainmap(override, override.getGainmapContents());
360         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
361         Canvas canvas = new Canvas(result);
362         Paint paint = new Paint();
363         paint.setFlags(0);
364         BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
365                 Shader.TileMode.CLAMP);
366         shader.setOverrideGainmap(override);
367         override.setRatioMax(1f, 1f, 1f);
368         paint.setShader(shader);
369         canvas.drawPaint(paint);
370         assertTestImageResult(result, initialOverride);
371     }
372 
373     @RequiresFlagsEnabled(Flags.FLAG_GAINMAP_ANIMATIONS)
374     @Test
375     public void bitmapShaderOverrideGainmapPaintObservesUpdatesHLG() {
376         Gainmap override = new Gainmap(sTestImage.getGainmap().getGainmapContents());
377         Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG);
378         Canvas canvas = new Canvas(result);
379         Paint paint = new Paint();
380         paint.setFlags(0);
381         BitmapShader shader = new BitmapShader(sTestImage, Shader.TileMode.CLAMP,
382                 Shader.TileMode.CLAMP);
383         shader.setOverrideGainmap(override);
384         paint.setShader(shader);
385         canvas.drawPaint(paint);
386         override.setRatioMax(1, 1, 1);
387         shader.setOverrideGainmap(override);
388         canvas.drawPaint(paint);
389         BitmapAsserter.assertBitmapIsVerified(result, new ColorVerifier(Color.WHITE, 3),
390                 "");
391     }
392 
393     @Test
394     public void createScaledBitmap() {
395         Bitmap result = Bitmap.createScaledBitmap(sTestImage, 20, 12, false);
396         assertEquals(result.getWidth(), 20);
397         assertEquals(result.getHeight(), 12);
398         assertTrue(result.hasGainmap());
399         Bitmap gainmapContents = result.getGainmap().getGainmapContents();
400         assertEquals(gainmapContents.getWidth(), 10);
401         assertEquals(gainmapContents.getHeight(), 6);
402 
403         assertChannels(gainmapContents.getColor(0, 0), Color.pack(Color.BLACK), 0f);
404         assertChannels(gainmapContents.getColor(1, 1), Color.pack(Color.BLACK), 0f);
405 
406         assertChannels(gainmapContents.getColor(2, 2), Color.pack(0xFF404040), 0f);
407         assertChannels(gainmapContents.getColor(3, 3), Color.pack(0xFF404040), 0f);
408 
409         assertChannels(gainmapContents.getColor(4, 2), Color.pack(0xFF808080), 0f);
410         assertChannels(gainmapContents.getColor(5, 3), Color.pack(0xFF808080), 0f);
411 
412         assertChannels(gainmapContents.getColor(6, 2), Color.pack(0xFFFFFFFF), 0f);
413         assertChannels(gainmapContents.getColor(7, 3), Color.pack(0xFFFFFFFF), 0f);
414 
415         assertChannels(gainmapContents.getColor(8, 4), Color.pack(Color.BLACK), 0f);
416         assertChannels(gainmapContents.getColor(9, 5), Color.pack(Color.BLACK), 0f);
417     }
418 
419     @Test
420     public void applyRotation180Matrix() {
421         Matrix m = new Matrix();
422         m.setRotate(180.0f, 5.f, 3.f);
423         Bitmap result = Bitmap.createBitmap(sTestImage, 0, 0, 10, 6, m, false);
424         assertEquals(result.getWidth(), 10);
425         assertEquals(result.getHeight(), 6);
426         assertTrue(result.hasGainmap());
427         Bitmap gainmapContents = result.getGainmap().getGainmapContents();
428         assertEquals(gainmapContents.getWidth(), 5);
429         assertEquals(gainmapContents.getHeight(), 3);
430         assertChannels(gainmapContents.getColor(0, 0), Color.pack(Color.BLACK), 0f);
431         assertChannels(gainmapContents.getColor(0, 1), Color.pack(Color.BLACK), 0f);
432         assertChannels(gainmapContents.getColor(1, 1), Color.pack(0xFFFFFFFF), 0f);
433         assertChannels(gainmapContents.getColor(2, 1), Color.pack(0xFF808080), 0f);
434         assertChannels(gainmapContents.getColor(3, 1), Color.pack(0xFF404040), 0f);
435         assertChannels(gainmapContents.getColor(4, 1), Color.pack(Color.BLACK), 0f);
436         assertChannels(gainmapContents.getColor(4, 2), Color.pack(Color.BLACK), 0f);
437     }
438 
439     @Test
440     public void applyRotation90Matrix() {
441         Matrix m = new Matrix();
442         m.setRotate(90.0f, 5.f, 3.f);
443         Bitmap result = Bitmap.createBitmap(sTestImage, 0, 0, 10, 6, m, false);
444         assertEquals(result.getWidth(), 6);
445         assertEquals(result.getHeight(), 10);
446         assertTrue(result.hasGainmap());
447         Bitmap gainmapContents = result.getGainmap().getGainmapContents();
448         assertEquals(gainmapContents.getWidth(), 3);
449         assertEquals(gainmapContents.getHeight(), 5);
450         assertChannels(gainmapContents.getColor(0, 0), Color.pack(Color.BLACK), 0f);
451         assertChannels(gainmapContents.getColor(1, 0), Color.pack(Color.BLACK), 0f);
452         assertChannels(gainmapContents.getColor(1, 1), Color.pack(0xFF404040), 0f);
453         assertChannels(gainmapContents.getColor(1, 2), Color.pack(0xFF808080), 0f);
454         assertChannels(gainmapContents.getColor(1, 3), Color.pack(0xFFFFFFFF), 0f);
455         assertChannels(gainmapContents.getColor(1, 4), Color.pack(Color.BLACK), 0f);
456         assertChannels(gainmapContents.getColor(2, 4), Color.pack(Color.BLACK), 0f);
457     }
458 
459     @Test
460     public void testRenderingDropsGainmap() {
461         Bitmap dest = Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888);
462         Gainmap gainmap = new Gainmap(Bitmap.createBitmap(5, 5, Bitmap.Config.ALPHA_8));
463         dest.setGainmap(gainmap);
464         assertTrue(dest.hasGainmap());
465         Canvas canvas = new Canvas(dest);
466         assertFalse(dest.hasGainmap());
467         canvas.setBitmap(null);
468         dest.setGainmap(gainmap);
469         assertTrue(dest.hasGainmap());
470         canvas.setBitmap(dest);
471         assertFalse(dest.hasGainmap());
472     }
473 }
474