diff options
Diffstat (limited to 'libs/hwui/jni/BitmapFactory.cpp')
-rw-r--r-- | libs/hwui/jni/BitmapFactory.cpp | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/libs/hwui/jni/BitmapFactory.cpp b/libs/hwui/jni/BitmapFactory.cpp new file mode 100644 index 000000000000..e8e89d81bdb7 --- /dev/null +++ b/libs/hwui/jni/BitmapFactory.cpp @@ -0,0 +1,667 @@ +#undef LOG_TAG +#define LOG_TAG "BitmapFactory" + +#include "BitmapFactory.h" +#include "CreateJavaOutputStreamAdaptor.h" +#include "GraphicsJNI.h" +#include "MimeType.h" +#include "NinePatchPeeker.h" +#include "SkAndroidCodec.h" +#include "SkBRDAllocator.h" +#include "SkFrontBufferedStream.h" +#include "SkMath.h" +#include "SkPixelRef.h" +#include "SkStream.h" +#include "SkUtils.h" +#include "Utils.h" + +#include <HardwareBitmapUploader.h> +#include <nativehelper/JNIPlatformHelp.h> +#include <androidfw/Asset.h> +#include <androidfw/ResourceTypes.h> +#include <cutils/compiler.h> +#include <fcntl.h> +#include <memory> +#include <stdio.h> +#include <sys/stat.h> + +jfieldID gOptions_justBoundsFieldID; +jfieldID gOptions_sampleSizeFieldID; +jfieldID gOptions_configFieldID; +jfieldID gOptions_colorSpaceFieldID; +jfieldID gOptions_premultipliedFieldID; +jfieldID gOptions_mutableFieldID; +jfieldID gOptions_ditherFieldID; +jfieldID gOptions_preferQualityOverSpeedFieldID; +jfieldID gOptions_scaledFieldID; +jfieldID gOptions_densityFieldID; +jfieldID gOptions_screenDensityFieldID; +jfieldID gOptions_targetDensityFieldID; +jfieldID gOptions_widthFieldID; +jfieldID gOptions_heightFieldID; +jfieldID gOptions_mimeFieldID; +jfieldID gOptions_outConfigFieldID; +jfieldID gOptions_outColorSpaceFieldID; +jfieldID gOptions_mCancelID; +jfieldID gOptions_bitmapFieldID; + +jfieldID gBitmap_ninePatchInsetsFieldID; + +jclass gBitmapConfig_class; +jmethodID gBitmapConfig_nativeToConfigMethodID; + +using namespace android; + +const char* getMimeType(SkEncodedImageFormat format) { + switch (format) { + case SkEncodedImageFormat::kBMP: + return "image/bmp"; + case SkEncodedImageFormat::kGIF: + return "image/gif"; + case SkEncodedImageFormat::kICO: + return "image/x-ico"; + case SkEncodedImageFormat::kJPEG: + return "image/jpeg"; + case SkEncodedImageFormat::kPNG: + return "image/png"; + case SkEncodedImageFormat::kWEBP: + return "image/webp"; + case SkEncodedImageFormat::kHEIF: + return "image/heif"; + case SkEncodedImageFormat::kWBMP: + return "image/vnd.wap.wbmp"; + case SkEncodedImageFormat::kDNG: + return "image/x-adobe-dng"; + default: + return nullptr; + } +} + +jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) { + jstring jstr = nullptr; + const char* mimeType = getMimeType(format); + if (mimeType) { + // NOTE: Caller should env->ExceptionCheck() for OOM + // (can't check for nullptr as it's a valid return value) + jstr = env->NewStringUTF(mimeType); + } + return jstr; +} + +class ScaleCheckingAllocator : public SkBitmap::HeapAllocator { +public: + ScaleCheckingAllocator(float scale, int size) + : mScale(scale), mSize(size) { + } + + virtual bool allocPixelRef(SkBitmap* bitmap) { + // accounts for scale in final allocation, using eventual size and config + const int bytesPerPixel = SkColorTypeBytesPerPixel(bitmap->colorType()); + const int requestedSize = bytesPerPixel * + int(bitmap->width() * mScale + 0.5f) * + int(bitmap->height() * mScale + 0.5f); + if (requestedSize > mSize) { + ALOGW("bitmap for alloc reuse (%d bytes) can't fit scaled bitmap (%d bytes)", + mSize, requestedSize); + return false; + } + return SkBitmap::HeapAllocator::allocPixelRef(bitmap); + } +private: + const float mScale; + const int mSize; +}; + +class RecyclingPixelAllocator : public SkBitmap::Allocator { +public: + RecyclingPixelAllocator(android::Bitmap* bitmap, unsigned int size) + : mBitmap(bitmap), mSize(size) { + } + + ~RecyclingPixelAllocator() { + } + + virtual bool allocPixelRef(SkBitmap* bitmap) { + const SkImageInfo& info = bitmap->info(); + if (info.colorType() == kUnknown_SkColorType) { + ALOGW("unable to reuse a bitmap as the target has an unknown bitmap configuration"); + return false; + } + + const size_t size = info.computeByteSize(bitmap->rowBytes()); + if (size > SK_MaxS32) { + ALOGW("bitmap is too large"); + return false; + } + + if (size > mSize) { + ALOGW("bitmap marked for reuse (%u bytes) can't fit new bitmap " + "(%zu bytes)", mSize, size); + return false; + } + + mBitmap->reconfigure(info, bitmap->rowBytes()); + bitmap->setPixelRef(sk_ref_sp(mBitmap), 0, 0); + return true; + } + +private: + android::Bitmap* const mBitmap; + const unsigned int mSize; +}; + +// Necessary for decodes when the native decoder cannot scale to appropriately match the sampleSize +// (for example, RAW). If the sampleSize divides evenly into the dimension, we require that the +// scale matches exactly. If sampleSize does not divide evenly, we allow the decoder to choose how +// best to round. +static bool needsFineScale(const int fullSize, const int decodedSize, const int sampleSize) { + if (fullSize % sampleSize == 0 && fullSize / sampleSize != decodedSize) { + return true; + } else if ((fullSize / sampleSize + 1) != decodedSize && + (fullSize / sampleSize) != decodedSize) { + return true; + } + return false; +} + +static bool needsFineScale(const SkISize fullSize, const SkISize decodedSize, + const int sampleSize) { + return needsFineScale(fullSize.width(), decodedSize.width(), sampleSize) || + needsFineScale(fullSize.height(), decodedSize.height(), sampleSize); +} + +static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, + jobject padding, jobject options, jlong inBitmapHandle, + jlong colorSpaceHandle) { + // Set default values for the options parameters. + int sampleSize = 1; + bool onlyDecodeSize = false; + SkColorType prefColorType = kN32_SkColorType; + bool isHardware = false; + bool isMutable = false; + float scale = 1.0f; + bool requireUnpremultiplied = false; + jobject javaBitmap = NULL; + sk_sp<SkColorSpace> prefColorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle); + + // Update with options supplied by the client. + if (options != NULL) { + sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); + // Correct a non-positive sampleSize. sampleSize defaults to zero within the + // options object, which is strange. + if (sampleSize <= 0) { + sampleSize = 1; + } + + if (env->GetBooleanField(options, gOptions_justBoundsFieldID)) { + onlyDecodeSize = true; + } + + // initialize these, in case we fail later on + env->SetIntField(options, gOptions_widthFieldID, -1); + env->SetIntField(options, gOptions_heightFieldID, -1); + env->SetObjectField(options, gOptions_mimeFieldID, 0); + env->SetObjectField(options, gOptions_outConfigFieldID, 0); + env->SetObjectField(options, gOptions_outColorSpaceFieldID, 0); + + jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); + prefColorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig); + isHardware = GraphicsJNI::isHardwareConfig(env, jconfig); + isMutable = env->GetBooleanField(options, gOptions_mutableFieldID); + requireUnpremultiplied = !env->GetBooleanField(options, gOptions_premultipliedFieldID); + javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID); + + if (env->GetBooleanField(options, gOptions_scaledFieldID)) { + const int density = env->GetIntField(options, gOptions_densityFieldID); + const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); + const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); + if (density != 0 && targetDensity != 0 && density != screenDensity) { + scale = (float) targetDensity / density; + } + } + } + + if (isMutable && isHardware) { + doThrowIAE(env, "Bitmaps with Config.HARDWARE are always immutable"); + return nullObjectReturn("Cannot create mutable hardware bitmap"); + } + + // Create the codec. + NinePatchPeeker peeker; + std::unique_ptr<SkAndroidCodec> codec; + { + SkCodec::Result result; + std::unique_ptr<SkCodec> c = SkCodec::MakeFromStream(std::move(stream), &result, + &peeker); + if (!c) { + SkString msg; + msg.printf("Failed to create image decoder with message '%s'", + SkCodec::ResultToString(result)); + return nullObjectReturn(msg.c_str()); + } + + codec = SkAndroidCodec::MakeFromCodec(std::move(c)); + if (!codec) { + return nullObjectReturn("SkAndroidCodec::MakeFromCodec returned null"); + } + } + + // Do not allow ninepatch decodes to 565. In the past, decodes to 565 + // would dither, and we do not want to pre-dither ninepatches, since we + // know that they will be stretched. We no longer dither 565 decodes, + // but we continue to prevent ninepatches from decoding to 565, in order + // to maintain the old behavior. + if (peeker.mPatch && kRGB_565_SkColorType == prefColorType) { + prefColorType = kN32_SkColorType; + } + + // Determine the output size. + SkISize size = codec->getSampledDimensions(sampleSize); + + int scaledWidth = size.width(); + int scaledHeight = size.height(); + bool willScale = false; + + // Apply a fine scaling step if necessary. + if (needsFineScale(codec->getInfo().dimensions(), size, sampleSize)) { + willScale = true; + scaledWidth = codec->getInfo().width() / sampleSize; + scaledHeight = codec->getInfo().height() / sampleSize; + } + + // Set the decode colorType + SkColorType decodeColorType = codec->computeOutputColorType(prefColorType); + if (decodeColorType == kRGBA_F16_SkColorType && isHardware && + !uirenderer::HardwareBitmapUploader::hasFP16Support()) { + decodeColorType = kN32_SkColorType; + } + + sk_sp<SkColorSpace> decodeColorSpace = codec->computeOutputColorSpace( + decodeColorType, prefColorSpace); + + // Set the options and return if the client only wants the size. + if (options != NULL) { + jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat()); + if (env->ExceptionCheck()) { + return nullObjectReturn("OOM in getMimeTypeAsJavaString()"); + } + env->SetIntField(options, gOptions_widthFieldID, scaledWidth); + env->SetIntField(options, gOptions_heightFieldID, scaledHeight); + env->SetObjectField(options, gOptions_mimeFieldID, mimeType); + + jint configID = GraphicsJNI::colorTypeToLegacyBitmapConfig(decodeColorType); + if (isHardware) { + configID = GraphicsJNI::kHardware_LegacyBitmapConfig; + } + jobject config = env->CallStaticObjectMethod(gBitmapConfig_class, + gBitmapConfig_nativeToConfigMethodID, configID); + env->SetObjectField(options, gOptions_outConfigFieldID, config); + + env->SetObjectField(options, gOptions_outColorSpaceFieldID, + GraphicsJNI::getColorSpace(env, decodeColorSpace.get(), decodeColorType)); + + if (onlyDecodeSize) { + return nullptr; + } + } + + // Scale is necessary due to density differences. + if (scale != 1.0f) { + willScale = true; + scaledWidth = static_cast<int>(scaledWidth * scale + 0.5f); + scaledHeight = static_cast<int>(scaledHeight * scale + 0.5f); + } + + android::Bitmap* reuseBitmap = nullptr; + unsigned int existingBufferSize = 0; + if (javaBitmap != nullptr) { + reuseBitmap = &bitmap::toBitmap(inBitmapHandle); + if (reuseBitmap->isImmutable()) { + ALOGW("Unable to reuse an immutable bitmap as an image decoder target."); + javaBitmap = nullptr; + reuseBitmap = nullptr; + } else { + existingBufferSize = reuseBitmap->getAllocationByteCount(); + } + } + + HeapAllocator defaultAllocator; + RecyclingPixelAllocator recyclingAllocator(reuseBitmap, existingBufferSize); + ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize); + SkBitmap::HeapAllocator heapAllocator; + SkBitmap::Allocator* decodeAllocator; + if (javaBitmap != nullptr && willScale) { + // This will allocate pixels using a HeapAllocator, since there will be an extra + // scaling step that copies these pixels into Java memory. This allocator + // also checks that the recycled javaBitmap is large enough. + decodeAllocator = &scaleCheckingAllocator; + } else if (javaBitmap != nullptr) { + decodeAllocator = &recyclingAllocator; + } else if (willScale || isHardware) { + // This will allocate pixels using a HeapAllocator, + // for scale case: there will be an extra scaling step. + // for hardware case: there will be extra swizzling & upload to gralloc step. + decodeAllocator = &heapAllocator; + } else { + decodeAllocator = &defaultAllocator; + } + + SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied); + + const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), + decodeColorType, alphaType, decodeColorSpace); + + SkImageInfo bitmapInfo = decodeInfo; + if (decodeColorType == kGray_8_SkColorType) { + // The legacy implementation of BitmapFactory used kAlpha8 for + // grayscale images (before kGray8 existed). While the codec + // recognizes kGray8, we need to decode into a kAlpha8 bitmap + // in order to avoid a behavior change. + bitmapInfo = + bitmapInfo.makeColorType(kAlpha_8_SkColorType).makeAlphaType(kPremul_SkAlphaType); + } + SkBitmap decodingBitmap; + if (!decodingBitmap.setInfo(bitmapInfo) || + !decodingBitmap.tryAllocPixels(decodeAllocator)) { + // SkAndroidCodec should recommend a valid SkImageInfo, so setInfo() + // should only only fail if the calculated value for rowBytes is too + // large. + // tryAllocPixels() can fail due to OOM on the Java heap, OOM on the + // native heap, or the recycled javaBitmap being too small to reuse. + return nullptr; + } + + // Use SkAndroidCodec to perform the decode. + SkAndroidCodec::AndroidOptions codecOptions; + codecOptions.fZeroInitialized = decodeAllocator == &defaultAllocator ? + SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized; + codecOptions.fSampleSize = sampleSize; + SkCodec::Result result = codec->getAndroidPixels(decodeInfo, decodingBitmap.getPixels(), + decodingBitmap.rowBytes(), &codecOptions); + switch (result) { + case SkCodec::kSuccess: + case SkCodec::kIncompleteInput: + break; + default: + return nullObjectReturn("codec->getAndroidPixels() failed."); + } + + // This is weird so let me explain: we could use the scale parameter + // directly, but for historical reasons this is how the corresponding + // Dalvik code has always behaved. We simply recreate the behavior here. + // The result is slightly different from simply using scale because of + // the 0.5f rounding bias applied when computing the target image size + const float scaleX = scaledWidth / float(decodingBitmap.width()); + const float scaleY = scaledHeight / float(decodingBitmap.height()); + + jbyteArray ninePatchChunk = NULL; + if (peeker.mPatch != NULL) { + if (willScale) { + peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight); + } + + size_t ninePatchArraySize = peeker.mPatch->serializedSize(); + ninePatchChunk = env->NewByteArray(ninePatchArraySize); + if (ninePatchChunk == NULL) { + return nullObjectReturn("ninePatchChunk == null"); + } + + jbyte* array = (jbyte*) env->GetPrimitiveArrayCritical(ninePatchChunk, NULL); + if (array == NULL) { + return nullObjectReturn("primitive array == null"); + } + + memcpy(array, peeker.mPatch, peeker.mPatchSize); + env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); + } + + jobject ninePatchInsets = NULL; + if (peeker.mHasInsets) { + ninePatchInsets = peeker.createNinePatchInsets(env, scale); + if (ninePatchInsets == NULL) { + return nullObjectReturn("nine patch insets == null"); + } + if (javaBitmap != NULL) { + env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets); + } + } + + SkBitmap outputBitmap; + if (willScale) { + // Set the allocator for the outputBitmap. + SkBitmap::Allocator* outputAllocator; + if (javaBitmap != nullptr) { + outputAllocator = &recyclingAllocator; + } else { + outputAllocator = &defaultAllocator; + } + + SkColorType scaledColorType = decodingBitmap.colorType(); + // FIXME: If the alphaType is kUnpremul and the image has alpha, the + // colors may not be correct, since Skia does not yet support drawing + // to/from unpremultiplied bitmaps. + outputBitmap.setInfo( + bitmapInfo.makeWH(scaledWidth, scaledHeight).makeColorType(scaledColorType)); + if (!outputBitmap.tryAllocPixels(outputAllocator)) { + // This should only fail on OOM. The recyclingAllocator should have + // enough memory since we check this before decoding using the + // scaleCheckingAllocator. + return nullObjectReturn("allocation failed for scaled bitmap"); + } + + SkPaint paint; + // kSrc_Mode instructs us to overwrite the uninitialized pixels in + // outputBitmap. Otherwise we would blend by default, which is not + // what we want. + paint.setBlendMode(SkBlendMode::kSrc); + paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering + + SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint); + } else { + outputBitmap.swap(decodingBitmap); + } + + if (padding) { + peeker.getPadding(env, padding); + } + + // If we get here, the outputBitmap should have an installed pixelref. + if (outputBitmap.pixelRef() == NULL) { + return nullObjectReturn("Got null SkPixelRef"); + } + + if (!isMutable && javaBitmap == NULL) { + // promise we will never change our pixels (great for sharing and pictures) + outputBitmap.setImmutable(); + } + + bool isPremultiplied = !requireUnpremultiplied; + if (javaBitmap != nullptr) { + bitmap::reinitBitmap(env, javaBitmap, outputBitmap.info(), isPremultiplied); + outputBitmap.notifyPixelsChanged(); + // If a java bitmap was passed in for reuse, pass it back + return javaBitmap; + } + + int bitmapCreateFlags = 0x0; + if (isMutable) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Mutable; + if (isPremultiplied) bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied; + + if (isHardware) { + sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(outputBitmap); + if (!hardwareBitmap.get()) { + return nullObjectReturn("Failed to allocate a hardware bitmap"); + } + return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags, + ninePatchChunk, ninePatchInsets, -1); + } + + // now create the java bitmap + return bitmap::createBitmap(env, defaultAllocator.getStorageObjAndReset(), + bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1); +} + +static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, + jobject padding, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) { + + jobject bitmap = NULL; + std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage)); + + if (stream.get()) { + std::unique_ptr<SkStreamRewindable> bufferedStream( + SkFrontBufferedStream::Make(std::move(stream), SkCodec::MinBufferedBytesNeeded())); + SkASSERT(bufferedStream.get() != NULL); + bitmap = doDecode(env, std::move(bufferedStream), padding, options, inBitmapHandle, + colorSpaceHandle); + } + return bitmap; +} + +static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fileDescriptor, + jobject padding, jobject bitmapFactoryOptions, jlong inBitmapHandle, jlong colorSpaceHandle) { +#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC + return nullObjectReturn("Not supported on Windows"); +#else + NPE_CHECK_RETURN_ZERO(env, fileDescriptor); + + int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + + struct stat fdStat; + if (fstat(descriptor, &fdStat) == -1) { + doThrowIOE(env, "broken file descriptor"); + return nullObjectReturn("fstat return -1"); + } + + // Restore the descriptor's offset on exiting this function. Even though + // we dup the descriptor, both the original and dup refer to the same open + // file description and changes to the file offset in one impact the other. + AutoFDSeek autoRestore(descriptor); + + // Duplicate the descriptor here to prevent leaking memory. A leak occurs + // if we only close the file descriptor and not the file object it is used to + // create. If we don't explicitly clean up the file (which in turn closes the + // descriptor) the buffers allocated internally by fseek will be leaked. + int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0); + + FILE* file = fdopen(dupDescriptor, "r"); + if (file == NULL) { + // cleanup the duplicated descriptor since it will not be closed when the + // file is cleaned up (fclose). + close(dupDescriptor); + return nullObjectReturn("Could not open file"); + } + + std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file)); + + // If there is no offset for the file descriptor, we use SkFILEStream directly. + if (::lseek(descriptor, 0, SEEK_CUR) == 0) { + assert(isSeekable(dupDescriptor)); + return doDecode(env, std::move(fileStream), padding, bitmapFactoryOptions, + inBitmapHandle, colorSpaceHandle); + } + + // Use a buffered stream. Although an SkFILEStream can be rewound, this + // ensures that SkImageDecoder::Factory never rewinds beyond the + // current position of the file descriptor. + std::unique_ptr<SkStreamRewindable> stream(SkFrontBufferedStream::Make(std::move(fileStream), + SkCodec::MinBufferedBytesNeeded())); + + return doDecode(env, std::move(stream), padding, bitmapFactoryOptions, inBitmapHandle, + colorSpaceHandle); +#endif +} + +static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset, + jobject padding, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) { + + Asset* asset = reinterpret_cast<Asset*>(native_asset); + // since we know we'll be done with the asset when we return, we can + // just use a simple wrapper + return doDecode(env, std::make_unique<AssetStreamAdaptor>(asset), padding, options, + inBitmapHandle, colorSpaceHandle); +} + +static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray, + jint offset, jint length, jobject options, jlong inBitmapHandle, jlong colorSpaceHandle) { + + AutoJavaByteArray ar(env, byteArray); + return doDecode(env, std::make_unique<SkMemoryStream>(ar.ptr() + offset, length, false), + nullptr, options, inBitmapHandle, colorSpaceHandle); +} + +static jboolean nativeIsSeekable(JNIEnv* env, jobject, jobject fileDescriptor) { + jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor); + return isSeekable(descriptor) ? JNI_TRUE : JNI_FALSE; +} + +/////////////////////////////////////////////////////////////////////////////// + +static const JNINativeMethod gMethods[] = { + { "nativeDecodeStream", + "(Ljava/io/InputStream;[BLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeStream + }, + + { "nativeDecodeFileDescriptor", + "(Ljava/io/FileDescriptor;Landroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeFileDescriptor + }, + + { "nativeDecodeAsset", + "(JLandroid/graphics/Rect;Landroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeAsset + }, + + { "nativeDecodeByteArray", + "([BIILandroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;", + (void*)nativeDecodeByteArray + }, + + { "nativeIsSeekable", + "(Ljava/io/FileDescriptor;)Z", + (void*)nativeIsSeekable + }, +}; + +int register_android_graphics_BitmapFactory(JNIEnv* env) { + jclass options_class = FindClassOrDie(env, "android/graphics/BitmapFactory$Options"); + gOptions_bitmapFieldID = GetFieldIDOrDie(env, options_class, "inBitmap", + "Landroid/graphics/Bitmap;"); + gOptions_justBoundsFieldID = GetFieldIDOrDie(env, options_class, "inJustDecodeBounds", "Z"); + gOptions_sampleSizeFieldID = GetFieldIDOrDie(env, options_class, "inSampleSize", "I"); + gOptions_configFieldID = GetFieldIDOrDie(env, options_class, "inPreferredConfig", + "Landroid/graphics/Bitmap$Config;"); + gOptions_colorSpaceFieldID = GetFieldIDOrDie(env, options_class, "inPreferredColorSpace", + "Landroid/graphics/ColorSpace;"); + gOptions_premultipliedFieldID = GetFieldIDOrDie(env, options_class, "inPremultiplied", "Z"); + gOptions_mutableFieldID = GetFieldIDOrDie(env, options_class, "inMutable", "Z"); + gOptions_ditherFieldID = GetFieldIDOrDie(env, options_class, "inDither", "Z"); + gOptions_preferQualityOverSpeedFieldID = GetFieldIDOrDie(env, options_class, + "inPreferQualityOverSpeed", "Z"); + gOptions_scaledFieldID = GetFieldIDOrDie(env, options_class, "inScaled", "Z"); + gOptions_densityFieldID = GetFieldIDOrDie(env, options_class, "inDensity", "I"); + gOptions_screenDensityFieldID = GetFieldIDOrDie(env, options_class, "inScreenDensity", "I"); + gOptions_targetDensityFieldID = GetFieldIDOrDie(env, options_class, "inTargetDensity", "I"); + gOptions_widthFieldID = GetFieldIDOrDie(env, options_class, "outWidth", "I"); + gOptions_heightFieldID = GetFieldIDOrDie(env, options_class, "outHeight", "I"); + gOptions_mimeFieldID = GetFieldIDOrDie(env, options_class, "outMimeType", "Ljava/lang/String;"); + gOptions_outConfigFieldID = GetFieldIDOrDie(env, options_class, "outConfig", + "Landroid/graphics/Bitmap$Config;"); + gOptions_outColorSpaceFieldID = GetFieldIDOrDie(env, options_class, "outColorSpace", + "Landroid/graphics/ColorSpace;"); + gOptions_mCancelID = GetFieldIDOrDie(env, options_class, "mCancel", "Z"); + + jclass bitmap_class = FindClassOrDie(env, "android/graphics/Bitmap"); + gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets", + "Landroid/graphics/NinePatch$InsetStruct;"); + + gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, + "android/graphics/Bitmap$Config")); + gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class, + "nativeToConfig", "(I)Landroid/graphics/Bitmap$Config;"); + + return android::RegisterMethodsOrDie(env, "android/graphics/BitmapFactory", + gMethods, NELEM(gMethods)); +} |