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 <utils/Asset.h>
     19 #include <utils/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 
     39 #if 0
     40     #define TRACE_BITMAP(code)  code
     41 #else
     42     #define TRACE_BITMAP(code)
     43 #endif
     44 
     45 using namespace android;
     46 
     47 static inline int32_t validOrNeg1(bool isValid, int32_t value) {
     48 //    return isValid ? value : -1;
     49     SkASSERT((int)isValid == 0 || (int)isValid == 1);
     50     return ((int32_t)isValid - 1) | value;
     51 }
     52 
     53 jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
     54     static const struct {
     55         SkImageDecoder::Format fFormat;
     56         const char*            fMimeType;
     57     } gMimeTypes[] = {
     58         { SkImageDecoder::kBMP_Format,  "image/bmp" },
     59         { SkImageDecoder::kGIF_Format,  "image/gif" },
     60         { SkImageDecoder::kICO_Format,  "image/x-ico" },
     61         { SkImageDecoder::kJPEG_Format, "image/jpeg" },
     62         { SkImageDecoder::kPNG_Format,  "image/png" },
     63         { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" }
     64     };
     65 
     66     const char* cstr = NULL;
     67     for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) {
     68         if (gMimeTypes[i].fFormat == format) {
     69             cstr = gMimeTypes[i].fMimeType;
     70             break;
     71         }
     72     }
     73 
     74     jstring jstr = 0;
     75     if (NULL != cstr) {
     76         jstr = env->NewStringUTF(cstr);
     77     }
     78     return jstr;
     79 }
     80 
     81 static bool optionsPurgeable(JNIEnv* env, jobject options) {
     82     return options != NULL &&
     83             env->GetBooleanField(options, gOptions_purgeableFieldID);
     84 }
     85 
     86 static bool optionsShareable(JNIEnv* env, jobject options) {
     87     return options != NULL &&
     88             env->GetBooleanField(options, gOptions_shareableFieldID);
     89 }
     90 
     91 static bool optionsJustBounds(JNIEnv* env, jobject options) {
     92     return options != NULL &&
     93             env->GetBooleanField(options, gOptions_justBoundsFieldID);
     94 }
     95 
     96 static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream,
     97                                    int sampleSize, bool ditherImage) {
     98     SkImageRef* pr;
     99     // only use ashmem for large images, since mmaps come at a price
    100     if (bitmap->getSize() >= 32 * 1024) {
    101         pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize);
    102     } else {
    103         pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize);
    104     }
    105     pr->setDitherImage(ditherImage);
    106     bitmap->setPixelRef(pr)->unref();
    107     pr->isOpaque(bitmap);
    108     return pr;
    109 }
    110 
    111 // since we "may" create a purgeable imageref, we require the stream be ref'able
    112 // i.e. dynamically allocated, since its lifetime may exceed the current stack
    113 // frame.
    114 static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding,
    115                         jobject options, bool allowPurgeable,
    116                         bool forcePurgeable = false) {
    117     int sampleSize = 1;
    118     SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
    119     SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
    120     bool doDither = true;
    121     bool isMutable = false;
    122     bool isPurgeable = forcePurgeable ||
    123                         (allowPurgeable && optionsPurgeable(env, options));
    124     bool preferQualityOverSpeed = false;
    125     jobject javaBitmap = NULL;
    126 
    127     if (NULL != options) {
    128         sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
    129         if (optionsJustBounds(env, options)) {
    130             mode = SkImageDecoder::kDecodeBounds_Mode;
    131         }
    132         // initialize these, in case we fail later on
    133         env->SetIntField(options, gOptions_widthFieldID, -1);
    134         env->SetIntField(options, gOptions_heightFieldID, -1);
    135         env->SetObjectField(options, gOptions_mimeFieldID, 0);
    136 
    137         jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
    138         prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig);
    139         isMutable = env->GetBooleanField(options, gOptions_mutableFieldID);
    140         doDither = env->GetBooleanField(options, gOptions_ditherFieldID);
    141         preferQualityOverSpeed = env->GetBooleanField(options,
    142                 gOptions_preferQualityOverSpeedFieldID);
    143         javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
    144     }
    145 
    146     SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
    147     if (NULL == decoder) {
    148         return nullObjectReturn("SkImageDecoder::Factory returned null");
    149     }
    150 
    151     decoder->setSampleSize(sampleSize);
    152     decoder->setDitherImage(doDither);
    153     decoder->setPreferQualityOverSpeed(preferQualityOverSpeed);
    154 
    155     NinePatchPeeker     peeker(decoder);
    156     JavaPixelAllocator  javaAllocator(env);
    157     SkBitmap*           bitmap;
    158     if (javaBitmap == NULL) {
    159         bitmap = new SkBitmap;
    160     } else {
    161         if (sampleSize != 1) {
    162             return nullObjectReturn("SkImageDecoder: Cannot reuse bitmap with sampleSize != 1");
    163         }
    164         bitmap = (SkBitmap *) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID);
    165         // config of supplied bitmap overrules config set in options
    166         prefConfig = bitmap->getConfig();
    167     }
    168     Res_png_9patch      dummy9Patch;
    169 
    170     SkAutoTDelete<SkImageDecoder>   add(decoder);
    171     SkAutoTDelete<SkBitmap>         adb(bitmap, (javaBitmap == NULL));
    172 
    173     decoder->setPeeker(&peeker);
    174     if (!isPurgeable) {
    175         decoder->setAllocator(&javaAllocator);
    176     }
    177 
    178     AutoDecoderCancel   adc(options, decoder);
    179 
    180     // To fix the race condition in case "requestCancelDecode"
    181     // happens earlier than AutoDecoderCancel object is added
    182     // to the gAutoDecoderCancelMutex linked list.
    183     if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) {
    184         return nullObjectReturn("gOptions_mCancelID");
    185     }
    186 
    187     SkImageDecoder::Mode decodeMode = mode;
    188     if (isPurgeable) {
    189         decodeMode = SkImageDecoder::kDecodeBounds_Mode;
    190     }
    191     if (!decoder->decode(stream, bitmap, prefConfig, decodeMode, javaBitmap != NULL)) {
    192         return nullObjectReturn("decoder->decode returned false");
    193     }
    194 
    195     // update options (if any)
    196     if (NULL != options) {
    197         env->SetIntField(options, gOptions_widthFieldID, bitmap->width());
    198         env->SetIntField(options, gOptions_heightFieldID, bitmap->height());
    199         // TODO: set the mimeType field with the data from the codec.
    200         // but how to reuse a set of strings, rather than allocating new one
    201         // each time?
    202         env->SetObjectField(options, gOptions_mimeFieldID,
    203                             getMimeTypeString(env, decoder->getFormat()));
    204     }
    205 
    206     // if we're in justBounds mode, return now (skip the java bitmap)
    207     if (SkImageDecoder::kDecodeBounds_Mode == mode) {
    208         return NULL;
    209     }
    210 
    211     jbyteArray ninePatchChunk = NULL;
    212     if (peeker.fPatchIsValid) {
    213         size_t ninePatchArraySize = peeker.fPatch->serializedSize();
    214         ninePatchChunk = env->NewByteArray(ninePatchArraySize);
    215         if (NULL == ninePatchChunk) {
    216             return nullObjectReturn("ninePatchChunk == null");
    217         }
    218         jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ninePatchChunk,
    219                                                               NULL);
    220         if (NULL == array) {
    221             return nullObjectReturn("primitive array == null");
    222         }
    223         peeker.fPatch->serialize(array);
    224         env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0);
    225     }
    226 
    227     // detach bitmap from its autodeleter, since we want to own it now
    228     adb.detach();
    229 
    230     if (padding) {
    231         if (peeker.fPatchIsValid) {
    232             GraphicsJNI::set_jrect(env, padding,
    233                                    peeker.fPatch->paddingLeft,
    234                                    peeker.fPatch->paddingTop,
    235                                    peeker.fPatch->paddingRight,
    236                                    peeker.fPatch->paddingBottom);
    237         } else {
    238             GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
    239         }
    240     }
    241 
    242     SkPixelRef* pr;
    243     if (isPurgeable) {
    244         pr = installPixelRef(bitmap, stream, sampleSize, doDither);
    245     } else {
    246         // if we get here, we're in kDecodePixels_Mode and will therefore
    247         // already have a pixelref installed.
    248         pr = bitmap->pixelRef();
    249     }
    250 
    251     if (!isMutable) {
    252         // promise we will never change our pixels (great for sharing and pictures)
    253         pr->setImmutable();
    254     }
    255 
    256     if (javaBitmap != NULL) {
    257         // If a java bitmap was passed in for reuse, pass it back
    258         return javaBitmap;
    259     }
    260     // now create the java bitmap
    261     return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(),
    262             isMutable, ninePatchChunk);
    263 }
    264 
    265 static jobject nativeDecodeStream(JNIEnv* env, jobject clazz,
    266                                   jobject is,       // InputStream
    267                                   jbyteArray storage,   // byte[]
    268                                   jobject padding,
    269                                   jobject options) {  // BitmapFactory$Options
    270     jobject bitmap = NULL;
    271     SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0);
    272 
    273     if (stream) {
    274         // for now we don't allow purgeable with java inputstreams
    275         bitmap = doDecode(env, stream, padding, options, false);
    276         stream->unref();
    277     }
    278     return bitmap;
    279 }
    280 
    281 static ssize_t getFDSize(int fd) {
    282     off64_t curr = ::lseek64(fd, 0, SEEK_CUR);
    283     if (curr < 0) {
    284         return 0;
    285     }
    286     size_t size = ::lseek(fd, 0, SEEK_END);
    287     ::lseek64(fd, curr, SEEK_SET);
    288     return size;
    289 }
    290 
    291 static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz,
    292                                           jobject fileDescriptor,
    293                                           jobject padding,
    294                                           jobject bitmapFactoryOptions) {
    295     NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
    296 
    297     jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
    298 
    299     bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions);
    300     bool isShareable = optionsShareable(env, bitmapFactoryOptions);
    301     bool weOwnTheFD = false;
    302     if (isPurgeable && isShareable) {
    303         int newFD = ::dup(descriptor);
    304         if (-1 != newFD) {
    305             weOwnTheFD = true;
    306             descriptor = newFD;
    307         }
    308     }
    309 
    310     SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD);
    311     SkAutoUnref aur(stream);
    312     if (!stream->isValid()) {
    313         return NULL;
    314     }
    315 
    316     /* Restore our offset when we leave, so we can be called more than once
    317        with the same descriptor. This is only required if we didn't dup the
    318        file descriptor, but it is OK to do it all the time.
    319     */
    320     AutoFDSeek as(descriptor);
    321 
    322     /* Allow purgeable iff we own the FD, i.e., in the puregeable and
    323        shareable case.
    324     */
    325     return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD);
    326 }
    327 
    328 /*  make a deep copy of the asset, and return it as a stream, or NULL if there
    329     was an error.
    330  */
    331 static SkStream* copyAssetToStream(Asset* asset) {
    332     // if we could "ref/reopen" the asset, we may not need to copy it here
    333     off64_t size = asset->seek(0, SEEK_SET);
    334     if ((off64_t)-1 == size) {
    335         SkDebugf("---- copyAsset: asset rewind failed\n");
    336         return NULL;
    337     }
    338 
    339     size = asset->getLength();
    340     if (size <= 0) {
    341         SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size);
    342         return NULL;
    343     }
    344 
    345     SkStream* stream = new SkMemoryStream(size);
    346     void* data = const_cast<void*>(stream->getMemoryBase());
    347     off64_t len = asset->read(data, size);
    348     if (len != size) {
    349         SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len);
    350         delete stream;
    351         stream = NULL;
    352     }
    353     return stream;
    354 }
    355 
    356 static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz,
    357                                  jint native_asset,    // Asset
    358                                  jobject padding,       // Rect
    359                                  jobject options) { // BitmapFactory$Options
    360     SkStream* stream;
    361     Asset* asset = reinterpret_cast<Asset*>(native_asset);
    362     bool forcePurgeable = optionsPurgeable(env, options);
    363     if (forcePurgeable) {
    364         // if we could "ref/reopen" the asset, we may not need to copy it here
    365         // and we could assume optionsShareable, since assets are always RO
    366         stream = copyAssetToStream(asset);
    367         if (NULL == stream) {
    368             return NULL;
    369         }
    370     } else {
    371         // since we know we'll be done with the asset when we return, we can
    372         // just use a simple wrapper
    373         stream = new AssetStreamAdaptor(asset);
    374     }
    375     SkAutoUnref aur(stream);
    376     return doDecode(env, stream, padding, options, true, forcePurgeable);
    377 }
    378 
    379 static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
    380                                      int offset, int length, jobject options) {
    381     /*  If optionsShareable() we could decide to just wrap the java array and
    382         share it, but that means adding a globalref to the java array object
    383         and managing its lifetime. For now we just always copy the array's data
    384         if optionsPurgeable(), unless we're just decoding bounds.
    385      */
    386     bool purgeable = optionsPurgeable(env, options)
    387             && !optionsJustBounds(env, options);
    388     AutoJavaByteArray ar(env, byteArray);
    389     SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
    390     SkAutoUnref aur(stream);
    391     return doDecode(env, stream, NULL, options, purgeable);
    392 }
    393 
    394 static void nativeRequestCancel(JNIEnv*, jobject joptions) {
    395     (void)AutoDecoderCancel::RequestCancel(joptions);
    396 }
    397 
    398 static jbyteArray nativeScaleNinePatch(JNIEnv* env, jobject, jbyteArray chunkObject, jfloat scale,
    399         jobject padding) {
    400 
    401     jbyte* array = env->GetByteArrayElements(chunkObject, 0);
    402     if (array != NULL) {
    403         size_t chunkSize = env->GetArrayLength(chunkObject);
    404         void* storage = alloca(chunkSize);
    405         android::Res_png_9patch* chunk = static_cast<android::Res_png_9patch*>(storage);
    406         memcpy(chunk, array, chunkSize);
    407         android::Res_png_9patch::deserialize(chunk);
    408 
    409         chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
    410         chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
    411         chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
    412         chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f);
    413 
    414         for (int i = 0; i < chunk->numXDivs; i++) {
    415             chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f);
    416             if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) {
    417                 chunk->xDivs[i]++;
    418             }
    419         }
    420 
    421         for (int i = 0; i < chunk->numYDivs; i++) {
    422             chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f);
    423             if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) {
    424                 chunk->yDivs[i]++;
    425             }
    426         }
    427 
    428         memcpy(array, chunk, chunkSize);
    429 
    430         if (padding) {
    431             GraphicsJNI::set_jrect(env, padding, chunk->paddingLeft, chunk->paddingTop,
    432                     chunk->paddingRight, chunk->paddingBottom);
    433         }
    434 
    435         env->ReleaseByteArrayElements(chunkObject, array, 0);
    436     }
    437     return chunkObject;
    438 }
    439 
    440 static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) {
    441     SkBitmap::Config config = static_cast<SkBitmap::Config>(nativeConfig);
    442 
    443     // these are the only default configs that make sense for codecs right now
    444     static const SkBitmap::Config gValidDefConfig[] = {
    445         SkBitmap::kRGB_565_Config,
    446         SkBitmap::kARGB_8888_Config,
    447     };
    448 
    449     for (size_t i = 0; i < SK_ARRAY_COUNT(gValidDefConfig); i++) {
    450         if (config == gValidDefConfig[i]) {
    451             SkImageDecoder::SetDeviceConfig(config);
    452             break;
    453         }
    454     }
    455 }
    456 
    457 static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) {
    458     jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
    459     return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE;
    460 }
    461 
    462 ///////////////////////////////////////////////////////////////////////////////
    463 
    464 static JNINativeMethod gMethods[] = {
    465     {   "nativeDecodeStream",
    466         "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    467         (void*)nativeDecodeStream
    468     },
    469 
    470     {   "nativeDecodeFileDescriptor",
    471         "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    472         (void*)nativeDecodeFileDescriptor
    473     },
    474 
    475     {   "nativeDecodeAsset",
    476         "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    477         (void*)nativeDecodeAsset
    478     },
    479 
    480     {   "nativeDecodeByteArray",
    481         "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;",
    482         (void*)nativeDecodeByteArray
    483     },
    484 
    485     {   "nativeScaleNinePatch",
    486         "([BFLandroid/graphics/Rect;)[B",
    487         (void*)nativeScaleNinePatch
    488     },
    489 
    490     {   "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig },
    491 
    492     {   "nativeIsSeekable",
    493         "(Ljava/io/FileDescriptor;)Z",
    494         (void*)nativeIsSeekable
    495     },
    496 };
    497 
    498 static JNINativeMethod gOptionsMethods[] = {
    499     {   "requestCancel", "()V", (void*)nativeRequestCancel }
    500 };
    501 
    502 static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz,
    503                                 const char fieldname[], const char type[]) {
    504     jfieldID id = env->GetFieldID(clazz, fieldname, type);
    505     SkASSERT(id);
    506     return id;
    507 }
    508 
    509 int register_android_graphics_BitmapFactory(JNIEnv* env) {
    510     jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options");
    511     SkASSERT(options_class);
    512     gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap",
    513         "Landroid/graphics/Bitmap;");
    514     gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z");
    515     gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I");
    516     gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig",
    517             "Landroid/graphics/Bitmap$Config;");
    518     gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z");
    519     gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z");
    520     gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z");
    521     gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
    522     gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
    523             "inPreferQualityOverSpeed", "Z");
    524     gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I");
    525     gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I");
    526     gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;");
    527     gOptions_mCancelID = getFieldIDCheck(env, options_class, "mCancel", "Z");
    528 
    529     jclass bitmap_class = env->FindClass("android/graphics/Bitmap");
    530     SkASSERT(bitmap_class);
    531     gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I");
    532 
    533     int ret = AndroidRuntime::registerNativeMethods(env,
    534                                     "android/graphics/BitmapFactory$Options",
    535                                     gOptionsMethods,
    536                                     SK_ARRAY_COUNT(gOptionsMethods));
    537     if (ret) {
    538         return ret;
    539     }
    540     return android::AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory",
    541                                          gMethods, SK_ARRAY_COUNT(gMethods));
    542 }
    543