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