BitmapFactory.cpp revision 2a6ecae93d4effa47827029d74f2136b5ae8558d
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    bool useExistingBitmap = false;
214    if (javaBitmap == NULL) {
215        bitmap = new SkBitmap;
216    } else {
217        if (sampleSize != 1) {
218            return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
219        }
220
221        bitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
222        // only reuse the provided bitmap if it is immutable
223        if (!bitmap->isImmutable()) {
224            useExistingBitmap = true;
225            // config of supplied bitmap overrules config set in options
226            prefConfig = bitmap->getConfig();
227        } else {
228            ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
229            bitmap = new SkBitmap;
230        }
231    }
232
233    SkAutoTDelete<SkImageDecoder> add(decoder);
234    SkAutoTDelete<SkBitmap> adb(bitmap, !useExistingBitmap);
235
236    decoder->setPeeker(&peeker);
237    if (!isPurgeable) {
238        decoder->setAllocator(&javaAllocator);
239    }
240
241    AutoDecoderCancel adc(options, decoder);
242
243    // To fix the race condition in case "requestCancelDecode"
244    // happens earlier than AutoDecoderCancel object is added
245    // to the gAutoDecoderCancelMutex linked list.
246    if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) {
247        return nullObjectReturn("gOptions_mCancelID");
248    }
249
250    SkImageDecoder::Mode decodeMode = mode;
251    if (isPurgeable) {
252        decodeMode = SkImageDecoder::kDecodeBounds_Mode;
253    }
254
255    SkBitmap* decoded;
256    if (willScale) {
257        decoded = new SkBitmap;
258    } else {
259        decoded = bitmap;
260    }
261    SkAutoTDelete<SkBitmap> adb2(willScale ? decoded : NULL);
262
263    if (!decoder->decode(stream, decoded, prefConfig, decodeMode, javaBitmap != NULL)) {
264        return nullObjectReturn("decoder->decode returned false");
265    }
266
267    int scaledWidth = decoded->width();
268    int scaledHeight = decoded->height();
269
270    if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
271        scaledWidth = int(scaledWidth * scale + 0.5f);
272        scaledHeight = int(scaledHeight * scale + 0.5f);
273    }
274
275    // update options (if any)
276    if (options != NULL) {
277        env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
278        env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
279        env->SetObjectField(options, gOptions_mimeFieldID,
280                getMimeTypeString(env, decoder->getFormat()));
281    }
282
283    // if we're in justBounds mode, return now (skip the java bitmap)
284    if (mode == SkImageDecoder::kDecodeBounds_Mode) {
285        return NULL;
286    }
287
288    jbyteArray ninePatchChunk = NULL;
289    if (peeker.fPatch != NULL) {
290        if (willScale) {
291            scaleNinePatchChunk(peeker.fPatch, scale);
292        }
293
294        size_t ninePatchArraySize = peeker.fPatch->serializedSize();
295        ninePatchChunk = env->NewByteArray(ninePatchArraySize);
296        if (ninePatchChunk == NULL) {
297            return nullObjectReturn("ninePatchChunk == null");
298        }
299
300        jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL);
301        if (array == NULL) {
302            return nullObjectReturn("primitive array == null");
303        }
304
305        peeker.fPatch->serialize(array);
306        env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
307    }
308
309    jintArray layoutBounds = NULL;
310    if (peeker.fLayoutBounds != NULL) {
311        layoutBounds = env->NewIntArray(4);
312        if (layoutBounds == NULL) {
313            return nullObjectReturn("layoutBounds == null");
314        }
315
316        jint scaledBounds[4];
317        if (willScale) {
318            for (int i=0; i<4; i++) {
319                scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f);
320            }
321        } else {
322            memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds));
323        }
324        env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds);
325        if (javaBitmap != NULL) {
326            env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds);
327        }
328    }
329
330    if (willScale) {
331        // This is weird so let me explain: we could use the scale parameter
332        // directly, but for historical reasons this is how the corresponding
333        // Dalvik code has always behaved. We simply recreate the behavior here.
334        // The result is slightly different from simply using scale because of
335        // the 0.5f rounding bias applied when computing the target image size
336        const float sx = scaledWidth / float(decoded->width());
337        const float sy = scaledHeight / float(decoded->height());
338
339        SkBitmap::Config config = decoded->config();
340        switch (config) {
341            case SkBitmap::kNo_Config:
342            case SkBitmap::kIndex8_Config:
343            case SkBitmap::kRLE_Index8_Config:
344                config = SkBitmap::kARGB_8888_Config;
345                break;
346            default:
347                break;
348        }
349
350        bitmap->setConfig(config, scaledWidth, scaledHeight);
351        bitmap->setIsOpaque(decoded->isOpaque());
352        bitmap->allocPixels(&javaAllocator, NULL);
353        bitmap->eraseColor(0);
354
355        SkPaint paint;
356        paint.setFilterBitmap(true);
357
358        SkCanvas canvas(*bitmap);
359        canvas.scale(sx, sy);
360        canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint);
361    }
362
363    if (padding) {
364        if (peeker.fPatch != NULL) {
365            GraphicsJNI::set_jrect(env, padding,
366                    peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop,
367                    peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom);
368        } else {
369            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
370        }
371    }
372
373    SkPixelRef* pr;
374    if (isPurgeable) {
375        pr = installPixelRef(bitmap, stream, sampleSize, doDither);
376    } else {
377        // if we get here, we're in kDecodePixels_Mode and will therefore
378        // already have a pixelref installed.
379        pr = bitmap->pixelRef();
380    }
381    if (pr == NULL) {
382        return nullObjectReturn("Got null SkPixelRef");
383    }
384
385    if (!isMutable) {
386        // promise we will never change our pixels (great for sharing and pictures)
387        pr->setImmutable();
388    }
389
390    // detach bitmap from its autodeleter, since we want to own it now
391    adb.detach();
392
393    if (useExistingBitmap) {
394        // If a java bitmap was passed in for reuse, pass it back
395        return javaBitmap;
396    }
397    // now create the java bitmap
398    return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
399            isMutable, ninePatchChunk, layoutBounds, -1);
400}
401
402static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
403        jobject padding, jobject options, jboolean applyScale, jfloat scale) {
404
405    jobject bitmap = NULL;
406    SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
407
408    if (stream) {
409        // for now we don't allow purgeable with java inputstreams
410        bitmap = doDecode(env, stream, padding, options, false, false, applyScale, scale);
411        stream->unref();
412    }
413    return bitmap;
414}
415
416static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
417        jobject padding, jobject options) {
418
419    return nativeDecodeStreamScaled(env, clazz, is, storage, padding, options, false, 1.0f);
420}
421
422static ssize_t getFDSize(int fd) {
423    off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
424    if (curr < 0) {
425        return 0;
426    }
427    size_t size = ::lseek(fd, 0, SEEK_END);
428    ::lseek64(fd, curr, SEEK_SET);
429    return size;
430}
431
432static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor,
433        jobject padding, jobject bitmapFactoryOptions) {
434
435    NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
436
437    jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
438
439    bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
440    bool isShareable = optionsShareable(env, bitmapFactoryOptions);
441    bool weOwnTheFD = false;
442    if (isPurgeable && isShareable) {
443        int newFD = ::dup(descriptor);
444        if (-1 != newFD) {
445            weOwnTheFD = true;
446            descriptor = newFD;
447        }
448    }
449
450    SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
451    SkAutoUnref aur(stream);
452    if (!stream->isValid()) {
453        return NULL;
454    }
455
456    /* Restore our offset when we leave, so we can be called more than once
457       with the same descriptor. This is only required if we didn't dup the
458       file descriptor, but it is OK to do it all the time.
459    */
460    AutoFDSeek as(descriptor);
461
462    /* Allow purgeable iff we own the FD, i.e., in the puregeable and
463       shareable case.
464    */
465    return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
466}
467
468/*  make a deep copy of the asset, and return it as a stream, or NULL if there
469    was an error.
470 */
471static SkStream* copyAssetToStream(Asset* asset) {
472    // if we could "ref/reopen" the asset, we may not need to copy it here
473    off64_t size = asset->seek(0, SEEK_SET);
474    if ((off64_t)-1 == size) {
475        SkDebugf("---- copyAsset: asset rewind failed\n");
476        return NULL;
477    }
478
479    size = asset->getLength();
480    if (size <= 0) {
481        SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
482        return NULL;
483    }
484
485    SkStream* stream = new SkMemoryStream(size);
486    void* data = const_cast<void*>(stream->getMemoryBase());
487    off64_t len = asset->read(data, size);
488    if (len != size) {
489        SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
490        delete stream;
491        stream = NULL;
492    }
493    return stream;
494}
495
496static jobject nativeDecodeAssetScaled(JNIEnv* env, jobject clazz, jint native_asset,
497        jobject padding, jobject options, jboolean applyScale, jfloat scale) {
498
499    SkStream* stream;
500    Asset* asset = reinterpret_cast<Asset*>(native_asset);
501    bool forcePurgeable = optionsPurgeable(env, options);
502    if (forcePurgeable) {
503        // if we could "ref/reopen" the asset, we may not need to copy it here
504        // and we could assume optionsShareable, since assets are always RO
505        stream = copyAssetToStream(asset);
506        if (stream == NULL) {
507            return NULL;
508        }
509    } else {
510        // since we know we'll be done with the asset when we return, we can
511        // just use a simple wrapper
512        stream = new AssetStreamAdaptor(asset);
513    }
514    SkAutoUnref aur(stream);
515    return doDecode(env, stream, padding, options, true, forcePurgeable, applyScale, scale);
516}
517
518static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset,
519        jobject padding, jobject options) {
520
521    return nativeDecodeAssetScaled(env, clazz, native_asset, padding, options, false, 1.0f);
522}
523
524static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
525        int offset, int length, jobject options) {
526
527    /*  If optionsShareable() we could decide to just wrap the java array and
528        share it, but that means adding a globalref to the java array object
529        and managing its lifetime. For now we just always copy the array's data
530        if optionsPurgeable(), unless we're just decoding bounds.
531     */
532    bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
533    AutoJavaByteArray ar(env, byteArray);
534    SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
535    SkAutoUnref aur(stream);
536    return doDecode(env, stream, NULL, options, purgeable);
537}
538
539static void nativeRequestCancel(JNIEnv*, jobject joptions) {
540    (void)AutoDecoderCancel::RequestCancel(joptions);
541}
542
543static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
544    jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
545    return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
546}
547
548///////////////////////////////////////////////////////////////////////////////
549
550static JNINativeMethod gMethods[] = {
551    {   "nativeDecodeStream",
552        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
553        (void*)nativeDecodeStream
554    },
555    {   "nativeDecodeStream",
556        "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
557        (void*)nativeDecodeStreamScaled
558    },
559
560    {   "nativeDecodeFileDescriptor",
561        "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
562        (void*)nativeDecodeFileDescriptor
563    },
564
565    {   "nativeDecodeAsset",
566        "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
567        (void*)nativeDecodeAsset
568    },
569
570    {   "nativeDecodeAsset",
571        "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;ZF)Landroid/graphics/Bitmap;",
572        (void*)nativeDecodeAssetScaled
573    },
574
575    {   "nativeDecodeByteArray",
576        "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
577        (void*)nativeDecodeByteArray
578    },
579
580    {   "nativeScaleNinePatch",
581        "([BFLandroid/graphics/Rect;)[B",
582        (void*)nativeScaleNinePatch
583    },
584
585    {   "nativeIsSeekable",
586        "(Ljava/io/FileDescriptor;)Z",
587        (void*)nativeIsSeekable
588    },
589};
590
591static JNINativeMethod gOptionsMethods[] = {
592    {   "requestCancel", "()V", (void*)nativeRequestCancel }
593};
594
595static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
596                                const char fieldname[], const char type[]) {
597    jfieldID id = env->GetFieldID(clazz, fieldname, type);
598    SkASSERT(id);
599    return id;
600}
601
602int register_android_graphics_BitmapFactory(JNIEnv* env) {
603    jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
604    SkASSERT(options_class);
605    gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
606        "Landroid/graphics/Bitmap;");
607    gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
608    gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
609    gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
610            "Landroid/graphics/Bitmap$Config;");
611    gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z");
612    gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z");
613    gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z");
614    gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
615    gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
616            "inPreferQualityOverSpeed", "Z");
617    gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I");
618    gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I");
619    gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;");
620    gOptions_mCancelID = getFieldIDCheck(env, options_class, "mCancel", "Z");
621
622    jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
623    SkASSERT(bitmap_class);
624    gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I");
625    gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mLayoutBounds", "[I");
626    int ret = AndroidRuntime::registerNativeMethods(env,
627                                    "android/graphics/BitmapFactory$Options",
628                                    gOptionsMethods,
629                                    SK_ARRAY_COUNT(gOptionsMethods));
630    if (ret) {
631        return ret;
632    }
633    return android::AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory",
634                                         gMethods, SK_ARRAY_COUNT(gMethods));
635}
636