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