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 org.testng.Assert.assertEquals; 20 import static org.testng.Assert.assertTrue; 21 22 import android.graphics.Bitmap; 23 import android.graphics.Canvas; 24 import android.graphics.Color; 25 import android.graphics.ColorSpace; 26 import android.graphics.Gainmap; 27 import android.graphics.HardwareBufferRenderer; 28 import android.graphics.Matrix; 29 import android.graphics.Picture; 30 import android.graphics.RecordingCanvas; 31 import android.graphics.RenderNode; 32 import android.hardware.HardwareBuffer; 33 34 import androidx.annotation.ColorLong; 35 import androidx.test.filters.SmallTest; 36 import androidx.test.runner.AndroidJUnit4; 37 38 import org.junit.Test; 39 import org.junit.runner.RunWith; 40 import org.testng.Assert; 41 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.TimeUnit; 44 45 @SmallTest 46 @RunWith(AndroidJUnit4.class) 47 public class GainmapTests { 48 49 private static final ColorSpace BT2020_HLG = ColorSpace.get(ColorSpace.Named.BT2020_HLG); 50 private static final ColorSpace BT2020_PQ = ColorSpace.get(ColorSpace.Named.BT2020_PQ); 51 52 // A 10x6 base image with a 5x3 (so 1/2 res) gainmap that boosts the center 3 pixels 53 // by 0x40, 0x80, and 0xff respectively 54 private static final Bitmap sTestImage; 55 static { 56 Bitmap base = Bitmap.createBitmap(10, 6, Bitmap.Config.ARGB_8888); 57 base.eraseColor(Color.WHITE); 58 59 Bitmap gainmapImage = Bitmap.createBitmap(5, 3, Bitmap.Config.ARGB_8888); 60 gainmapImage.eraseColor(0); 61 gainmapImage.setPixel(1, 1, 0xFF404040); 62 gainmapImage.setPixel(2, 1, 0xFF808080); 63 gainmapImage.setPixel(3, 1, 0xFFFFFFFF); 64 65 Gainmap gainmap = new Gainmap(gainmapImage); 66 base.setGainmap(gainmap); 67 sTestImage = base; 68 } 69 70 private static final Picture sTestPicture; 71 static { 72 sTestPicture = new Picture(); 73 Canvas canvas = sTestPicture.beginRecording(sTestImage.getWidth(), sTestImage.getHeight()); canvas.drawBitmap(sTestImage, 0, 0, null)74 canvas.drawBitmap(sTestImage, 0, 0, null); sTestPicture.endRecording()75 sTestPicture.endRecording(); 76 } 77 assertChannels(Color result, @ColorLong long expected, float delta)78 private static void assertChannels(Color result, @ColorLong long expected, float delta) { 79 ColorSpace.Connector connector = ColorSpace.connect(Color.colorSpace(expected), 80 result.getColorSpace()); 81 float[] mapped = connector.transform(Color.red(expected), Color.green(expected), 82 Color.blue(expected)); 83 Assert.assertEquals(result.red(), mapped[0], delta, "red channel mismatch"); 84 Assert.assertEquals(result.green(), mapped[1], delta, "green channel mismatch"); 85 Assert.assertEquals(result.blue(), mapped[2], delta, "blue channel mismatch"); 86 } 87 mapWhiteWithGain(Gainmap gainmap, double gain)88 private static @ColorLong long mapWhiteWithGain(Gainmap gainmap, double gain) { 89 double logRatioMin = Math.log(gainmap.getRatioMin()[0]); 90 double logRatioMax = Math.log(gainmap.getRatioMax()[0]); 91 double epsilonSdr = gainmap.getEpsilonSdr()[0]; 92 double epsilonHdr = gainmap.getEpsilonHdr()[0]; 93 double L = (logRatioMin * (1 - gain)) + (logRatioMax * gain); 94 float D = (float) ((1.0 + epsilonSdr) * Math.exp(L) - epsilonHdr); 95 return Color.pack(D, D, D, 1.f, ColorSpace.get(ColorSpace.Named.LINEAR_EXTENDED_SRGB)); 96 } 97 mapWhiteWithGain(double gain)98 private static @ColorLong long mapWhiteWithGain(double gain) { 99 return mapWhiteWithGain(sTestImage.getGainmap(), gain); 100 } 101 toleranceForResult(Bitmap result)102 private static float toleranceForResult(Bitmap result) { 103 // 8888 precision ain't so great 104 if (result.getConfig() == Bitmap.Config.ARGB_8888) { 105 // PQ math on GLES2.0 is very poor 106 if (result.getColorSpace().getId() == ColorSpace.Named.BT2020_PQ.ordinal()) { 107 return 0.06f; 108 } 109 return 0.02f; 110 } 111 return 0.002f; 112 } 113 assertTestImageResult(Bitmap result)114 private static void assertTestImageResult(Bitmap result) { 115 final float delta = toleranceForResult(result); 116 assertChannels(result.getColor(0, 0), Color.pack(Color.WHITE), delta); 117 assertChannels(result.getColor(2, 2), mapWhiteWithGain(0x40 / 255.f), delta); 118 assertChannels(result.getColor(4, 2), mapWhiteWithGain(0x80 / 255.f), delta); 119 assertChannels(result.getColor(6, 2), mapWhiteWithGain(0xFF / 255.f), delta); 120 } 121 renderTestImageWithHardware(ColorSpace dest)122 private static Bitmap renderTestImageWithHardware(ColorSpace dest) { 123 return renderTestImageWithHardware(dest, false); 124 } 125 renderTestImageWithHardware(ColorSpace dest, boolean usePicture)126 private static Bitmap renderTestImageWithHardware(ColorSpace dest, boolean usePicture) { 127 HardwareBuffer buffer = HardwareBuffer.create(sTestImage.getWidth(), sTestImage.getHeight(), 128 HardwareBuffer.RGBA_8888, 129 1, HardwareBuffer.USAGE_GPU_COLOR_OUTPUT | HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE); 130 HardwareBufferRenderer renderer = new HardwareBufferRenderer(buffer); 131 RenderNode content = new RenderNode("gainmap"); 132 content.setPosition(0, 0, sTestImage.getWidth(), sTestImage.getHeight()); 133 RecordingCanvas canvas = content.beginRecording(); 134 if (usePicture) { 135 canvas.drawPicture(sTestPicture); 136 } else { 137 canvas.drawBitmap(sTestImage, 0, 0, null); 138 } 139 content.endRecording(); 140 renderer.setContentRoot(content); 141 CountDownLatch latch = new CountDownLatch(1); 142 renderer.obtainRenderRequest().setColorSpace(dest).draw(Runnable::run, result -> { 143 result.getFence().awaitForever(); 144 latch.countDown(); 145 }); 146 try { 147 Assert.assertTrue(latch.await(5, TimeUnit.SECONDS)); 148 } catch (InterruptedException ex) { 149 Assert.fail(ex.getMessage()); 150 } 151 return Bitmap.wrapHardwareBuffer(buffer, dest).copy(Bitmap.Config.ARGB_8888, false); 152 } 153 154 @Test gainmapToHlgSoftware()155 public void gainmapToHlgSoftware() { 156 Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG); 157 Canvas canvas = new Canvas(result); 158 canvas.drawBitmap(sTestImage, 0f, 0f, null); 159 assertTestImageResult(result); 160 } 161 162 @Test gainmapToPqSoftware()163 public void gainmapToPqSoftware() { 164 Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_PQ); 165 Canvas canvas = new Canvas(result); 166 canvas.drawBitmap(sTestImage, 0f, 0f, null); 167 assertTestImageResult(result); 168 } 169 170 @Test gainmapToHlgPictureSoftware()171 public void gainmapToHlgPictureSoftware() { 172 Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG); 173 Canvas canvas = new Canvas(result); 174 canvas.drawPicture(sTestPicture); 175 assertTestImageResult(result); 176 } 177 178 @Test gainmapToPqPictureSoftware()179 public void gainmapToPqPictureSoftware() { 180 Bitmap result = Bitmap.createBitmap(10, 6, Bitmap.Config.RGBA_F16, false, BT2020_HLG); 181 Canvas canvas = new Canvas(result); 182 canvas.drawPicture(sTestPicture); 183 assertTestImageResult(result); 184 } 185 186 @Test gainmapToHlgHardware()187 public void gainmapToHlgHardware() throws Exception { 188 Bitmap result = renderTestImageWithHardware(BT2020_HLG); 189 assertTestImageResult(result); 190 } 191 192 @Test gainmapToPqHardware()193 public void gainmapToPqHardware() { 194 Bitmap result = renderTestImageWithHardware(BT2020_PQ); 195 assertTestImageResult(result); 196 } 197 198 @Test gainmapToHlgPictureHardware()199 public void gainmapToHlgPictureHardware() throws Exception { 200 Bitmap result = renderTestImageWithHardware(BT2020_HLG, true); 201 assertTestImageResult(result); 202 } 203 204 @Test gainmapToPqPictureHardware()205 public void gainmapToPqPictureHardware() { 206 Bitmap result = renderTestImageWithHardware(BT2020_PQ, true); 207 assertTestImageResult(result); 208 } 209 210 @Test createScaledBitmap()211 public void createScaledBitmap() { 212 Bitmap result = Bitmap.createScaledBitmap(sTestImage, 20, 12, false); 213 assertEquals(result.getWidth(), 20); 214 assertEquals(result.getHeight(), 12); 215 assertTrue(result.hasGainmap()); 216 Bitmap gainmapContents = result.getGainmap().getGainmapContents(); 217 assertEquals(gainmapContents.getWidth(), 10); 218 assertEquals(gainmapContents.getHeight(), 6); 219 220 assertChannels(gainmapContents.getColor(0, 0), Color.pack(Color.BLACK), 0f); 221 assertChannels(gainmapContents.getColor(1, 1), Color.pack(Color.BLACK), 0f); 222 223 assertChannels(gainmapContents.getColor(2, 2), Color.pack(0xFF404040), 0f); 224 assertChannels(gainmapContents.getColor(3, 3), Color.pack(0xFF404040), 0f); 225 226 assertChannels(gainmapContents.getColor(4, 2), Color.pack(0xFF808080), 0f); 227 assertChannels(gainmapContents.getColor(5, 3), Color.pack(0xFF808080), 0f); 228 229 assertChannels(gainmapContents.getColor(6, 2), Color.pack(0xFFFFFFFF), 0f); 230 assertChannels(gainmapContents.getColor(7, 3), Color.pack(0xFFFFFFFF), 0f); 231 232 assertChannels(gainmapContents.getColor(8, 4), Color.pack(Color.BLACK), 0f); 233 assertChannels(gainmapContents.getColor(9, 5), Color.pack(Color.BLACK), 0f); 234 } 235 236 @Test applyRotation180Matrix()237 public void applyRotation180Matrix() { 238 Matrix m = new Matrix(); 239 m.setRotate(180.0f, 5.f, 3.f); 240 Bitmap result = Bitmap.createBitmap(sTestImage, 0, 0, 10, 6, m, false); 241 assertEquals(result.getWidth(), 10); 242 assertEquals(result.getHeight(), 6); 243 assertTrue(result.hasGainmap()); 244 Bitmap gainmapContents = result.getGainmap().getGainmapContents(); 245 assertEquals(gainmapContents.getWidth(), 5); 246 assertEquals(gainmapContents.getHeight(), 3); 247 assertChannels(gainmapContents.getColor(0, 0), Color.pack(Color.BLACK), 0f); 248 assertChannels(gainmapContents.getColor(0, 1), Color.pack(Color.BLACK), 0f); 249 assertChannels(gainmapContents.getColor(1, 1), Color.pack(0xFFFFFFFF), 0f); 250 assertChannels(gainmapContents.getColor(2, 1), Color.pack(0xFF808080), 0f); 251 assertChannels(gainmapContents.getColor(3, 1), Color.pack(0xFF404040), 0f); 252 assertChannels(gainmapContents.getColor(4, 1), Color.pack(Color.BLACK), 0f); 253 assertChannels(gainmapContents.getColor(4, 2), Color.pack(Color.BLACK), 0f); 254 } 255 256 @Test applyRotation90Matrix()257 public void applyRotation90Matrix() { 258 Matrix m = new Matrix(); 259 m.setRotate(90.0f, 5.f, 3.f); 260 Bitmap result = Bitmap.createBitmap(sTestImage, 0, 0, 10, 6, m, false); 261 assertEquals(result.getWidth(), 6); 262 assertEquals(result.getHeight(), 10); 263 assertTrue(result.hasGainmap()); 264 Bitmap gainmapContents = result.getGainmap().getGainmapContents(); 265 assertEquals(gainmapContents.getWidth(), 3); 266 assertEquals(gainmapContents.getHeight(), 5); 267 assertChannels(gainmapContents.getColor(0, 0), Color.pack(Color.BLACK), 0f); 268 assertChannels(gainmapContents.getColor(1, 0), Color.pack(Color.BLACK), 0f); 269 assertChannels(gainmapContents.getColor(1, 1), Color.pack(0xFF404040), 0f); 270 assertChannels(gainmapContents.getColor(1, 2), Color.pack(0xFF808080), 0f); 271 assertChannels(gainmapContents.getColor(1, 3), Color.pack(0xFFFFFFFF), 0f); 272 assertChannels(gainmapContents.getColor(1, 4), Color.pack(Color.BLACK), 0f); 273 assertChannels(gainmapContents.getColor(2, 4), Color.pack(Color.BLACK), 0f); 274 } 275 } 276