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