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