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