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