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