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