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