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