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