1 /* 2 * Copyright (C) 2017 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 #include "Bitmap.h" 18 #include "BitmapFactory.h" 19 #include "ByteBufferStreamAdaptor.h" 20 #include "CreateJavaOutputStreamAdaptor.h" 21 #include "GraphicsJNI.h" 22 #include "ImageDecoder.h" 23 #include "Utils.h" 24 #include "core_jni_helpers.h" 25 26 #include <hwui/Bitmap.h> 27 #include <HardwareBitmapUploader.h> 28 29 #include <SkAndroidCodec.h> 30 #include <SkEncodedImageFormat.h> 31 #include <SkFrontBufferedStream.h> 32 #include <SkStream.h> 33 34 #include <androidfw/Asset.h> 35 #include <jni.h> 36 #include <sys/stat.h> 37 38 using namespace android; 39 40 static jclass gImageDecoder_class; 41 static jclass gSize_class; 42 static jclass gDecodeException_class; 43 static jclass gCanvas_class; 44 static jmethodID gImageDecoder_constructorMethodID; 45 static jmethodID gImageDecoder_postProcessMethodID; 46 static jmethodID gSize_constructorMethodID; 47 static jmethodID gDecodeException_constructorMethodID; 48 static jmethodID gCallback_onPartialImageMethodID; 49 static jmethodID gCanvas_constructorMethodID; 50 static jmethodID gCanvas_releaseMethodID; 51 52 // Clear and return any pending exception for handling other than throwing directly. 53 static jthrowable get_and_clear_exception(JNIEnv* env) { 54 jthrowable jexception = env->ExceptionOccurred(); 55 if (jexception) { 56 env->ExceptionClear(); 57 } 58 return jexception; 59 } 60 61 // Throw a new ImageDecoder.DecodeException. Returns null for convenience. 62 static jobject throw_exception(JNIEnv* env, ImageDecoder::Error error, const char* msg, 63 jthrowable cause, jobject source) { 64 jstring jstr = nullptr; 65 if (msg) { 66 jstr = env->NewStringUTF(msg); 67 if (!jstr) { 68 // Out of memory. 69 return nullptr; 70 } 71 } 72 jthrowable exception = (jthrowable) env->NewObject(gDecodeException_class, 73 gDecodeException_constructorMethodID, error, jstr, cause, source); 74 // Only throw if not out of memory. 75 if (exception) { 76 env->Throw(exception); 77 } 78 return nullptr; 79 } 80 81 static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream, jobject source) { 82 if (!stream.get()) { 83 return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream", 84 nullptr, source); 85 } 86 std::unique_ptr<ImageDecoder> decoder(new ImageDecoder); 87 SkCodec::Result result; 88 auto codec = SkCodec::MakeFromStream(std::move(stream), &result, decoder->mPeeker.get()); 89 if (jthrowable jexception = get_and_clear_exception(env)) { 90 return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source); 91 } 92 if (!codec) { 93 switch (result) { 94 case SkCodec::kIncompleteInput: 95 return throw_exception(env, ImageDecoder::kSourceIncomplete, "", nullptr, source); 96 default: 97 SkString msg; 98 msg.printf("Failed to create image decoder with message '%s'", 99 SkCodec::ResultToString(result)); 100 return throw_exception(env, ImageDecoder::kSourceMalformedData, msg.c_str(), 101 nullptr, source); 102 103 } 104 } 105 106 const bool animated = codec->getFrameCount() > 1; 107 if (jthrowable jexception = get_and_clear_exception(env)) { 108 return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source); 109 } 110 111 decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), 112 SkAndroidCodec::ExifOrientationBehavior::kRespect); 113 if (!decoder->mCodec.get()) { 114 return throw_exception(env, ImageDecoder::kSourceMalformedData, "", nullptr, source); 115 } 116 117 const auto& info = decoder->mCodec->getInfo(); 118 const int width = info.width(); 119 const int height = info.height(); 120 const bool isNinePatch = decoder->mPeeker->mPatch != nullptr; 121 return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID, 122 reinterpret_cast<jlong>(decoder.release()), width, height, 123 animated, isNinePatch); 124 } 125 126 static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, 127 jobject fileDescriptor, jobject source) { 128 int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); 129 130 struct stat fdStat; 131 if (fstat(descriptor, &fdStat) == -1) { 132 return throw_exception(env, ImageDecoder::kSourceMalformedData, 133 "broken file descriptor; fstat returned -1", nullptr, source); 134 } 135 136 int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0); 137 FILE* file = fdopen(dupDescriptor, "r"); 138 if (file == NULL) { 139 close(dupDescriptor); 140 return throw_exception(env, ImageDecoder::kSourceMalformedData, "Could not open file", 141 nullptr, source); 142 } 143 144 std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); 145 return native_create(env, std::move(fileStream), source); 146 } 147 148 static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/, 149 jobject is, jbyteArray storage, jobject source) { 150 std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false)); 151 152 if (!stream.get()) { 153 return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream", 154 nullptr, source); 155 } 156 157 std::unique_ptr<SkStream> bufferedStream( 158 SkFrontBufferedStream::Make(std::move(stream), 159 SkCodec::MinBufferedBytesNeeded())); 160 return native_create(env, std::move(bufferedStream), source); 161 } 162 163 static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/, jlong assetPtr, 164 jobject source) { 165 Asset* asset = reinterpret_cast<Asset*>(assetPtr); 166 std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset)); 167 return native_create(env, std::move(stream), source); 168 } 169 170 static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer, 171 jint initialPosition, jint limit, jobject source) { 172 std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer, 173 initialPosition, limit); 174 if (!stream) { 175 return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to read ByteBuffer", 176 nullptr, source); 177 } 178 return native_create(env, std::move(stream), source); 179 } 180 181 static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray, 182 jint offset, jint length, jobject source) { 183 std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length)); 184 return native_create(env, std::move(stream), source); 185 } 186 187 jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas) { 188 jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID, 189 reinterpret_cast<jlong>(canvas.get())); 190 if (!jcanvas) { 191 doThrowOOME(env, "Failed to create Java Canvas for PostProcess!"); 192 return ImageDecoder::kUnknown; 193 } 194 195 // jcanvas now owns canvas. 196 canvas.release(); 197 198 return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas); 199 } 200 201 static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 202 jobject jdecoder, jboolean jpostProcess, 203 jint desiredWidth, jint desiredHeight, jobject jsubset, 204 jboolean requireMutable, jint allocator, 205 jboolean requireUnpremul, jboolean preferRamOverQuality, 206 jboolean asAlphaMask, jlong colorSpaceHandle, 207 jboolean extended) { 208 auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); 209 SkAndroidCodec* codec = decoder->mCodec.get(); 210 const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight); 211 SkISize decodeSize = desiredSize; 212 const int sampleSize = codec->computeSampleSize(&decodeSize); 213 const bool scale = desiredSize != decodeSize; 214 SkImageInfo decodeInfo = codec->getInfo().makeWH(decodeSize.width(), decodeSize.height()); 215 if (scale && requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) { 216 doThrowISE(env, "Cannot scale unpremultiplied pixels!"); 217 return nullptr; 218 } 219 220 switch (decodeInfo.alphaType()) { 221 case kUnpremul_SkAlphaType: 222 if (!requireUnpremul) { 223 decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType); 224 } 225 break; 226 case kPremul_SkAlphaType: 227 if (requireUnpremul) { 228 decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType); 229 } 230 break; 231 case kOpaque_SkAlphaType: 232 break; 233 case kUnknown_SkAlphaType: 234 doThrowIOE(env, "Unknown alpha type"); 235 return nullptr; 236 } 237 238 SkColorType colorType = kN32_SkColorType; 239 if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) { 240 // We have to trick Skia to decode this to a single channel. 241 colorType = kGray_8_SkColorType; 242 } else if (preferRamOverQuality) { 243 // FIXME: The post-process might add alpha, which would make a 565 244 // result incorrect. If we call the postProcess before now and record 245 // to a picture, we can know whether alpha was added, and if not, we 246 // can still use 565. 247 if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) { 248 // If the final result will be hardware, decoding to 565 and then 249 // uploading to the gpu as 8888 will not save memory. This still 250 // may save us from using F16, but do not go down to 565. 251 if (allocator != ImageDecoder::kHardware_Allocator && 252 (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) { 253 colorType = kRGB_565_SkColorType; 254 } 255 } 256 // Otherwise, stick with N32 257 } else if (extended) { 258 colorType = kRGBA_F16_SkColorType; 259 } else { 260 colorType = codec->computeOutputColorType(colorType); 261 } 262 263 const bool isHardware = !requireMutable 264 && (allocator == ImageDecoder::kDefault_Allocator || 265 allocator == ImageDecoder::kHardware_Allocator) 266 && colorType != kGray_8_SkColorType; 267 268 if (colorType == kRGBA_F16_SkColorType && isHardware && 269 !uirenderer::HardwareBitmapUploader::hasFP16Support()) { 270 colorType = kN32_SkColorType; 271 } 272 273 sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); 274 colorSpace = codec->computeOutputColorSpace(colorType, colorSpace); 275 decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace); 276 277 SkBitmap bm; 278 auto bitmapInfo = decodeInfo; 279 if (asAlphaMask && colorType == kGray_8_SkColorType) { 280 bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType); 281 } 282 if (!bm.setInfo(bitmapInfo)) { 283 doThrowIOE(env, "Failed to setInfo properly"); 284 return nullptr; 285 } 286 287 sk_sp<Bitmap> nativeBitmap; 288 // If we are going to scale or subset, we will create a new bitmap later on, 289 // so use the heap for the temporary. 290 // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380. 291 if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) { 292 nativeBitmap = Bitmap::allocateAshmemBitmap(&bm); 293 } else { 294 nativeBitmap = Bitmap::allocateHeapBitmap(&bm); 295 } 296 if (!nativeBitmap) { 297 SkString msg; 298 msg.printf("OOM allocating Bitmap with dimensions %i x %i", 299 decodeInfo.width(), decodeInfo.height()); 300 doThrowOOME(env, msg.c_str()); 301 return nullptr; 302 } 303 304 SkAndroidCodec::AndroidOptions options; 305 options.fSampleSize = sampleSize; 306 auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options); 307 jthrowable jexception = get_and_clear_exception(env); 308 int onPartialImageError = jexception ? ImageDecoder::kSourceException 309 : 0; // No error. 310 switch (result) { 311 case SkCodec::kSuccess: 312 // Ignore the exception, since the decode was successful anyway. 313 jexception = nullptr; 314 onPartialImageError = 0; 315 break; 316 case SkCodec::kIncompleteInput: 317 if (!jexception) { 318 onPartialImageError = ImageDecoder::kSourceIncomplete; 319 } 320 break; 321 case SkCodec::kErrorInInput: 322 if (!jexception) { 323 onPartialImageError = ImageDecoder::kSourceMalformedData; 324 } 325 break; 326 default: 327 SkString msg; 328 msg.printf("getPixels failed with error %s", SkCodec::ResultToString(result)); 329 doThrowIOE(env, msg.c_str()); 330 return nullptr; 331 } 332 333 if (onPartialImageError) { 334 env->CallVoidMethod(jdecoder, gCallback_onPartialImageMethodID, onPartialImageError, 335 jexception); 336 if (env->ExceptionCheck()) { 337 return nullptr; 338 } 339 } 340 341 jbyteArray ninePatchChunk = nullptr; 342 jobject ninePatchInsets = nullptr; 343 344 // Ignore ninepatch when post-processing. 345 if (!jpostProcess) { 346 // FIXME: Share more code with BitmapFactory.cpp. 347 if (decoder->mPeeker->mPatch != nullptr) { 348 size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize(); 349 ninePatchChunk = env->NewByteArray(ninePatchArraySize); 350 if (ninePatchChunk == nullptr) { 351 doThrowOOME(env, "Failed to allocate nine patch chunk."); 352 return nullptr; 353 } 354 355 env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize, 356 reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch)); 357 } 358 359 if (decoder->mPeeker->mHasInsets) { 360 ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f); 361 if (ninePatchInsets == nullptr) { 362 doThrowOOME(env, "Failed to allocate nine patch insets."); 363 return nullptr; 364 } 365 } 366 } 367 368 if (scale || jsubset) { 369 int translateX = 0; 370 int translateY = 0; 371 if (jsubset) { 372 SkIRect subset; 373 GraphicsJNI::jrect_to_irect(env, jsubset, &subset); 374 375 translateX = -subset.fLeft; 376 translateY = -subset.fTop; 377 desiredWidth = subset.width(); 378 desiredHeight = subset.height(); 379 } 380 SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight); 381 SkBitmap scaledBm; 382 if (!scaledBm.setInfo(scaledInfo)) { 383 doThrowIOE(env, "Failed scaled setInfo"); 384 return nullptr; 385 } 386 387 sk_sp<Bitmap> scaledPixelRef; 388 if (allocator == ImageDecoder::kSharedMemory_Allocator) { 389 scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm); 390 } else { 391 scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm); 392 } 393 if (!scaledPixelRef) { 394 SkString msg; 395 msg.printf("OOM allocating scaled Bitmap with dimensions %i x %i", 396 desiredWidth, desiredHeight); 397 doThrowOOME(env, msg.c_str()); 398 return nullptr; 399 } 400 401 SkPaint paint; 402 paint.setBlendMode(SkBlendMode::kSrc); 403 paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering 404 405 SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy); 406 canvas.translate(translateX, translateY); 407 if (scale) { 408 float scaleX = (float) desiredWidth / decodeInfo.width(); 409 float scaleY = (float) desiredHeight / decodeInfo.height(); 410 canvas.scale(scaleX, scaleY); 411 } 412 413 canvas.drawBitmap(bm, 0.0f, 0.0f, &paint); 414 415 bm.swap(scaledBm); 416 nativeBitmap = std::move(scaledPixelRef); 417 } 418 419 if (jpostProcess) { 420 std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm)); 421 422 jint pixelFormat = postProcessAndRelease(env, jdecoder, std::move(canvas)); 423 if (env->ExceptionCheck()) { 424 return nullptr; 425 } 426 427 SkAlphaType newAlphaType = bm.alphaType(); 428 switch (pixelFormat) { 429 case ImageDecoder::kUnknown: 430 break; 431 case ImageDecoder::kTranslucent: 432 newAlphaType = kPremul_SkAlphaType; 433 break; 434 case ImageDecoder::kOpaque: 435 newAlphaType = kOpaque_SkAlphaType; 436 break; 437 default: 438 SkString msg; 439 msg.printf("invalid return from postProcess: %i", pixelFormat); 440 doThrowIAE(env, msg.c_str()); 441 return nullptr; 442 } 443 444 if (newAlphaType != bm.alphaType()) { 445 if (!bm.setAlphaType(newAlphaType)) { 446 SkString msg; 447 msg.printf("incompatible return from postProcess: %i", pixelFormat); 448 doThrowIAE(env, msg.c_str()); 449 return nullptr; 450 } 451 nativeBitmap->setAlphaType(newAlphaType); 452 } 453 } 454 455 int bitmapCreateFlags = 0x0; 456 if (!requireUnpremul) { 457 // Even if the image is opaque, setting this flag means that 458 // if alpha is added (e.g. by PostProcess), it will be marked as 459 // premultiplied. 460 bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied; 461 } 462 463 if (requireMutable) { 464 bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable; 465 } else { 466 if (isHardware) { 467 sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm); 468 if (hwBitmap) { 469 hwBitmap->setImmutable(); 470 return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags, 471 ninePatchChunk, ninePatchInsets); 472 } 473 if (allocator == ImageDecoder::kHardware_Allocator) { 474 doThrowOOME(env, "failed to allocate hardware Bitmap!"); 475 return nullptr; 476 } 477 // If we failed to create a hardware bitmap, go ahead and create a 478 // software one. 479 } 480 481 nativeBitmap->setImmutable(); 482 } 483 return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk, 484 ninePatchInsets); 485 } 486 487 static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 488 jint sampleSize) { 489 auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); 490 SkISize size = decoder->mCodec->getSampledDimensions(sampleSize); 491 return env->NewObject(gSize_class, gSize_constructorMethodID, size.width(), size.height()); 492 } 493 494 static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, 495 jobject outPadding) { 496 auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); 497 decoder->mPeeker->getPadding(env, outPadding); 498 } 499 500 static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) { 501 delete reinterpret_cast<ImageDecoder*>(nativePtr); 502 } 503 504 static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 505 auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); 506 return encodedFormatToString(env, decoder->mCodec->getEncodedFormat()); 507 } 508 509 static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { 510 auto* codec = reinterpret_cast<ImageDecoder*>(nativePtr)->mCodec.get(); 511 auto colorType = codec->computeOutputColorType(kN32_SkColorType); 512 sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType); 513 return GraphicsJNI::getColorSpace(env, colorSpace.get(), colorType); 514 } 515 516 static const JNINativeMethod gImageDecoderMethods[] = { 517 { "nCreate", "(JLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset }, 518 { "nCreate", "(Ljava/nio/ByteBuffer;IILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer }, 519 { "nCreate", "([BIILandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray }, 520 { "nCreate", "(Ljava/io/InputStream;[BLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream }, 521 { "nCreate", "(Ljava/io/FileDescriptor;Landroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd }, 522 { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZJZ)Landroid/graphics/Bitmap;", 523 (void*) ImageDecoder_nDecodeBitmap }, 524 { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize }, 525 { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding }, 526 { "nClose", "(J)V", (void*) ImageDecoder_nClose}, 527 { "nGetMimeType", "(J)Ljava/lang/String;", (void*) ImageDecoder_nGetMimeType }, 528 { "nGetColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*) ImageDecoder_nGetColorSpace }, 529 }; 530 531 int register_android_graphics_ImageDecoder(JNIEnv* env) { 532 gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder")); 533 gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZZ)V"); 534 gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;)I"); 535 536 gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size")); 537 gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "<init>", "(II)V"); 538 539 gDecodeException_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$DecodeException")); 540 gDecodeException_constructorMethodID = GetMethodIDOrDie(env, gDecodeException_class, "<init>", "(ILjava/lang/String;Ljava/lang/Throwable;Landroid/graphics/ImageDecoder$Source;)V"); 541 542 gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(ILjava/lang/Throwable;)V"); 543 544 gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas")); 545 gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V"); 546 gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V"); 547 548 return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods, 549 NELEM(gImageDecoderMethods)); 550 } 551