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