• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
validOrNeg1(bool isValid,int32_t value)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 
getMimeTypeString(JNIEnv * env,SkImageDecoder::Format format)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 
optionsPurgeable(JNIEnv * env,jobject options)81 static bool optionsPurgeable(JNIEnv* env, jobject options) {
82     return options != NULL &&
83             env->GetBooleanField(options, gOptions_purgeableFieldID);
84 }
85 
optionsShareable(JNIEnv * env,jobject options)86 static bool optionsShareable(JNIEnv* env, jobject options) {
87     return options != NULL &&
88             env->GetBooleanField(options, gOptions_shareableFieldID);
89 }
90 
optionsJustBounds(JNIEnv * env,jobject options)91 static bool optionsJustBounds(JNIEnv* env, jobject options) {
92     return options != NULL &&
93             env->GetBooleanField(options, gOptions_justBoundsFieldID);
94 }
95 
installPixelRef(SkBitmap * bitmap,SkStream * stream,int sampleSize,bool ditherImage)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.
doDecode(JNIEnv * env,SkStream * stream,jobject padding,jobject options,bool allowPurgeable,bool forcePurgeable=false)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 
nativeDecodeStream(JNIEnv * env,jobject clazz,jobject is,jbyteArray storage,jobject padding,jobject options)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 
getFDSize(int fd)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 
nativeDecodeFileDescriptor(JNIEnv * env,jobject clazz,jobject fileDescriptor,jobject padding,jobject bitmapFactoryOptions)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  */
copyAssetToStream(Asset * asset)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 
nativeDecodeAsset(JNIEnv * env,jobject clazz,jint native_asset,jobject padding,jobject options)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 
nativeDecodeByteArray(JNIEnv * env,jobject,jbyteArray byteArray,int offset,int length,jobject options)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 
nativeRequestCancel(JNIEnv *,jobject joptions)394 static void nativeRequestCancel(JNIEnv*, jobject joptions) {
395     (void)AutoDecoderCancel::RequestCancel(joptions);
396 }
397 
nativeScaleNinePatch(JNIEnv * env,jobject,jbyteArray chunkObject,jfloat scale,jobject padding)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 
nativeSetDefaultConfig(JNIEnv * env,jobject,int nativeConfig)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 
nativeIsSeekable(JNIEnv * env,jobject,jobject fileDescriptor)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 
getFieldIDCheck(JNIEnv * env,jclass clazz,const char fieldname[],const char type[])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 
register_android_graphics_BitmapFactory(JNIEnv * env)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