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