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