|  | #undef LOG_TAG | 
|  | #define LOG_TAG "BitmapFactory" | 
|  |  | 
|  | #include "BitmapFactory.h" | 
|  | #include "CreateJavaOutputStreamAdaptor.h" | 
|  | #include "FrontBufferedStream.h" | 
|  | #include "GraphicsJNI.h" | 
|  | #include "MimeType.h" | 
|  | #include "NinePatchPeeker.h" | 
|  | #include "SkAndroidCodec.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(skia::FrontBufferedStream::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(skia::FrontBufferedStream::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)); | 
|  | } |