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