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