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