BitmapFactory.cpp revision 7b2f8b8fb7064a1d3b6d942b978c30c24c9d7299
1#define LOG_TAG "BitmapFactory"
2
3#include "BitmapFactory.h"
4#include "NinePatchPeeker.h"
5#include "SkImageDecoder.h"
6#include "SkImageRef_ashmem.h"
7#include "SkImageRef_GlobalPool.h"
8#include "SkPixelRef.h"
9#include "SkStream.h"
10#include "SkTemplates.h"
11#include "SkUtils.h"
12#include "CreateJavaOutputStreamAdaptor.h"
13#include "AutoDecodeCancel.h"
14#include "Utils.h"
15#include "JNIHelp.h"
16
17#include <android_runtime/AndroidRuntime.h>
18#include <androidfw/Asset.h>
19#include <androidfw/ResourceTypes.h>
20#include <netinet/in.h>
21#include <sys/mman.h>
22#include <sys/stat.h>
23
24jfieldID gOptions_justBoundsFieldID;
25jfieldID gOptions_sampleSizeFieldID;
26jfieldID gOptions_configFieldID;
27jfieldID gOptions_mutableFieldID;
28jfieldID gOptions_ditherFieldID;
29jfieldID gOptions_purgeableFieldID;
30jfieldID gOptions_shareableFieldID;
31jfieldID gOptions_preferQualityOverSpeedFieldID;
32jfieldID gOptions_widthFieldID;
33jfieldID gOptions_heightFieldID;
34jfieldID gOptions_mimeFieldID;
35jfieldID gOptions_mCancelID;
36jfieldID gOptions_bitmapFieldID;
37jfieldID gBitmap_nativeBitmapFieldID;
38
39#if 0
40    #define TRACE_BITMAP(code)  code
41#else
42    #define TRACE_BITMAP(code)
43#endif
44
45using namespace android;
46
47static inline int32_t validOrNeg1(bool isValid, int32_t value) {
48//    return isValid ? value : -1;
49    SkASSERT((int)isValid == 0 || (int)isValid == 1);
50    return ((int32_t)isValid - 1) | value;
51}
52
53jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
54    static const struct {
55        SkImageDecoder::Format fFormat;
56        const char*            fMimeType;
57    } gMimeTypes[] = {
58        { SkImageDecoder::kBMP_Format,  "image/bmp" },
59        { SkImageDecoder::kGIF_Format,  "image/gif" },
60        { SkImageDecoder::kICO_Format,  "image/x-ico" },
61        { SkImageDecoder::kJPEG_Format, "image/jpeg" },
62        { SkImageDecoder::kPNG_Format,  "image/png" },
63        { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
64    };
65
66    const char* cstr = NULL;
67    for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
68        if (gMimeTypes[i].fFormat == format) {
69            cstr = gMimeTypes[i].fMimeType;
70            break;
71        }
72    }
73
74    jstring jstr = 0;
75    if (NULL != cstr) {
76        jstr = env->NewStringUTF(cstr);
77    }
78    return jstr;
79}
80
81static bool optionsPurgeable(JNIEnv* env, jobject options) {
82    return options != NULL && env->GetBooleanField(options, gOptions_purgeableFieldID);
83}
84
85static bool optionsShareable(JNIEnv* env, jobject options) {
86    return options != NULL && env->GetBooleanField(options, gOptions_shareableFieldID);
87}
88
89static bool optionsJustBounds(JNIEnv* env, jobject options) {
90    return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID);
91}
92
93static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) {
94    chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
95    chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
96    chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
97    chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
98
99    for (int i = 0; i < chunk->numXDivs; i++) {
100        chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
101        if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
102            chunk->xDivs[i]++;
103        }
104    }
105
106    for (int i = 0; i < chunk->numYDivs; i++) {
107        chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
108        if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
109            chunk->yDivs[i]++;
110        }
111    }
112}
113
114static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
115        jobject padding) {
116
117    jbyte* array = env->GetByteArrayElements(chunkObject, 0);
118    if (array != NULL) {
119        size_t chunkSize = env->GetArrayLength(chunkObject);
120        void* storage = alloca(chunkSize);
121        android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
122        memcpy(chunk, array, chunkSize);
123        android::Res_png_9patch::deserialize(chunk);
124
125        scaleNinePatchChunk(chunk, scale);
126        memcpy(array, chunk, chunkSize);
127
128        if (padding) {
129            GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
130                    chunk->paddingRight, chunk->paddingBottom);
131        }
132
133        env->ReleaseByteArrayElements(chunkObject, array, 0);
134    }
135    return chunkObject;
136}
137
138static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
139        int sampleSize, bool ditherImage) {
140
141    SkImageRef* pr;
142    // only use ashmem for large images, since mmaps come at a price
143    if (bitmap->getSize() >= 32 * 1024) {
144        pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
145    } else {
146        pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
147    }
148    pr->setDitherImage(ditherImage);
149    bitmap->setPixelRef(pr)->unref();
150    pr->isOpaque(bitmap);
151    return pr;
152}
153
154// since we "may" create a purgeable imageref, we require the stream be ref'able
155// i.e. dynamically allocated, since its lifetime may exceed the current stack
156// frame.
157static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
158        jobject options, bool allowPurgeable, bool forcePurgeable = false,
159        bool applyScale = false, float scale = 1.0f) {
160
161    int sampleSize = 1;
162
163    SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
164    SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
165
166    bool doDither = true;
167    bool isMutable = false;
168    bool willScale = applyScale && scale != 1.0f;
169    bool isPurgeable = !willScale &&
170            (forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)));
171    bool preferQualityOverSpeed = false;
172
173    jobject javaBitmap = NULL;
174
175    if (options != NULL) {
176        sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
177        if (optionsJustBounds(env, options)) {
178            mode = SkImageDecoder::kDecodeBounds_Mode;
179        }
180
181        // initialize these, in case we fail later on
182        env->SetIntField(options, gOptions_widthFieldID, -1);
183        env->SetIntField(options, gOptions_heightFieldID, -1);
184        env->SetObjectField(options, gOptions_mimeFieldID, 0);
185
186        jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
187        prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
188        isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
189        doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
190        preferQualityOverSpeed = env->GetBooleanField(options,
191                gOptions_preferQualityOverSpeedFieldID);
192        javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
193    }
194
195    if (willScale && javaBitmap != NULL) {
196        return nullObjectReturn("Cannot pre-scale a reused bitmap");
197    }
198
199    SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
200    if (decoder == NULL) {
201        return nullObjectReturn("SkImageDecoder::Factory returned null");
202    }
203
204    decoder->setSampleSize(sampleSize);
205    decoder->setDitherImage(doDither);
206    decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
207
208    NinePatchPeeker peeker(decoder);
209    JavaPixelAllocator javaAllocator(env);
210
211    SkBitmap* bitmap;
212    if (javaBitmap == NULL) {
213        bitmap = new SkBitmap;
214    } else {
215        if (sampleSize != 1) {
216            return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
217        }
218        bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
219        // config of supplied bitmap overrules config set in options
220        prefConfig = bitmap->getConfig();
221    }
222
223    SkAutoTDelete<SkImageDecoder> add(decoder);
224    SkAutoTDelete<SkBitmap> adb(bitmap, javaBitmap == NULL);
225
226    decoder->setPeeker(&peeker);
227    if (!isPurgeable) {
228        decoder->setAllocator(&javaAllocator);
229    }
230
231    AutoDecoderCancel adc(options, decoder);
232
233    // To fix the race condition in case "requestCancelDecode"
234    // happens earlier than AutoDecoderCancel object is added
235    // to the gAutoDecoderCancelMutex linked list.
236    if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
237        return nullObjectReturn("gOptions_mCancelID");
238    }
239
240    SkImageDecoder::Mode decodeMode = mode;
241    if (isPurgeable) {
242        decodeMode = SkImageDecoder::kDecodeBounds_Mode;
243    }
244
245    SkBitmap* decoded;
246    if (willScale) {
247        decoded = new SkBitmap;
248    } else {
249        decoded = bitmap;
250    }
251    SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL);
252
253    if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) {
254        return nullObjectReturn("decoder->decode returned false");
255    }
256
257    int scaledWidth = decoded->width();
258    int scaledHeight = decoded->height();
259
260    if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
261        scaledWidth = int(scaledWidth * scale + 0.5f);
262        scaledHeight = int(scaledHeight * scale + 0.5f);
263    }
264
265    // update options (if any)
266    if (options != NULL) {
267        env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
268        env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
269        env->SetObjectField(options, gOptions_mimeFieldID,
270                getMimeTypeString(env, decoder->getFormat()));
271    }
272
273    // if we're in justBounds mode, return now (skip the java bitmap)
274    if (mode == SkImageDecoder::kDecodeBounds_Mode) {
275        return NULL;
276    }
277
278    jbyteArray ninePatchChunk = NULL;
279    if (peeker.fPatchIsValid) {
280        if (willScale) {
281            scaleNinePatchChunk(peeker.fPatch, scale);
282        }
283
284        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
285        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
286        if (ninePatchChunk == NULL) {
287            return nullObjectReturn("ninePatchChunk == null");
288        }
289
290        jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
291        if (array == NULL) {
292            return nullObjectReturn("primitive array == null");
293        }
294
295        peeker.fPatch->serialize(array);
296        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
297    }
298
299    // detach bitmap from its autodeleter, since we want to own it now
300    adb.detach();
301
302    if (willScale) {
303        // This is weird so let me explain: we could use the scale parameter
304        // directly, but for historical reasons this is how the corresponding
305        // Dalvik code has always behaved. We simply recreate the behavior here.
306        // The result is slightly different from simply using scale because of
307        // the 0.5f rounding bias applied when computing the target image size
308        const float sx = scaledWidth / float(decoded->width());
309        const float sy = scaledHeight / float(decoded->height());
310
311        bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight);
312        bitmap->allocPixels(&javaAllocator, NULL);
313        bitmap->eraseColor(0);
314
315        SkPaint paint;
316        paint.setFilterBitmap(true);
317
318        SkCanvas canvas(*bitmap);
319        canvas.scale(sx, sy);
320        canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
321    }
322
323    if (padding) {
324        if (peeker.fPatchIsValid) {
325            GraphicsJNI::set_jrect(env, padding,
326                    peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
327                    peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
328        } else {
329            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
330        }
331    }
332
333    SkPixelRef* pr;
334    if (isPurgeable) {
335        pr = installPixelRef(bitmap, stream, sampleSize, doDither);
336    } else {
337        // if we get here, we're in kDecodePixels_Mode and will therefore
338        // already have a pixelref installed.
339        pr = bitmap->pixelRef();
340    }
341
342    if (!isMutable) {
343        // promise we will never change our pixels (great for sharing and pictures)
344        pr->setImmutable();
345    }
346
347    if (javaBitmap != NULL) {
348        // If a java bitmap was passed in for reuse, pass it back
349        return javaBitmap;
350    }
351    // now create the java bitmap
352    return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
353            isMutable, ninePatchChunk);
354}
355
356static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
357        jobject padding, jobject options, jboolean applyScale, jfloat scale) {
358
359    jobject bitmap = NULL;
360    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
361
362    if (stream) {
363        // for now we don't allow purgeable with java inputstreams
364        bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale);
365        stream->unref();
366    }
367    return bitmap;
368}
369
370static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
371        jobject padding, jobject options) {
372
373    return nativeDecodeStreamScaled(env, clazz, is, storage, padding, options, false, 1.0f);
374}
375
376static ssize_t getFDSize(int fd) {
377    off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
378    if (curr < 0) {
379        return 0;
380    }
381    size_t size = ::lseek(fd, 0, SEEK_END);
382    ::lseek64(fd, curr, SEEK_SET);
383    return size;
384}
385
386static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
387        jobject padding, jobject bitmapFactoryOptions) {
388
389    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
390
391    jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
392
393    bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
394    bool isShareable = optionsShareable(env, bitmapFactoryOptions);
395    bool weOwnTheFD = false;
396    if (isPurgeable && isShareable) {
397        int newFD = ::dup(descriptor);
398        if (-1 != newFD) {
399            weOwnTheFD = true;
400            descriptor = newFD;
401        }
402    }
403
404    SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
405    SkAutoUnref aur(stream);
406    if (!stream->isValid()) {
407        return NULL;
408    }
409
410    /* Restore our offset when we leave, so we can be called more than once
411       with the same descriptor. This is only required if we didn't dup the
412       file descriptor, but it is OK to do it all the time.
413    */
414    AutoFDSeek as(descriptor);
415
416    /* Allow purgeable iff we own the FD, i.e., in the puregeable and
417       shareable case.
418    */
419    return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
420}
421
422/*  make a deep copy of the asset, and return it as a stream, or NULL if there
423    was an error.
424 */
425static SkStream* copyAssetToStream(Asset* asset) {
426    // if we could "ref/reopen" the asset, we may not need to copy it here
427    off64_t size = asset->seek(0, SEEK_SET);
428    if ((off64_t)-1 == size) {
429        SkDebugf("---- copyAsset: asset rewind failed\n");
430        return NULL;
431    }
432
433    size = asset->getLength();
434    if (size <= 0) {
435        SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
436        return NULL;
437    }
438
439    SkStream* stream = new SkMemoryStream(size);
440    void* data = const_cast<void*>(stream->getMemoryBase());
441    off64_t len = asset->read(data, size);
442    if (len != size) {
443        SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
444        delete stream;
445        stream = NULL;
446    }
447    return stream;
448}
449
450static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_asset,
451        jobject padding, jobject options, jboolean applyScale, jfloat scale) {
452
453    SkStream* stream;
454    Asset* asset = reinterpret_cast<Asset*>(native_asset);
455    bool forcePurgeable = optionsPurgeable(env, options);
456    if (forcePurgeable) {
457        // if we could "ref/reopen" the asset, we may not need to copy it here
458        // and we could assume optionsShareable, since assets are always RO
459        stream = copyAssetToStream(asset);
460        if (stream == NULL) {
461            return NULL;
462        }
463    } else {
464        // since we know we'll be done with the asset when we return, we can
465        // just use a simple wrapper
466        stream = new AssetStreamAdaptor(asset);
467    }
468    SkAutoUnref aur(stream);
469    return doDecode(env, stream, padding, options, true, forcePurgeable, applyScale, scale);
470}
471
472static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
473        jobject padding, jobject options) {
474
475    return nativeDecodeAssetScaled(env, clazz, native_asset, padding, options, false, 1.0f);
476}
477
478static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
479        int offset, int length, jobject options) {
480
481    /*  If optionsShareable() we could decide to just wrap the java array and
482        share it, but that means adding a globalref to the java array object
483        and managing its lifetime. For now we just always copy the array's data
484        if optionsPurgeable(), unless we're just decoding bounds.
485     */
486    bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
487    AutoJavaByteArray ar(env, byteArray);
488    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
489    SkAutoUnref aur(stream);
490    return doDecode(env, stream, NULL, options, purgeable);
491}
492
493static void nativeRequestCancel(JNIEnv*, jobject joptions) {
494    (void)AutoDecoderCancel::RequestCancel(joptions);
495}
496
497static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
498    jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
499    return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
500}
501
502///////////////////////////////////////////////////////////////////////////////
503
504static JNINativeMethod gMethods[] = {
505    {   "nativeDecodeStream",
506        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
507        (void*)nativeDecodeStream
508    },
509    {   "nativeDecodeStream",
510        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
511        (void*)nativeDecodeStreamScaled
512    },
513
514    {   "nativeDecodeFileDescriptor",
515        "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
516        (void*)nativeDecodeFileDescriptor
517    },
518
519    {   "nativeDecodeAsset",
520        "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
521        (void*)nativeDecodeAsset
522    },
523
524    {   "nativeDecodeAsset",
525        "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
526        (void*)nativeDecodeAssetScaled
527    },
528
529    {   "nativeDecodeByteArray",
530        "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
531        (void*)nativeDecodeByteArray
532    },
533
534    {   "nativeScaleNinePatch",
535        "([BFLandroid/graphics/Rect;)[B",
536        (void*)nativeScaleNinePatch
537    },
538
539    {   "nativeIsSeekable",
540        "(Ljava/io/FileDescriptor;)Z",
541        (void*)nativeIsSeekable
542    },
543};
544
545static JNINativeMethod gOptionsMethods[] = {
546    {   "requestCancel", "()V", (void*)nativeRequestCancel }
547};
548
549static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
550                                const char fieldname[], const char type[]) {
551    jfieldID id = env->GetFieldID(clazz, fieldname, type);
552    SkASSERT(id);
553    return id;
554}
555
556int register_android_graphics_BitmapFactory(JNIEnv* env) {
557    jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
558    SkASSERT(options_class);
559    gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
560        "Landroid/graphics/Bitmap;");
561    gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
562    gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
563    gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
564            "Landroid/graphics/Bitmap$Config;");
565    gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z");
566    gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z");
567    gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z");
568    gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
569    gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
570            "inPreferQualityOverSpeed", "Z");
571    gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I");
572    gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I");
573    gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;");
574    gOptions_mCancelID = getFieldIDCheck(env, options_class, "mCancel", "Z");
575
576    jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
577    SkASSERT(bitmap_class);
578    gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I");
579
580    int ret = AndroidRuntime::registerNativeMethods(env,
581                                    "android/graphics/BitmapFactory$Options",
582                                    gOptionsMethods,
583                                    SK_ARRAY_COUNT(gOptionsMethods));
584    if (ret) {
585        return ret;
586    }
587    return android::AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory",
588                                         gMethods, SK_ARRAY_COUNT(gMethods));
589}
590