BitmapFactory.cpp revision d63ee7565211d9a5dd4251079e3073886562b6d6
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_scaledFieldID; 33jfieldID gOptions_densityFieldID; 34jfieldID gOptions_screenDensityFieldID; 35jfieldID gOptions_targetDensityFieldID; 36jfieldID gOptions_widthFieldID; 37jfieldID gOptions_heightFieldID; 38jfieldID gOptions_mimeFieldID; 39jfieldID gOptions_mCancelID; 40jfieldID gOptions_bitmapFieldID; 41jfieldID gBitmap_nativeBitmapFieldID; 42jfieldID gBitmap_layoutBoundsFieldID; 43 44#if 0 45 #define TRACE_BITMAP(code) code 46#else 47 #define TRACE_BITMAP(code) 48#endif 49 50using namespace android; 51 52static inline int32_t validOrNeg1(bool isValid, int32_t value) { 53// return isValid ? value : -1; 54 SkASSERT((int)isValid == 0 || (int)isValid == 1); 55 return ((int32_t)isValid - 1) | value; 56} 57 58jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) { 59 static const struct { 60 SkImageDecoder::Format fFormat; 61 const char* fMimeType; 62 } gMimeTypes[] = { 63 { SkImageDecoder::kBMP_Format, "image/bmp" }, 64 { SkImageDecoder::kGIF_Format, "image/gif" }, 65 { SkImageDecoder::kICO_Format, "image/x-ico" }, 66 { SkImageDecoder::kJPEG_Format, "image/jpeg" }, 67 { SkImageDecoder::kPNG_Format, "image/png" }, 68 { SkImageDecoder::kWEBP_Format, "image/webp" }, 69 { SkImageDecoder::kWBMP_Format, "image/vnd.wap.wbmp" } 70 }; 71 72 const char* cstr = NULL; 73 for (size_t i = 0; i < SK_ARRAY_COUNT(gMimeTypes); i++) { 74 if (gMimeTypes[i].fFormat == format) { 75 cstr = gMimeTypes[i].fMimeType; 76 break; 77 } 78 } 79 80 jstring jstr = 0; 81 if (NULL != cstr) { 82 jstr = env->NewStringUTF(cstr); 83 } 84 return jstr; 85} 86 87static bool optionsPurgeable(JNIEnv* env, jobject options) { 88 return options != NULL && env->GetBooleanField(options, gOptions_purgeableFieldID); 89} 90 91static bool optionsShareable(JNIEnv* env, jobject options) { 92 return options != NULL && env->GetBooleanField(options, gOptions_shareableFieldID); 93} 94 95static bool optionsJustBounds(JNIEnv* env, jobject options) { 96 return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID); 97} 98 99static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) { 100 chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f); 101 chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f); 102 chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f); 103 chunk->paddingBottom = int(chunk->paddingBottom * scale + 0.5f); 104 105 for (int i = 0; i < chunk->numXDivs; i++) { 106 chunk->xDivs[i] = int(chunk->xDivs[i] * scale + 0.5f); 107 if (i > 0 && chunk->xDivs[i] == chunk->xDivs[i - 1]) { 108 chunk->xDivs[i]++; 109 } 110 } 111 112 for (int i = 0; i < chunk->numYDivs; i++) { 113 chunk->yDivs[i] = int(chunk->yDivs[i] * scale + 0.5f); 114 if (i > 0 && chunk->yDivs[i] == chunk->yDivs[i - 1]) { 115 chunk->yDivs[i]++; 116 } 117 } 118} 119 120static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, 121 int sampleSize, bool ditherImage) { 122 123 SkImageRef* pr; 124 // only use ashmem for large images, since mmaps come at a price 125 if (bitmap->getSize() >= 32 * 1024) { 126 pr = new SkImageRef_ashmem(stream, bitmap->config(), sampleSize); 127 } else { 128 pr = new SkImageRef_GlobalPool(stream, bitmap->config(), sampleSize); 129 } 130 pr->setDitherImage(ditherImage); 131 bitmap->setPixelRef(pr)->unref(); 132 pr->isOpaque(bitmap); 133 return pr; 134} 135 136static SkBitmap::Config configForScaledOutput(SkBitmap::Config config) { 137 switch (config) { 138 case SkBitmap::kNo_Config: 139 case SkBitmap::kIndex8_Config: 140 case SkBitmap::kRLE_Index8_Config: 141 return SkBitmap::kARGB_8888_Config; 142 default: 143 break; 144 } 145 return config; 146} 147 148class ScaleCheckingAllocator : public SkBitmap::HeapAllocator { 149public: 150 ScaleCheckingAllocator(float scale, int size) 151 : mScale(scale), mSize(size) { 152 } 153 154 virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { 155 // accounts for scale in final allocation, using eventual size and config 156 const int bytesPerPixel = SkBitmap::ComputeBytesPerPixel( 157 configForScaledOutput(bitmap->getConfig())); 158 const int requestedSize = bytesPerPixel * 159 int(bitmap->width() * mScale + 0.5f) * 160 int(bitmap->height() * mScale + 0.5f); 161 if (requestedSize > mSize) { 162 ALOGW("bitmap for alloc reuse (%d bytes) can't fit scaled bitmap (%d bytes)", 163 mSize, requestedSize); 164 return false; 165 } 166 return SkBitmap::HeapAllocator::allocPixelRef(bitmap, ctable); 167 } 168private: 169 const float mScale; 170 const int mSize; 171}; 172 173class RecyclingPixelAllocator : public SkBitmap::Allocator { 174public: 175 RecyclingPixelAllocator(SkPixelRef* pixelRef, unsigned int size) 176 : mPixelRef(pixelRef), mSize(size) { 177 SkSafeRef(mPixelRef); 178 } 179 180 ~RecyclingPixelAllocator() { 181 SkSafeUnref(mPixelRef); 182 } 183 184 virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { 185 if (!bitmap->getSize64().is32() || bitmap->getSize() > mSize) { 186 ALOGW("bitmap marked for reuse (%d bytes) can't fit new bitmap (%d bytes)", 187 mSize, bitmap->getSize()); 188 return false; 189 } 190 bitmap->setPixelRef(mPixelRef); 191 bitmap->lockPixels(); 192 return true; 193 } 194 195private: 196 SkPixelRef* const mPixelRef; 197 const unsigned int mSize; 198}; 199 200// since we "may" create a purgeable imageref, we require the stream be ref'able 201// i.e. dynamically allocated, since its lifetime may exceed the current stack 202// frame. 203static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, 204 jobject options, bool allowPurgeable, bool forcePurgeable = false) { 205 206 int sampleSize = 1; 207 208 SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode; 209 SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config; 210 211 bool doDither = true; 212 bool isMutable = false; 213 float scale = 1.0f; 214 bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options)); 215 bool preferQualityOverSpeed = false; 216 217 jobject javaBitmap = NULL; 218 219 if (options != NULL) { 220 sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); 221 if (optionsJustBounds(env, options)) { 222 mode = SkImageDecoder::kDecodeBounds_Mode; 223 } 224 225 // initialize these, in case we fail later on 226 env->SetIntField(options, gOptions_widthFieldID, -1); 227 env->SetIntField(options, gOptions_heightFieldID, -1); 228 env->SetObjectField(options, gOptions_mimeFieldID, 0); 229 230 jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); 231 prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); 232 isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); 233 doDither = env->GetBooleanField(options, gOptions_ditherFieldID); 234 preferQualityOverSpeed = env->GetBooleanField(options, 235 gOptions_preferQualityOverSpeedFieldID); 236 javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); 237 238 if (env->GetBooleanField(options, gOptions_scaledFieldID)) { 239 const int density = env->GetIntField(options, gOptions_densityFieldID); 240 const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); 241 const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); 242 if (density != 0 && targetDensity != 0 && density != screenDensity) { 243 scale = (float) targetDensity / density; 244 } 245 } 246 } 247 248 const bool willScale = scale != 1.0f; 249 isPurgeable &= !willScale; 250 251 SkImageDecoder* decoder = SkImageDecoder::Factory(stream); 252 if (decoder == NULL) { 253 return nullObjectReturn("SkImageDecoder::Factory returned null"); 254 } 255 256 decoder->setSampleSize(sampleSize); 257 decoder->setDitherImage(doDither); 258 decoder->setPreferQualityOverSpeed(preferQualityOverSpeed); 259 260 SkBitmap* outputBitmap = NULL; 261 unsigned int existingBufferSize = 0; 262 if (javaBitmap != NULL) { 263 outputBitmap = (SkBitmap*) env->GetIntField(javaBitmap, gBitmap_nativeBitmapFieldID); 264 if (outputBitmap->isImmutable()) { 265 ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); 266 javaBitmap = NULL; 267 outputBitmap = NULL; 268 } else { 269 existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap); 270 } 271 } 272 273 SkAutoTDelete<SkBitmap> adb(outputBitmap == NULL ? new SkBitmap : NULL); 274 if (outputBitmap == NULL) outputBitmap = adb.get(); 275 276 NinePatchPeeker peeker(decoder); 277 decoder->setPeeker(&peeker); 278 279 SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode; 280 281 JavaPixelAllocator javaAllocator(env); 282 RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize); 283 ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); 284 SkBitmap::Allocator* outputAllocator = (javaBitmap != NULL) ? 285 (SkBitmap::Allocator*)&recyclingAllocator : (SkBitmap::Allocator*)&javaAllocator; 286 if (decodeMode != SkImageDecoder::kDecodeBounds_Mode) { 287 if (!willScale) { 288 decoder->setAllocator(outputAllocator); 289 } else if (javaBitmap != NULL) { 290 // check for eventual scaled bounds at allocation time, so we don't decode the bitmap 291 // only to find the scaled result too large to fit in the allocation 292 decoder->setAllocator(&scaleCheckingAllocator); 293 } 294 } 295 296 // Only setup the decoder to be deleted after its stack-based, refcounted 297 // components (allocators, peekers, etc) are declared. This prevents RefCnt 298 // asserts from firing due to the order objects are deleted from the stack. 299 SkAutoTDelete<SkImageDecoder> add(decoder); 300 301 AutoDecoderCancel adc(options, decoder); 302 303 // To fix the race condition in case "requestCancelDecode" 304 // happens earlier than AutoDecoderCancel object is added 305 // to the gAutoDecoderCancelMutex linked list. 306 if (options != NULL && env->GetBooleanField(options, gOptions_mCancelID)) { 307 return nullObjectReturn("gOptions_mCancelID"); 308 } 309 310 SkBitmap decodingBitmap; 311 if (!decoder->decode(stream, &decodingBitmap, prefConfig, decodeMode)) { 312 return nullObjectReturn("decoder->decode returned false"); 313 } 314 315 int scaledWidth = decodingBitmap.width(); 316 int scaledHeight = decodingBitmap.height(); 317 318 if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { 319 scaledWidth = int(scaledWidth * scale + 0.5f); 320 scaledHeight = int(scaledHeight * scale + 0.5f); 321 } 322 323 // update options (if any) 324 if (options != NULL) { 325 env->SetIntField(options, gOptions_widthFieldID, scaledWidth); 326 env->SetIntField(options, gOptions_heightFieldID, scaledHeight); 327 env->SetObjectField(options, gOptions_mimeFieldID, 328 getMimeTypeString(env, decoder->getFormat())); 329 } 330 331 // if we're in justBounds mode, return now (skip the java bitmap) 332 if (mode == SkImageDecoder::kDecodeBounds_Mode) { 333 return NULL; 334 } 335 336 jbyteArray ninePatchChunk = NULL; 337 if (peeker.fPatch != NULL) { 338 if (willScale) { 339 scaleNinePatchChunk(peeker.fPatch, scale); 340 } 341 342 size_t ninePatchArraySize = peeker.fPatch->serializedSize(); 343 ninePatchChunk = env->NewByteArray(ninePatchArraySize); 344 if (ninePatchChunk == NULL) { 345 return nullObjectReturn("ninePatchChunk == null"); 346 } 347 348 jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); 349 if (array == NULL) { 350 return nullObjectReturn("primitive array == null"); 351 } 352 353 peeker.fPatch->serialize(array); 354 env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); 355 } 356 357 jintArray layoutBounds = NULL; 358 if (peeker.fLayoutBounds != NULL) { 359 layoutBounds = env->NewIntArray(4); 360 if (layoutBounds == NULL) { 361 return nullObjectReturn("layoutBounds == null"); 362 } 363 364 jint scaledBounds[4]; 365 if (willScale) { 366 for (int i=0; i<4; i++) { 367 scaledBounds[i] = (jint)((((jint*)peeker.fLayoutBounds)[i]*scale) + .5f); 368 } 369 } else { 370 memcpy(scaledBounds, (jint*)peeker.fLayoutBounds, sizeof(scaledBounds)); 371 } 372 env->SetIntArrayRegion(layoutBounds, 0, 4, scaledBounds); 373 if (javaBitmap != NULL) { 374 env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds); 375 } 376 } 377 378 if (willScale) { 379 // This is weird so let me explain: we could use the scale parameter 380 // directly, but for historical reasons this is how the corresponding 381 // Dalvik code has always behaved. We simply recreate the behavior here. 382 // The result is slightly different from simply using scale because of 383 // the 0.5f rounding bias applied when computing the target image size 384 const float sx = scaledWidth / float(decodingBitmap.width()); 385 const float sy = scaledHeight / float(decodingBitmap.height()); 386 387 // TODO: avoid copying when scaled size equals decodingBitmap size 388 SkBitmap::Config config = configForScaledOutput(decodingBitmap.config()); 389 outputBitmap->setConfig(config, scaledWidth, scaledHeight); 390 outputBitmap->setIsOpaque(decodingBitmap.isOpaque()); 391 if (!outputBitmap->allocPixels(outputAllocator, NULL)) { 392 return nullObjectReturn("allocation failed for scaled bitmap"); 393 } 394 outputBitmap->eraseColor(0); 395 396 SkPaint paint; 397 paint.setFilterBitmap(true); 398 399 SkCanvas canvas(*outputBitmap); 400 canvas.scale(sx, sy); 401 canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); 402 } else { 403 outputBitmap->swap(decodingBitmap); 404 } 405 406 if (padding) { 407 if (peeker.fPatch != NULL) { 408 GraphicsJNI::set_jrect(env, padding, 409 peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, 410 peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); 411 } else { 412 GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1); 413 } 414 } 415 416 SkPixelRef* pr; 417 if (isPurgeable) { 418 pr = installPixelRef(outputBitmap, stream, sampleSize, doDither); 419 } else { 420 // if we get here, we're in kDecodePixels_Mode and will therefore 421 // already have a pixelref installed. 422 pr = outputBitmap->pixelRef(); 423 } 424 if (pr == NULL) { 425 return nullObjectReturn("Got null SkPixelRef"); 426 } 427 428 if (!isMutable && javaBitmap == NULL) { 429 // promise we will never change our pixels (great for sharing and pictures) 430 pr->setImmutable(); 431 } 432 433 // detach bitmap from its autodeleter, since we want to own it now 434 adb.detach(); 435 436 if (javaBitmap != NULL) { 437 GraphicsJNI::reinitBitmap(env, javaBitmap); 438 outputBitmap->notifyPixelsChanged(); 439 // If a java bitmap was passed in for reuse, pass it back 440 return javaBitmap; 441 } 442 // now create the java bitmap 443 return GraphicsJNI::createBitmap(env, outputBitmap, javaAllocator.getStorageObj(), 444 isMutable, ninePatchChunk, layoutBounds, -1); 445} 446 447static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, 448 jobject padding, jobject options) { 449 450 jobject bitmap = NULL; 451 SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0); 452 453 if (stream) { 454 // for now we don't allow purgeable with java inputstreams 455 bitmap = doDecode(env, stream, padding, options, false, false); 456 stream->unref(); 457 } 458 return bitmap; 459} 460 461static ssize_t getFDSize(int fd) { 462 off64_t curr = ::lseek64(fd, 0, SEEK_CUR); 463 if (curr < 0) { 464 return 0; 465 } 466 size_t size = ::lseek(fd, 0, SEEK_END); 467 ::lseek64(fd, curr, SEEK_SET); 468 return size; 469} 470 471static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, 472 jobject padding, jobject bitmapFactoryOptions) { 473 474 NPE_CHECK_RETURN_ZERO(env, fileDescriptor); 475 476 jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); 477 478 bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions); 479 bool isShareable = optionsShareable(env, bitmapFactoryOptions); 480 bool weOwnTheFD = false; 481 if (isPurgeable && isShareable) { 482 int newFD = ::dup(descriptor); 483 if (-1 != newFD) { 484 weOwnTheFD = true; 485 descriptor = newFD; 486 } 487 } 488 489 SkFDStream* stream = new SkFDStream(descriptor, weOwnTheFD); 490 SkAutoUnref aur(stream); 491 if (!stream->isValid()) { 492 return NULL; 493 } 494 495 /* Restore our offset when we leave, so we can be called more than once 496 with the same descriptor. This is only required if we didn't dup the 497 file descriptor, but it is OK to do it all the time. 498 */ 499 AutoFDSeek as(descriptor); 500 501 /* Allow purgeable iff we own the FD, i.e., in the puregeable and 502 shareable case. 503 */ 504 return doDecode(env, stream, padding, bitmapFactoryOptions, weOwnTheFD); 505} 506 507/* make a deep copy of the asset, and return it as a stream, or NULL if there 508 was an error. 509 */ 510static SkStream* copyAssetToStream(Asset* asset) { 511 // if we could "ref/reopen" the asset, we may not need to copy it here 512 off64_t size = asset->seek(0, SEEK_SET); 513 if ((off64_t)-1 == size) { 514 SkDebugf("---- copyAsset: asset rewind failed\n"); 515 return NULL; 516 } 517 518 size = asset->getLength(); 519 if (size <= 0) { 520 SkDebugf("---- copyAsset: asset->getLength() returned %d\n", size); 521 return NULL; 522 } 523 524 SkStream* stream = new SkMemoryStream(size); 525 void* data = const_cast<void*>(stream->getMemoryBase()); 526 off64_t len = asset->read(data, size); 527 if (len != size) { 528 SkDebugf("---- copyAsset: asset->read(%d) returned %d\n", size, len); 529 delete stream; 530 stream = NULL; 531 } 532 return stream; 533} 534 535static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jint native_asset, 536 jobject padding, jobject options) { 537 538 SkStream* stream; 539 Asset* asset = reinterpret_cast<Asset*>(native_asset); 540 bool forcePurgeable = optionsPurgeable(env, options); 541 if (forcePurgeable) { 542 // if we could "ref/reopen" the asset, we may not need to copy it here 543 // and we could assume optionsShareable, since assets are always RO 544 stream = copyAssetToStream(asset); 545 if (stream == NULL) { 546 return NULL; 547 } 548 } else { 549 // since we know we'll be done with the asset when we return, we can 550 // just use a simple wrapper 551 stream = new AssetStreamAdaptor(asset); 552 } 553 SkAutoUnref aur(stream); 554 return doDecode(env, stream, padding, options, true, forcePurgeable); 555} 556 557static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, 558 int offset, int length, jobject options) { 559 560 /* If optionsShareable() we could decide to just wrap the java array and 561 share it, but that means adding a globalref to the java array object 562 and managing its lifetime. For now we just always copy the array's data 563 if optionsPurgeable(), unless we're just decoding bounds. 564 */ 565 bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options); 566 AutoJavaByteArray ar(env, byteArray); 567 SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable); 568 SkAutoUnref aur(stream); 569 return doDecode(env, stream, NULL, options, purgeable); 570} 571 572static void nativeRequestCancel(JNIEnv*, jobject joptions) { 573 (void)AutoDecoderCancel::RequestCancel(joptions); 574} 575 576static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { 577 jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); 578 return ::lseek64(descriptor, 0, SEEK_CUR) != -1 ? JNI_TRUE : JNI_FALSE; 579} 580 581/////////////////////////////////////////////////////////////////////////////// 582 583static JNINativeMethod gMethods[] = { 584 { "nativeDecodeStream", 585 "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", 586 (void*)nativeDecodeStream 587 }, 588 589 { "nativeDecodeFileDescriptor", 590 "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", 591 (void*)nativeDecodeFileDescriptor 592 }, 593 594 { "nativeDecodeAsset", 595 "(ILandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", 596 (void*)nativeDecodeAsset 597 }, 598 599 { "nativeDecodeByteArray", 600 "([BIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", 601 (void*)nativeDecodeByteArray 602 }, 603 604 { "nativeIsSeekable", 605 "(Ljava/io/FileDescriptor;)Z", 606 (void*)nativeIsSeekable 607 }, 608}; 609 610static JNINativeMethod gOptionsMethods[] = { 611 { "requestCancel", "()V", (void*)nativeRequestCancel } 612}; 613 614static jfieldID getFieldIDCheck(JNIEnv* env, jclass clazz, 615 const char fieldname[], const char type[]) { 616 jfieldID id = env->GetFieldID(clazz, fieldname, type); 617 SkASSERT(id); 618 return id; 619} 620 621int register_android_graphics_BitmapFactory(JNIEnv* env) { 622 jclass options_class = env->FindClass("android/graphics/BitmapFactory$Options"); 623 SkASSERT(options_class); 624 gOptions_bitmapFieldID = getFieldIDCheck(env, options_class, "inBitmap", 625 "Landroid/graphics/Bitmap;"); 626 gOptions_justBoundsFieldID = getFieldIDCheck(env, options_class, "inJustDecodeBounds", "Z"); 627 gOptions_sampleSizeFieldID = getFieldIDCheck(env, options_class, "inSampleSize", "I"); 628 gOptions_configFieldID = getFieldIDCheck(env, options_class, "inPreferredConfig", 629 "Landroid/graphics/Bitmap$Config;"); 630 gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z"); 631 gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z"); 632 gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z"); 633 gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z"); 634 gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class, 635 "inPreferQualityOverSpeed", "Z"); 636 gOptions_scaledFieldID = getFieldIDCheck(env, options_class, "inScaled", "Z"); 637 gOptions_densityFieldID = getFieldIDCheck(env, options_class, "inDensity", "I"); 638 gOptions_screenDensityFieldID = getFieldIDCheck(env, options_class, "inScreenDensity", "I"); 639 gOptions_targetDensityFieldID = getFieldIDCheck(env, options_class, "inTargetDensity", "I"); 640 gOptions_widthFieldID = getFieldIDCheck(env, options_class, "outWidth", "I"); 641 gOptions_heightFieldID = getFieldIDCheck(env, options_class, "outHeight", "I"); 642 gOptions_mimeFieldID = getFieldIDCheck(env, options_class, "outMimeType", "Ljava/lang/String;"); 643 gOptions_mCancelID = getFieldIDCheck(env, options_class, "mCancel", "Z"); 644 645 jclass bitmap_class = env->FindClass("android/graphics/Bitmap"); 646 SkASSERT(bitmap_class); 647 gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I"); 648 gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mLayoutBounds", "[I"); 649 int ret = AndroidRuntime::registerNativeMethods(env, 650 "android/graphics/BitmapFactory$Options", 651 gOptionsMethods, 652 SK_ARRAY_COUNT(gOptionsMethods)); 653 if (ret) { 654 return ret; 655 } 656 return android::AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory", 657 gMethods, SK_ARRAY_COUNT(gMethods)); 658} 659