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