1 /* <lambda>null2 * Copyright 2020 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 android.content.res.AssetManager 20 import android.graphics.Bitmap 21 import android.graphics.BitmapFactory 22 import android.graphics.Color 23 import android.graphics.ImageDecoder 24 import android.graphics.Matrix 25 import android.graphics.Rect 26 import android.media.ExifInterface 27 import android.uirendering.cts.bitmapcomparers.MSSIMComparer 28 import android.uirendering.cts.bitmapverifiers.BitmapVerifier 29 import android.uirendering.cts.bitmapverifiers.ColorVerifier 30 import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier 31 import android.uirendering.cts.bitmapverifiers.RectVerifier 32 import android.uirendering.cts.bitmapverifiers.RegionVerifier 33 import android.uirendering.cts.differencevisualizers.PassFailVisualizer 34 import android.uirendering.cts.util.BitmapDumper 35 import androidx.test.InstrumentationRegistry 36 import junitparams.JUnitParamsRunner 37 import junitparams.Parameters 38 import kotlin.test.assertEquals 39 import kotlin.test.assertTrue 40 import kotlin.test.fail 41 import org.junit.Test 42 import org.junit.runner.RunWith 43 44 @RunWith(JUnitParamsRunner::class) 45 class AImageDecoderTest { 46 init { 47 System.loadLibrary("ctsuirendering_jni") 48 } 49 50 private val ANDROID_IMAGE_DECODER_SUCCESS = 0 51 private val ANDROID_IMAGE_DECODER_INVALID_CONVERSION = -3 52 private val ANDROID_IMAGE_DECODER_INVALID_SCALE = -4 53 private val ANDROID_IMAGE_DECODER_BAD_PARAMETER = -5 54 private val ANDROID_IMAGE_DECODER_FINISHED = -10 55 private val ANDROID_IMAGE_DECODER_INVALID_STATE = -11 56 57 private val DEBUG_CAPTURE_IMAGES = false 58 59 private fun getAssets(): AssetManager { 60 return InstrumentationRegistry.getTargetContext().getAssets() 61 } 62 63 @Test 64 fun testNullDecoder() = nTestNullDecoder() 65 66 @Test 67 fun testToString() = nTestToString() 68 69 private enum class Crop { 70 Top, // Crop a section of the image that contains the top 71 Left, // Crop a section of the image that contains the left 72 None, 73 } 74 75 /** 76 * Helper class to decode a scaled, cropped image to compare to AImageDecoder. 77 * 78 * Includes properties for getting the right scale and crop values to use in 79 * AImageDecoder. 80 */ 81 private inner class DecodeAndCropper constructor( 82 image: String, 83 scale: Float, 84 crop: Crop 85 ) { 86 val bitmap: Bitmap 87 var targetWidth: Int = 0 88 private set 89 var targetHeight: Int = 0 90 private set 91 val cropRect: Rect? 92 93 init { 94 val source = ImageDecoder.createSource(getAssets(), image) 95 val tmpBm = ImageDecoder.decodeBitmap(source) { 96 decoder, info, _ -> 97 decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE 98 if (scale == 1.0f) { 99 targetWidth = info.size.width 100 targetHeight = info.size.height 101 } else { 102 targetWidth = (info.size.width * scale).toInt() 103 targetHeight = (info.size.height * scale).toInt() 104 decoder.setTargetSize(targetWidth, targetHeight) 105 } 106 } 107 cropRect = when (crop) { 108 Crop.Top -> Rect((targetWidth / 3.0f).toInt(), 0, 109 (targetWidth * 2 / 3.0f).toInt(), 110 (targetHeight / 2.0f).toInt()) 111 Crop.Left -> Rect(0, (targetHeight / 3.0f).toInt(), 112 (targetWidth / 2.0f).toInt(), 113 (targetHeight * 2 / 3.0f).toInt()) 114 Crop.None -> null 115 } 116 if (cropRect == null) { 117 bitmap = tmpBm 118 } else { 119 // Crop using Bitmap, rather than ImageDecoder, because it uses 120 // the same code as AImageDecoder for cropping. 121 bitmap = Bitmap.createBitmap(tmpBm, cropRect.left, cropRect.top, 122 cropRect.width(), cropRect.height()) 123 if (bitmap !== tmpBm) { 124 tmpBm.recycle() 125 } 126 } 127 } 128 } 129 130 // Create a Bitmap with the same size and colorspace as bitmap. 131 private fun makeEmptyBitmap(bitmap: Bitmap) = Bitmap.createBitmap( 132 bitmap.width, 133 bitmap.height, 134 bitmap.config!!, 135 true, 136 bitmap.colorSpace!! 137 ) 138 139 private fun setCrop(decoder: Long, rect: Rect): Int = with(rect) { 140 nSetCrop(decoder, left, top, right, bottom) 141 } 142 143 /** 144 * Test that all frames in the image look as expected. 145 * 146 * @param image Name of the animated image file. 147 * @param frameName Template for creating the name of the expected image 148 * file for the i'th frame. 149 * @param numFrames Total number of frames in the animated image. 150 * @param scaleFactor The factor by which to scale the image. 151 * @param crop The crop setting to use. 152 * @param mssimThreshold The minimum MSSIM value to accept as similar. Some 153 * images do not match exactly, but they've been 154 * manually verified to look the same. 155 * @param testName Optional name of the calling test for BitmapDumper. 156 */ 157 private fun decodeAndCropFrames( 158 image: String, 159 frameName: String, 160 numFrames: Int, 161 scaleFactor: Float, 162 crop: Crop, 163 mssimThreshold: Double, 164 testName: String = "" 165 ) { 166 val decodeAndCropper = DecodeAndCropper(image, scaleFactor, crop) 167 var expectedBm = decodeAndCropper.bitmap 168 169 val asset = nOpenAsset(getAssets(), image) 170 val decoder = nCreateFromAsset(asset) 171 if (scaleFactor != 1.0f) { 172 with(decodeAndCropper) { 173 assertEquals( 174 nSetTargetSize(decoder, targetWidth, targetHeight), 175 ANDROID_IMAGE_DECODER_SUCCESS 176 ) 177 } 178 } 179 with(decodeAndCropper.cropRect) { 180 this?.let { 181 assertEquals(setCrop(decoder, this), ANDROID_IMAGE_DECODER_SUCCESS) 182 } 183 } 184 185 val testBm = makeEmptyBitmap(decodeAndCropper.bitmap) 186 187 var i = 0 188 while (true) { 189 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 190 val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold)) 191 if (!verifier.verify(testBm)) { 192 if (DEBUG_CAPTURE_IMAGES) { 193 BitmapDumper.dumpBitmaps(expectedBm, testBm, PassFailVisualizer()) 194 } 195 fail("$image has mismatch in frame $i") 196 } 197 expectedBm.recycle() 198 199 i++ 200 when (val result = nAdvanceFrame(decoder)) { 201 ANDROID_IMAGE_DECODER_SUCCESS -> { 202 assertTrue(i < numFrames, "Unexpected frame $i in $image") 203 expectedBm = DecodeAndCropper(frameName.format(i), scaleFactor, crop).bitmap 204 } 205 ANDROID_IMAGE_DECODER_FINISHED -> { 206 assertEquals(i, numFrames, "Expected $numFrames frames in $image; found $i") 207 break 208 } 209 else -> fail("Unexpected error $result when advancing $image to frame $i") 210 } 211 } 212 213 nDeleteDecoder(decoder) 214 nCloseAsset(asset) 215 } 216 217 fun animationsAndFrames() = arrayOf( 218 arrayOf<Any>("animated.gif", "animated_%03d.gif", 4), 219 arrayOf<Any>("animated_webp.webp", "animated_%03d.gif", 4), 220 arrayOf<Any>("required_gif.gif", "required_%03d.png", 7), 221 arrayOf<Any>("required_webp.webp", "required_%03d.png", 7), 222 arrayOf<Any>("alphabetAnim.gif", "alphabetAnim_%03d.png", 13), 223 arrayOf<Any>("blendBG.webp", "blendBG_%03d.png", 7), 224 arrayOf<Any>("stoplight.webp", "stoplight_%03d.png", 3) 225 ) 226 227 @Test 228 @Parameters(method = "animationsAndFrames") 229 fun testDecodeFrames(image: String, frameName: String, numFrames: Int) { 230 decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.None, .955) 231 } 232 233 @Test 234 @Parameters(method = "animationsAndFrames") 235 fun testDecodeFramesScaleDown(image: String, frameName: String, numFrames: Int) { 236 // Perceptually, this image looks reasonable, but the MSSIM is low enough to be 237 // meaningless. It has been manually verified. 238 if (image == "alphabetAnim.gif") return 239 decodeAndCropFrames( 240 image, 241 frameName, 242 numFrames, 243 .5f, 244 Crop.None, 245 .749, 246 "testDecodeFramesScaleDown" 247 ) 248 } 249 250 @Test 251 @Parameters(method = "animationsAndFrames") 252 fun testDecodeFramesScaleDown2(image: String, frameName: String, numFrames: Int) { 253 decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.None, .749) 254 } 255 256 @Test 257 @Parameters(method = "animationsAndFrames") 258 fun testDecodeFramesScaleUp(image: String, frameName: String, numFrames: Int) { 259 decodeAndCropFrames(image, frameName, numFrames, 2.0f, Crop.None, .875) 260 } 261 262 @Test 263 @Parameters(method = "animationsAndFrames") 264 fun testDecodeFramesAndCropTop(image: String, frameName: String, numFrames: Int) { 265 decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Top, .934) 266 } 267 268 @Test 269 @Parameters(method = "animationsAndFrames") 270 fun testDecodeFramesAndCropTopScaleDown(image: String, frameName: String, numFrames: Int) { 271 // Perceptually, this image looks reasonable, but the MSSIM is low enough to be 272 // meaningless. It has been manually verified. 273 if (image == "alphabetAnim.gif") return 274 decodeAndCropFrames( 275 image, 276 frameName, 277 numFrames, 278 .5f, 279 Crop.Top, 280 .749, 281 "testDecodeFramesAndCropTopScaleDown" 282 ) 283 } 284 285 @Test 286 @Parameters(method = "animationsAndFrames") 287 fun testDecodeFramesAndCropTopScaleDown2(image: String, frameName: String, numFrames: Int) { 288 decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Top, .749) 289 } 290 291 @Test 292 @Parameters(method = "animationsAndFrames") 293 fun testDecodeFramesAndCropTopScaleUp(image: String, frameName: String, numFrames: Int) { 294 decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Top, .908) 295 } 296 297 @Test 298 @Parameters(method = "animationsAndFrames") 299 fun testDecodeFramesAndCropLeft(image: String, frameName: String, numFrames: Int) { 300 decodeAndCropFrames(image, frameName, numFrames, 1.0f, Crop.Left, .924) 301 } 302 303 @Test 304 @Parameters(method = "animationsAndFrames") 305 fun testDecodeFramesAndCropLeftScaleDown(image: String, frameName: String, numFrames: Int) { 306 // Perceptually, this image looks reasonable, but the MSSIM is low enough to be 307 // meaningless. It has been manually verified. 308 if (image == "alphabetAnim.gif") return 309 decodeAndCropFrames( 310 image, 311 frameName, 312 numFrames, 313 .5f, 314 Crop.Left, 315 .596, 316 "testDecodeFramesAndCropLeftScaleDown" 317 ) 318 } 319 320 @Test 321 @Parameters(method = "animationsAndFrames") 322 fun testDecodeFramesAndCropLeftScaleDown2(image: String, frameName: String, numFrames: Int) { 323 decodeAndCropFrames(image, frameName, numFrames, .75f, Crop.Left, .596) 324 } 325 326 @Test 327 @Parameters(method = "animationsAndFrames") 328 fun testDecodeFramesAndCropLeftScaleUp(image: String, frameName: String, numFrames: Int) { 329 decodeAndCropFrames(image, frameName, numFrames, 3.0f, Crop.Left, .894) 330 } 331 332 @Test 333 @Parameters(method = "animationsAndFrames") 334 fun testRewind(image: String, unused: String, numFrames: Int) { 335 val frame0 = with(ImageDecoder.createSource(getAssets(), image)) { 336 ImageDecoder.decodeBitmap(this) { 337 decoder, _, _ -> 338 decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE 339 } 340 } 341 342 // Regardless of the current frame, calling rewind and decoding should 343 // look like frame_0. 344 for (framesBeforeReset in 0 until numFrames) { 345 val asset = nOpenAsset(getAssets(), image) 346 val decoder = nCreateFromAsset(asset) 347 val testBm = makeEmptyBitmap(frame0) 348 for (i in 1..framesBeforeReset) { 349 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 350 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder)) 351 } 352 353 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder)) 354 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 355 356 val verifier = GoldenImageVerifier(frame0, MSSIMComparer(1.0)) 357 assertTrue( 358 verifier.verify(testBm), 359 "Mismatch in $image after " + 360 "decoding $framesBeforeReset and then rewinding!" 361 ) 362 363 nDeleteDecoder(decoder) 364 nCloseAsset(asset) 365 } 366 } 367 368 @Test 369 @Parameters(method = "animationsAndFrames") 370 fun testDecodeReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) { 371 val asset = nOpenAsset(getAssets(), image) 372 val decoder = nCreateFromAsset(asset) 373 for (i in 0 until (numFrames - 1)) { 374 assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS) 375 } 376 377 assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED) 378 379 // Create a Bitmap to decode into and verify that no decoding occurred. 380 val width = nGetWidth(decoder) 381 val height = nGetHeight(decoder) 382 val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true) 383 nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_FINISHED) 384 385 nDeleteDecoder(decoder) 386 nCloseAsset(asset) 387 388 // Every pixel should be transparent black, as no decoding happened. 389 assertTrue(ColorVerifier(0, 0).verify(bitmap)) 390 bitmap.recycle() 391 } 392 393 @Test 394 @Parameters(method = "animationsAndFrames") 395 fun testAdvanceReturnsFinishedAtEnd(image: String, unused: String, numFrames: Int) { 396 val asset = nOpenAsset(getAssets(), image) 397 val decoder = nCreateFromAsset(asset) 398 for (i in 0 until (numFrames - 1)) { 399 assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS) 400 } 401 402 for (i in 0..1000) { 403 assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_FINISHED) 404 } 405 406 nDeleteDecoder(decoder) 407 nCloseAsset(asset) 408 } 409 410 fun nonAnimatedAssets() = arrayOf( 411 "blue-16bit-prophoto.png", 412 "green-p3.png", 413 "linear-rgba16f.png", 414 "orange-prophotorgb.png", 415 "animated_001.gif", 416 "animated_002.gif", 417 "sunset1.jpg" 418 ) 419 420 @Test 421 @Parameters(method = "nonAnimatedAssets") 422 fun testAdvanceFrameFailsNonAnimated(image: String) { 423 val asset = nOpenAsset(getAssets(), image) 424 val decoder = nCreateFromAsset(asset) 425 assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nAdvanceFrame(decoder)) 426 nDeleteDecoder(decoder) 427 nCloseAsset(asset) 428 } 429 430 @Test 431 @Parameters(method = "nonAnimatedAssets") 432 fun testRewindFailsNonAnimated(image: String) { 433 val asset = nOpenAsset(getAssets(), image) 434 val decoder = nCreateFromAsset(asset) 435 assertEquals(ANDROID_IMAGE_DECODER_BAD_PARAMETER, nRewind(decoder)) 436 nDeleteDecoder(decoder) 437 nCloseAsset(asset) 438 } 439 440 fun imagesAndSetters(): ArrayList<Any> { 441 val setters = arrayOf<(Long) -> Int>( 442 { decoder -> nSetUnpremultipliedRequired(decoder, true) }, 443 { decoder -> 444 val rect = Rect(0, 0, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2) 445 setCrop(decoder, rect) 446 }, 447 { decoder -> 448 val ANDROID_BITMAP_FORMAT_RGBA_F16 = 9 449 nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_F16) 450 }, 451 { decoder -> 452 nSetTargetSize(decoder, nGetWidth(decoder) / 2, nGetHeight(decoder) / 2) 453 }, 454 { decoder -> 455 val ADATASPACE_DISPLAY_P3 = 143261696 456 nSetDataSpace(decoder, ADATASPACE_DISPLAY_P3) 457 } 458 ) 459 val list = ArrayList<Any>() 460 for (animations in animationsAndFrames()) { 461 for (setter in setters) { 462 list.add(arrayOf(animations[0], animations[2], setter)) 463 } 464 } 465 return list 466 } 467 468 @Test 469 @Parameters(method = "imagesAndSetters") 470 fun testSettersFailOnLatterFrames(image: String, numFrames: Int, setter: (Long) -> Int) { 471 // Verify that the setter succeeds on the first frame. 472 with(nOpenAsset(getAssets(), image)) { 473 val decoder = nCreateFromAsset(this) 474 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder)) 475 nDeleteDecoder(decoder) 476 nCloseAsset(this) 477 } 478 479 for (framesBeforeSet in 1 until numFrames) { 480 val asset = nOpenAsset(getAssets(), image) 481 val decoder = nCreateFromAsset(asset) 482 for (i in 1..framesBeforeSet) { 483 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder)) 484 } 485 486 // Not on the first frame, so the setter fails. 487 assertEquals(ANDROID_IMAGE_DECODER_INVALID_STATE, setter(decoder)) 488 489 // Rewind to the beginning. Now the setter can succeed. 490 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder)) 491 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, setter(decoder)) 492 493 nDeleteDecoder(decoder) 494 nCloseAsset(asset) 495 } 496 } 497 498 fun unpremulTestFiles() = arrayOf( 499 "alphabetAnim.gif", 500 "animated_webp.webp", 501 "stoplight.webp" 502 ) 503 504 @Test 505 @Parameters(method = "unpremulTestFiles") 506 fun testUnpremul(image: String) { 507 val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) { 508 ImageDecoder.decodeBitmap(this) { 509 decoder, _, _ -> 510 decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE 511 decoder.setUnpremultipliedRequired(true) 512 } 513 } 514 515 val testBm = makeEmptyBitmap(expectedBm) 516 517 val asset = nOpenAsset(getAssets(), image) 518 val decoder = nCreateFromAsset(asset) 519 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true)) 520 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 521 522 val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0)) 523 assertTrue(verifier.verify(testBm), "$image did not match in unpremul") 524 525 nDeleteDecoder(decoder) 526 nCloseAsset(asset) 527 } 528 529 fun imagesWithAlpha() = arrayOf( 530 "alphabetAnim.gif", 531 "animated_webp.webp", 532 "animated.gif" 533 ) 534 535 @Test 536 @Parameters(method = "imagesWithAlpha") 537 fun testUnpremulThenScaleFailsWithAlpha(image: String) { 538 val asset = nOpenAsset(getAssets(), image) 539 val decoder = nCreateFromAsset(asset) 540 val width = nGetWidth(decoder) 541 val height = nGetHeight(decoder) 542 543 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true)) 544 assertEquals( 545 ANDROID_IMAGE_DECODER_INVALID_SCALE, 546 nSetTargetSize(decoder, width * 2, height * 2) 547 ) 548 nDeleteDecoder(decoder) 549 nCloseAsset(asset) 550 } 551 552 @Test 553 @Parameters(method = "imagesWithAlpha") 554 fun testScaleThenUnpremulFailsWithAlpha(image: String) { 555 val asset = nOpenAsset(getAssets(), image) 556 val decoder = nCreateFromAsset(asset) 557 val width = nGetWidth(decoder) 558 val height = nGetHeight(decoder) 559 560 assertEquals( 561 ANDROID_IMAGE_DECODER_SUCCESS, 562 nSetTargetSize(decoder, width * 2, height * 2) 563 ) 564 assertEquals( 565 ANDROID_IMAGE_DECODER_INVALID_CONVERSION, 566 nSetUnpremultipliedRequired(decoder, true) 567 ) 568 nDeleteDecoder(decoder) 569 nCloseAsset(asset) 570 } 571 572 fun opaquePlusScale(): ArrayList<Any> { 573 val opaqueImages = arrayOf("sunset1.jpg", "blendBG.webp", "stoplight.webp") 574 val scales = arrayOf(.5f, .75f, 2.0f) 575 val list = ArrayList<Any>() 576 for (image in opaqueImages) { 577 for (scale in scales) { 578 list.add(arrayOf(image, scale)) 579 } 580 } 581 return list 582 } 583 584 @Test 585 @Parameters(method = "opaquePlusScale") 586 fun testUnpremulPlusScaleOpaque(image: String, scale: Float) { 587 val expectedBm = with(ImageDecoder.createSource(getAssets(), image)) { 588 ImageDecoder.decodeBitmap(this) { 589 decoder, info, _ -> 590 decoder.isUnpremultipliedRequired = true 591 decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE 592 val width = (info.size.width * scale).toInt() 593 val height = (info.size.height * scale).toInt() 594 decoder.setTargetSize(width, height) 595 } 596 } 597 val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(1.0)) 598 599 // Flipping the order of setting unpremul and scaling results in taking 600 // a different code path. Ensure both succeed. 601 val ops = listOf( 602 { decoder: Long -> nSetUnpremultipliedRequired(decoder, true) }, 603 { decoder: Long -> nSetTargetSize(decoder, expectedBm.width, expectedBm.height) } 604 ) 605 606 for (order in setOf(ops, ops.asReversed())) { 607 val testBm = makeEmptyBitmap(expectedBm) 608 val asset = nOpenAsset(getAssets(), image) 609 val decoder = nCreateFromAsset(asset) 610 for (op in order) { 611 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, op(decoder)) 612 } 613 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 614 assertTrue(verifier.verify(testBm)) 615 616 nDeleteDecoder(decoder) 617 nCloseAsset(asset) 618 testBm.recycle() 619 } 620 expectedBm.recycle() 621 } 622 623 @Test 624 fun testUnpremulPlusScaleWithFrameWithAlpha() { 625 // The first frame of this image is opaque, so unpremul + scale succeeds. 626 // But frame 3 has alpha, so decoding it with unpremul + scale fails. 627 val image = "blendBG.webp" 628 val scale = 2.0f 629 val asset = nOpenAsset(getAssets(), image) 630 val decoder = nCreateFromAsset(asset) 631 val width = (nGetWidth(decoder) * scale).toInt() 632 val height = (nGetHeight(decoder) * scale).toInt() 633 634 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetUnpremultipliedRequired(decoder, true)) 635 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nSetTargetSize(decoder, width, height)) 636 637 val testBm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true) 638 for (i in 0 until 3) { 639 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 640 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nAdvanceFrame(decoder)) 641 } 642 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_INVALID_SCALE) 643 644 nDeleteDecoder(decoder) 645 nCloseAsset(asset) 646 } 647 648 @Test 649 @Parameters(method = "nonAnimatedAssets") 650 fun testGetFrameInfoSucceedsNonAnimated(image: String) { 651 val asset = nOpenAsset(getAssets(), image) 652 val decoder = nCreateFromAsset(asset) 653 val frameInfo = nCreateFrameInfo() 654 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo)) 655 656 if (image.startsWith("animated")) { 657 // Although these images have only one frame, they still contain encoded frame info. 658 val ANDROID_IMAGE_DECODER_INFINITE = Integer.MAX_VALUE 659 assertEquals(ANDROID_IMAGE_DECODER_INFINITE, nGetRepeatCount(decoder)) 660 assertEquals(250_000_000L, nGetDuration(frameInfo)) 661 assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, nGetDisposeOp(frameInfo)) 662 } else { 663 // Since these are not animated and have no encoded frame info, they should use 664 // defaults. 665 assertEquals(0, nGetRepeatCount(decoder)) 666 assertEquals(0L, nGetDuration(frameInfo)) 667 assertEquals(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, nGetDisposeOp(frameInfo)) 668 } 669 670 nTestGetFrameRect(frameInfo, 0, 0, nGetWidth(decoder), nGetHeight(decoder)) 671 if (image.endsWith("gif")) { 672 // GIFs do not support SRC, so they always report SRC_OVER. 673 assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, nGetBlendOp(frameInfo)) 674 } else { 675 assertEquals(ANDROID_IMAGE_DECODER_BLEND_OP_SRC, nGetBlendOp(frameInfo)) 676 } 677 assertEquals(nGetAlpha(decoder), nGetFrameAlpha(frameInfo)) 678 679 nDeleteFrameInfo(frameInfo) 680 nDeleteDecoder(decoder) 681 nCloseAsset(asset) 682 } 683 684 @Test 685 fun testNullFrameInfo() = nTestNullFrameInfo(getAssets(), "animated.gif") 686 687 @Test 688 @Parameters(method = "animationsAndFrames") 689 fun testGetFrameInfo(image: String, frameName: String, numFrames: Int) { 690 val asset = nOpenAsset(getAssets(), image) 691 val decoder = nCreateFromAsset(asset) 692 val frameInfo = nCreateFrameInfo() 693 for (i in 0 until numFrames) { 694 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nGetFrameInfo(decoder, frameInfo)) 695 val result = nAdvanceFrame(decoder) 696 val expectedResult = if (i == numFrames - 1) { 697 ANDROID_IMAGE_DECODER_FINISHED 698 } else { 699 ANDROID_IMAGE_DECODER_SUCCESS 700 } 701 assertEquals(expectedResult, result) 702 } 703 704 assertEquals(ANDROID_IMAGE_DECODER_FINISHED, nGetFrameInfo(decoder, frameInfo)) 705 706 nDeleteFrameInfo(frameInfo) 707 nDeleteDecoder(decoder) 708 nCloseAsset(asset) 709 } 710 711 fun animationsAndDurations() = arrayOf( 712 arrayOf<Any>("animated.gif", LongArray(4) { 250_000_000 }), 713 arrayOf<Any>("animated_webp.webp", LongArray(4) { 250_000_000 }), 714 arrayOf<Any>("required_gif.gif", LongArray(7) { 100_000_000 }), 715 arrayOf<Any>("required_webp.webp", LongArray(7) { 100_000_000 }), 716 arrayOf<Any>("alphabetAnim.gif", LongArray(13) { 100_000_000 }), 717 arrayOf<Any>("blendBG.webp", longArrayOf( 718 525_000_000, 719 500_000_000, 720 525_000_000, 721 437_000_000, 722 609_000_000, 723 729_000_000, 724 444_000_000 725 )), 726 arrayOf<Any>("stoplight.webp", longArrayOf( 727 1_000_000_000, 728 500_000_000, 729 1_000_000_000 730 )) 731 ) 732 733 @Test 734 @Parameters(method = "animationsAndDurations") 735 fun testDurations(image: String, durations: LongArray) = testFrameInfo(image) { 736 frameInfo, i -> 737 assertEquals(durations[i], nGetDuration(frameInfo)) 738 } 739 740 /** 741 * Iterate through all frames and call a lambda that tests an individual frame's info. 742 * 743 * @param image Name of the image asset to test 744 * @param test Lambda with two parameters: A pointer to the native decoder, and the 745 * current frame number. 746 */ 747 private fun testFrameInfo(image: String, test: (Long, Int) -> Unit) { 748 val asset = nOpenAsset(getAssets(), image) 749 val decoder = nCreateFromAsset(asset) 750 val frameInfo = nCreateFrameInfo() 751 var frame = 0 752 do { 753 assertEquals( 754 ANDROID_IMAGE_DECODER_SUCCESS, 755 nGetFrameInfo(decoder, frameInfo), 756 "Failed to getFrameInfo for frame $frame of $image!" 757 ) 758 test(frameInfo, frame) 759 frame++ 760 } while (ANDROID_IMAGE_DECODER_SUCCESS == nAdvanceFrame(decoder)) 761 762 nDeleteFrameInfo(frameInfo) 763 nDeleteDecoder(decoder) 764 nCloseAsset(asset) 765 } 766 767 fun animationsAndRects() = arrayOf( 768 // Each group of four Ints represents a frame's rectangle 769 arrayOf<Any>("animated.gif", intArrayOf(0, 0, 278, 183, 770 0, 0, 278, 183, 771 0, 0, 278, 183, 772 0, 0, 278, 183)), 773 arrayOf<Any>("animated_webp.webp", intArrayOf(0, 0, 278, 183, 774 0, 0, 278, 183, 775 0, 0, 278, 183, 776 0, 0, 278, 183)), 777 arrayOf<Any>("required_gif.gif", intArrayOf(0, 0, 100, 100, 778 0, 0, 75, 75, 779 0, 0, 50, 50, 780 0, 0, 60, 60, 781 0, 0, 100, 100, 782 0, 0, 50, 50, 783 0, 0, 75, 75)), 784 arrayOf<Any>("required_webp.webp", intArrayOf(0, 0, 100, 100, 785 0, 0, 75, 75, 786 0, 0, 50, 50, 787 0, 0, 60, 60, 788 0, 0, 100, 100, 789 0, 0, 50, 50, 790 0, 0, 75, 75)), 791 arrayOf<Any>("alphabetAnim.gif", intArrayOf(25, 25, 75, 75, 792 25, 25, 75, 75, 793 25, 25, 75, 75, 794 37, 37, 62, 62, 795 37, 37, 62, 62, 796 25, 25, 75, 75, 797 0, 0, 50, 50, 798 0, 0, 100, 100, 799 25, 25, 75, 75, 800 25, 25, 75, 75, 801 0, 0, 100, 100, 802 25, 25, 75, 75, 803 37, 37, 62, 62)), 804 805 arrayOf<Any>("blendBG.webp", intArrayOf(0, 0, 200, 200, 806 0, 0, 200, 200, 807 0, 0, 200, 200, 808 0, 0, 200, 200, 809 0, 0, 200, 200, 810 100, 100, 200, 200, 811 100, 100, 200, 200)), 812 arrayOf<Any>("stoplight.webp", intArrayOf(0, 0, 145, 55, 813 0, 0, 145, 55, 814 0, 0, 145, 55)) 815 ) 816 817 @Test 818 @Parameters(method = "animationsAndRects") 819 fun testFrameRects(image: String, rects: IntArray) = testFrameInfo(image) { 820 frameInfo, i -> 821 val left = rects[i * 4] 822 val top = rects[i * 4 + 1] 823 val right = rects[i * 4 + 2] 824 val bottom = rects[i * 4 + 3] 825 try { 826 nTestGetFrameRect(frameInfo, left, top, right, bottom) 827 } catch (t: Throwable) { 828 throw AssertionError("$image, frame $i: ${t.message}", t) 829 } 830 } 831 832 fun animationsAndAlphas() = arrayOf( 833 arrayOf<Any>("animated.gif", BooleanArray(4) { true }), 834 arrayOf<Any>("animated_webp.webp", BooleanArray(4) { true }), 835 arrayOf<Any>("required_gif.gif", booleanArrayOf( 836 false, 837 true, 838 true, 839 true, 840 true, 841 true, 842 true, 843 true 844 )), 845 arrayOf<Any>("required_webp.webp", BooleanArray(7) { false }), 846 arrayOf<Any>("alphabetAnim.gif", booleanArrayOf(true, false, true, false, 847 true, true, true, true, true, true, true, true, true)), 848 arrayOf<Any>("blendBG.webp", booleanArrayOf( 849 false, 850 true, 851 false, 852 true, 853 false, 854 true, 855 true 856 )), 857 arrayOf<Any>("stoplight.webp", BooleanArray(3) { false }) 858 ) 859 860 @Test 861 @Parameters(method = "animationsAndAlphas") 862 fun testAlphas(image: String, alphas: BooleanArray) = testFrameInfo(image) { 863 frameInfo, i -> 864 assertEquals( 865 alphas[i], 866 nGetFrameAlpha(frameInfo), 867 "Mismatch in alpha for $image frame $i " + 868 "expected ${alphas[i]}" 869 ) 870 } 871 872 private val ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE = 1 873 private val ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND = 2 874 private val ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS = 3 875 876 fun animationsAndDisposeOps() = arrayOf( 877 arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND }), 878 arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }), 879 arrayOf<Any>("required_gif.gif", intArrayOf( 880 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 881 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, 882 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 883 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 884 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 885 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, 886 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE 887 )), 888 arrayOf<Any>("required_webp.webp", intArrayOf( 889 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 890 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, 891 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 892 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 893 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 894 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, 895 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE 896 )), 897 arrayOf<Any>("alphabetAnim.gif", intArrayOf(ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 898 ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS, 899 ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS, 900 ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS, 901 ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS, 902 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 903 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, 904 ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 905 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, ANDROID_IMAGE_DECODER_DISPOSE_OP_BACKGROUND, 906 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE)), 907 arrayOf<Any>("blendBG.webp", IntArray(7) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }), 908 arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE }) 909 ) 910 911 @Test 912 @Parameters(method = "animationsAndDisposeOps") 913 fun testDisposeOps(image: String, disposeOps: IntArray) = testFrameInfo(image) { 914 frameInfo, i -> 915 assertEquals(disposeOps[i], nGetDisposeOp(frameInfo)) 916 } 917 918 private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC = 1 919 private val ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER = 2 920 921 fun animationsAndBlendOps() = arrayOf( 922 arrayOf<Any>("animated.gif", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }), 923 arrayOf<Any>("animated_webp.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC }), 924 arrayOf<Any>("required_gif.gif", IntArray(7) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }), 925 arrayOf<Any>("required_webp.webp", intArrayOf( 926 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 927 ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, 928 ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, 929 ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, 930 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 931 ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, 932 ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER 933 )), 934 arrayOf<Any>("alphabetAnim.gif", IntArray(13) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }), 935 arrayOf<Any>("blendBG.webp", intArrayOf( 936 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 937 ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER, 938 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 939 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 940 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 941 ANDROID_IMAGE_DECODER_BLEND_OP_SRC, 942 ANDROID_IMAGE_DECODER_BLEND_OP_SRC 943 )), 944 arrayOf<Any>("stoplight.webp", IntArray(4) { ANDROID_IMAGE_DECODER_BLEND_OP_SRC_OVER }) 945 ) 946 947 @Test 948 @Parameters(method = "animationsAndBlendOps") 949 fun testBlendOps(image: String, blendOps: IntArray) = testFrameInfo(image) { 950 frameInfo, i -> 951 assertEquals( 952 blendOps[i], 953 nGetBlendOp(frameInfo), 954 "Mismatch in blend op for $image " + 955 "frame $i, expected: ${blendOps[i]}" 956 ) 957 } 958 959 @Test 960 fun testHandleDisposePrevious() { 961 // The first frame is ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, followed by a single 962 // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS frame. The third frame looks different 963 // depending on whether that is respected. 964 val image = "RestorePrevious.gif" 965 val disposeOps = intArrayOf( 966 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE, 967 ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS, 968 ANDROID_IMAGE_DECODER_DISPOSE_OP_NONE 969 ) 970 val asset = nOpenAsset(getAssets(), image) 971 val decoder = nCreateFromAsset(asset) 972 973 val width = nGetWidth(decoder) 974 val height = nGetHeight(decoder) 975 val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888, true) 976 977 val verifiers = arrayOf<BitmapVerifier>( 978 ColorVerifier(Color.BLACK, 0), 979 RectVerifier(Color.BLACK, Color.RED, Rect(0, 0, 100, 80), 0), 980 RectVerifier(Color.BLACK, Color.GREEN, Rect(0, 0, 100, 50), 0) 981 ) 982 983 with(nCreateFrameInfo()) { 984 for (i in 0..2) { 985 nGetFrameInfo(decoder, this) 986 assertEquals(disposeOps[i], nGetDisposeOp(this)) 987 988 nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS) 989 assertTrue(verifiers[i].verify(bitmap)) 990 nAdvanceFrame(decoder) 991 } 992 nDeleteFrameInfo(this) 993 } 994 995 // Now redecode without letting AImageDecoder handle 996 // ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS. 997 bitmap.eraseColor(Color.TRANSPARENT) 998 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder)) 999 nSetHandleDisposePrevious(decoder, false) 1000 1001 // If the client does not handle ANDROID_IMAGE_DECODER_DISPOSE_OP_PREVIOUS 1002 // the final frame does not match. 1003 for (i in 0..2) { 1004 nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS) 1005 assertEquals(i != 2, verifiers[i].verify(bitmap)) 1006 1007 if (i == 2) { 1008 // Not only can we verify that frame 2 does not look as expected, but it 1009 // should look as if we decoded frame 1 and did not revert it. 1010 val verifier = RegionVerifier() 1011 verifier.addVerifier(Rect(0, 0, 100, 50), ColorVerifier(Color.GREEN, 0)) 1012 verifier.addVerifier(Rect(0, 50, 100, 80), ColorVerifier(Color.RED, 0)) 1013 verifier.addVerifier(Rect(0, 80, 100, 100), ColorVerifier(Color.BLACK, 0)) 1014 assertTrue(verifier.verify(bitmap)) 1015 } 1016 nAdvanceFrame(decoder) 1017 } 1018 1019 // Now redecode and manually store/restore the first frame. 1020 bitmap.eraseColor(Color.TRANSPARENT) 1021 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder)) 1022 nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS) 1023 val storedFrame = bitmap 1024 for (i in 1..2) { 1025 assertEquals(nAdvanceFrame(decoder), ANDROID_IMAGE_DECODER_SUCCESS) 1026 val frame = storedFrame.copy(storedFrame.config!!, true) 1027 nDecode(decoder, frame, ANDROID_IMAGE_DECODER_SUCCESS) 1028 assertTrue(verifiers[i].verify(frame)) 1029 frame.recycle() 1030 } 1031 1032 // This setting can be switched back, so that AImageDecoder handles it. 1033 bitmap.eraseColor(Color.TRANSPARENT) 1034 assertEquals(ANDROID_IMAGE_DECODER_SUCCESS, nRewind(decoder)) 1035 nSetHandleDisposePrevious(decoder, true) 1036 1037 for (i in 0..2) { 1038 nDecode(decoder, bitmap, ANDROID_IMAGE_DECODER_SUCCESS) 1039 assertTrue(verifiers[i].verify(bitmap)) 1040 nAdvanceFrame(decoder) 1041 } 1042 1043 bitmap.recycle() 1044 nDeleteDecoder(decoder) 1045 nCloseAsset(asset) 1046 } 1047 1048 @Test 1049 @Parameters(method = "animationsAndAlphas") 1050 fun test565NoAnimation(image: String, alphas: BooleanArray) { 1051 val asset = nOpenAsset(getAssets(), image) 1052 val decoder = nCreateFromAsset(asset) 1053 val ANDROID_BITMAP_FORMAT_RGB_565 = 4 1054 if (alphas[0]) { 1055 assertEquals( 1056 ANDROID_IMAGE_DECODER_INVALID_CONVERSION, 1057 nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGB_565) 1058 ) 1059 } else { 1060 assertEquals( 1061 ANDROID_IMAGE_DECODER_SUCCESS, 1062 nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGB_565) 1063 ) 1064 assertEquals( 1065 ANDROID_IMAGE_DECODER_INVALID_STATE, 1066 nAdvanceFrame(decoder) 1067 ) 1068 } 1069 1070 nDeleteDecoder(decoder) 1071 nCloseAsset(asset) 1072 } 1073 1074 private fun handleRotation(original: Bitmap, image: String): Bitmap { 1075 // ExifInterface does not support GIF. 1076 if (image.endsWith("gif")) return original 1077 1078 val inputStream = getAssets().open(image) 1079 val exifInterface = ExifInterface(inputStream) 1080 var rotation = 0 1081 when (exifInterface.getAttributeInt( 1082 ExifInterface.TAG_ORIENTATION, 1083 ExifInterface.ORIENTATION_NORMAL 1084 )) { 1085 ExifInterface.ORIENTATION_NORMAL, ExifInterface.ORIENTATION_UNDEFINED -> return original 1086 ExifInterface.ORIENTATION_ROTATE_90 -> rotation = 90 1087 ExifInterface.ORIENTATION_ROTATE_180 -> rotation = 180 1088 ExifInterface.ORIENTATION_ROTATE_270 -> rotation = 270 1089 else -> fail("Unexpected orientation for $image!") 1090 } 1091 1092 val m = Matrix() 1093 m.setRotate(rotation.toFloat(), original.width / 2.0f, original.height / 2.0f) 1094 return Bitmap.createBitmap(original, 0, 0, original.width, original.height, m, false) 1095 } 1096 1097 private fun decodeF16(image: String): Bitmap { 1098 val options = BitmapFactory.Options() 1099 options.inPreferredConfig = Bitmap.Config.RGBA_F16 1100 val inputStream = getAssets().open(image) 1101 val bm = BitmapFactory.decodeStream(inputStream, null, options) 1102 if (bm == null) { 1103 fail("Failed to decode $image to RGBA_F16!") 1104 } 1105 return bm 1106 } 1107 1108 @Test 1109 @Parameters(method = "animationsAndFrames") 1110 fun testDecodeFramesF16(image: String, frameName: String, numFrames: Int) { 1111 var expectedBm = handleRotation(decodeF16(image), image) 1112 1113 val asset = nOpenAsset(getAssets(), image) 1114 val decoder = nCreateFromAsset(asset) 1115 val ANDROID_BITMAP_FORMAT_RGBA_F16 = 9 1116 nSetAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_F16) 1117 1118 val testBm = makeEmptyBitmap(expectedBm) 1119 1120 val mssimThreshold = .95 1121 var i = 0 1122 while (true) { 1123 nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS) 1124 val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold)) 1125 assertTrue(verifier.verify(testBm), "$image has mismatch in frame $i") 1126 expectedBm.recycle() 1127 1128 i++ 1129 when (val result = nAdvanceFrame(decoder)) { 1130 ANDROID_IMAGE_DECODER_SUCCESS -> { 1131 assertTrue(i < numFrames, "Unexpected frame $i in $image") 1132 expectedBm = decodeF16(frameName.format(i)) 1133 } 1134 ANDROID_IMAGE_DECODER_FINISHED -> { 1135 assertEquals(i, numFrames, "Expected $numFrames frames in $image; found $i") 1136 break 1137 } 1138 else -> fail("Unexpected error $result when advancing $image to frame $i") 1139 } 1140 } 1141 1142 nDeleteDecoder(decoder) 1143 nCloseAsset(asset) 1144 } 1145 1146 private external fun nTestNullDecoder() 1147 private external fun nTestToString() 1148 private external fun nOpenAsset(assets: AssetManager, name: String): Long 1149 private external fun nCloseAsset(asset: Long) 1150 private external fun nCreateFromAsset(asset: Long): Long 1151 private external fun nGetWidth(decoder: Long): Int 1152 private external fun nGetHeight(decoder: Long): Int 1153 private external fun nDeleteDecoder(decoder: Long) 1154 private external fun nSetTargetSize(decoder: Long, width: Int, height: Int): Int 1155 private external fun nSetCrop(decoder: Long, left: Int, top: Int, right: Int, bottom: Int): Int 1156 private external fun nDecode(decoder: Long, dst: Bitmap, expectedResult: Int) 1157 private external fun nAdvanceFrame(decoder: Long): Int 1158 private external fun nRewind(decoder: Long): Int 1159 private external fun nSetUnpremultipliedRequired(decoder: Long, required: Boolean): Int 1160 private external fun nSetAndroidBitmapFormat(decoder: Long, format: Int): Int 1161 private external fun nSetDataSpace(decoder: Long, format: Int): Int 1162 private external fun nCreateFrameInfo(): Long 1163 private external fun nDeleteFrameInfo(frameInfo: Long) 1164 private external fun nGetFrameInfo(decoder: Long, frameInfo: Long): Int 1165 private external fun nTestNullFrameInfo(assets: AssetManager, name: String) 1166 private external fun nGetDuration(frameInfo: Long): Long 1167 private external fun nTestGetFrameRect( 1168 frameInfo: Long, 1169 expectedLeft: Int, 1170 expectedTop: Int, 1171 expectedRight: Int, 1172 expectedBottom: Int 1173 ) 1174 private external fun nGetFrameAlpha(frameInfo: Long): Boolean 1175 private external fun nGetAlpha(decoder: Long): Boolean 1176 private external fun nGetDisposeOp(frameInfo: Long): Int 1177 private external fun nGetBlendOp(frameInfo: Long): Int 1178 private external fun nGetRepeatCount(decoder: Long): Int 1179 private external fun nSetHandleDisposePrevious(decoder: Long, handle: Boolean) 1180 } 1181