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