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