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