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