Reflects refactoring of JNI helper code that depends on private methods within libnativehelper. Bug: 151443957 Test: Treehugger Change-Id: I7af128f42ae89a77a8e3fb113ea533331153c535 Merged-In: I7af128f42ae89a77a8e3fb113ea533331153c535 Exempt-From-Owner-Approval: cherry pick (cherry picked from commit 329c612e3d5ae440bf13e1f5fc9a4012263524e9)
668 lines
27 KiB
C++
668 lines
27 KiB
C++
#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));
|
|
}
|