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