|
|
|
|
@@ -2,11 +2,8 @@
|
|
|
|
|
|
|
|
|
|
#include "BitmapFactory.h"
|
|
|
|
|
#include "NinePatchPeeker.h"
|
|
|
|
|
#include "SkData.h"
|
|
|
|
|
#include "SkFrontBufferedStream.h"
|
|
|
|
|
#include "SkImageDecoder.h"
|
|
|
|
|
#include "SkImageRef_ashmem.h"
|
|
|
|
|
#include "SkImageRef_GlobalPool.h"
|
|
|
|
|
#include "SkMath.h"
|
|
|
|
|
#include "SkPixelRef.h"
|
|
|
|
|
#include "SkStream.h"
|
|
|
|
|
@@ -32,8 +29,6 @@ jfieldID gOptions_configFieldID;
|
|
|
|
|
jfieldID gOptions_premultipliedFieldID;
|
|
|
|
|
jfieldID gOptions_mutableFieldID;
|
|
|
|
|
jfieldID gOptions_ditherFieldID;
|
|
|
|
|
jfieldID gOptions_purgeableFieldID;
|
|
|
|
|
jfieldID gOptions_shareableFieldID;
|
|
|
|
|
jfieldID gOptions_preferQualityOverSpeedFieldID;
|
|
|
|
|
jfieldID gOptions_scaledFieldID;
|
|
|
|
|
jfieldID gOptions_densityFieldID;
|
|
|
|
|
@@ -90,14 +85,6 @@ jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) {
|
|
|
|
|
return jstr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool optionsPurgeable(JNIEnv* env, jobject options) {
|
|
|
|
|
return options != NULL && env->GetBooleanField(options, gOptions_purgeableFieldID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool optionsShareable(JNIEnv* env, jobject options) {
|
|
|
|
|
return options != NULL && env->GetBooleanField(options, gOptions_shareableFieldID);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool optionsJustBounds(JNIEnv* env, jobject options) {
|
|
|
|
|
return options != NULL && env->GetBooleanField(options, gOptions_justBoundsFieldID);
|
|
|
|
|
}
|
|
|
|
|
@@ -125,27 +112,6 @@ static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStreamRewindable* stream,
|
|
|
|
|
int sampleSize, bool ditherImage) {
|
|
|
|
|
|
|
|
|
|
if (kUnknown_SkColorType == bitmap->colorType()) {
|
|
|
|
|
ALOGW("bitmap has unknown configuration so no memory has been allocated");
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SkImageRef* pr;
|
|
|
|
|
// only use ashmem for large images, since mmaps come at a price
|
|
|
|
|
if (bitmap->getSize() >= 32 * 1024) {
|
|
|
|
|
pr = new SkImageRef_ashmem(bitmap->info(), stream, sampleSize);
|
|
|
|
|
} else {
|
|
|
|
|
pr = new SkImageRef_GlobalPool(bitmap->info(), stream, sampleSize);
|
|
|
|
|
}
|
|
|
|
|
pr->setDitherImage(ditherImage);
|
|
|
|
|
bitmap->setPixelRef(pr)->unref();
|
|
|
|
|
pr->isOpaque(bitmap);
|
|
|
|
|
return pr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static SkColorType colorTypeForScaledOutput(SkColorType colorType) {
|
|
|
|
|
switch (colorType) {
|
|
|
|
|
case kUnknown_SkColorType:
|
|
|
|
|
@@ -229,21 +195,17 @@ private:
|
|
|
|
|
const unsigned int mSize;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// since we "may" create a purgeable imageref, we require the stream be ref'able
|
|
|
|
|
// i.e. dynamically allocated, since its lifetime may exceed the current stack
|
|
|
|
|
// frame.
|
|
|
|
|
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding,
|
|
|
|
|
jobject options, bool allowPurgeable, bool forcePurgeable = false) {
|
|
|
|
|
jobject options) {
|
|
|
|
|
|
|
|
|
|
int sampleSize = 1;
|
|
|
|
|
|
|
|
|
|
SkImageDecoder::Mode mode = SkImageDecoder::kDecodePixels_Mode;
|
|
|
|
|
SkImageDecoder::Mode decodeMode = SkImageDecoder::kDecodePixels_Mode;
|
|
|
|
|
SkBitmap::Config prefConfig = SkBitmap::kARGB_8888_Config;
|
|
|
|
|
|
|
|
|
|
bool doDither = true;
|
|
|
|
|
bool isMutable = false;
|
|
|
|
|
float scale = 1.0f;
|
|
|
|
|
bool isPurgeable = forcePurgeable || (allowPurgeable && optionsPurgeable(env, options));
|
|
|
|
|
bool preferQualityOverSpeed = false;
|
|
|
|
|
bool requireUnpremultiplied = false;
|
|
|
|
|
|
|
|
|
|
@@ -252,7 +214,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
|
|
|
|
|
if (options != NULL) {
|
|
|
|
|
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
|
|
|
|
|
if (optionsJustBounds(env, options)) {
|
|
|
|
|
mode = SkImageDecoder::kDecodeBounds_Mode;
|
|
|
|
|
decodeMode = SkImageDecoder::kDecodeBounds_Mode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// initialize these, in case we fail later on
|
|
|
|
|
@@ -280,7 +242,6 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool willScale = scale != 1.0f;
|
|
|
|
|
isPurgeable &= !willScale;
|
|
|
|
|
|
|
|
|
|
SkImageDecoder* decoder = SkImageDecoder::Factory(stream);
|
|
|
|
|
if (decoder == NULL) {
|
|
|
|
|
@@ -311,8 +272,6 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
|
|
|
|
|
NinePatchPeeker peeker(decoder);
|
|
|
|
|
decoder->setPeeker(&peeker);
|
|
|
|
|
|
|
|
|
|
SkImageDecoder::Mode decodeMode = isPurgeable ? SkImageDecoder::kDecodeBounds_Mode : mode;
|
|
|
|
|
|
|
|
|
|
JavaPixelAllocator javaAllocator(env);
|
|
|
|
|
RecyclingPixelAllocator recyclingAllocator(outputBitmap->pixelRef(), existingBufferSize);
|
|
|
|
|
ScaleCheckingAllocator scaleCheckingAllocator(scale, existingBufferSize);
|
|
|
|
|
@@ -353,7 +312,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
|
|
|
|
|
int scaledWidth = decodingBitmap.width();
|
|
|
|
|
int scaledHeight = decodingBitmap.height();
|
|
|
|
|
|
|
|
|
|
if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) {
|
|
|
|
|
if (willScale && decodeMode != SkImageDecoder::kDecodeBounds_Mode) {
|
|
|
|
|
scaledWidth = int(scaledWidth * scale + 0.5f);
|
|
|
|
|
scaledHeight = int(scaledHeight * scale + 0.5f);
|
|
|
|
|
}
|
|
|
|
|
@@ -367,7 +326,7 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we're in justBounds mode, return now (skip the java bitmap)
|
|
|
|
|
if (mode == SkImageDecoder::kDecodeBounds_Mode) {
|
|
|
|
|
if (decodeMode == SkImageDecoder::kDecodeBounds_Mode) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -459,21 +418,15 @@ static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SkPixelRef* pr;
|
|
|
|
|
if (isPurgeable) {
|
|
|
|
|
pr = installPixelRef(outputBitmap, stream, sampleSize, doDither);
|
|
|
|
|
} else {
|
|
|
|
|
// if we get here, we're in kDecodePixels_Mode and will therefore
|
|
|
|
|
// already have a pixelref installed.
|
|
|
|
|
pr = outputBitmap->pixelRef();
|
|
|
|
|
}
|
|
|
|
|
if (pr == NULL) {
|
|
|
|
|
// if we get here, we're in kDecodePixels_Mode and will therefore
|
|
|
|
|
// already have a pixelref installed.
|
|
|
|
|
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)
|
|
|
|
|
pr->setImmutable();
|
|
|
|
|
outputBitmap->setImmutable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// detach bitmap from its autodeleter, since we want to own it now
|
|
|
|
|
@@ -512,8 +465,7 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteA
|
|
|
|
|
SkAutoTUnref<SkStreamRewindable> bufferedStream(
|
|
|
|
|
SkFrontBufferedStream::Create(stream, BYTES_TO_BUFFER));
|
|
|
|
|
SkASSERT(bufferedStream.get() != NULL);
|
|
|
|
|
// for now we don't allow purgeable with java inputstreams
|
|
|
|
|
bitmap = doDecode(env, bufferedStream, padding, options, false, false);
|
|
|
|
|
bitmap = doDecode(env, bufferedStream, padding, options);
|
|
|
|
|
}
|
|
|
|
|
return bitmap;
|
|
|
|
|
}
|
|
|
|
|
@@ -542,76 +494,33 @@ static jobject nativeDecodeFileDescriptor(JNIEnv* env, jobject clazz, jobject fi
|
|
|
|
|
SkAutoTUnref<SkFILEStream> fileStream(new SkFILEStream(file,
|
|
|
|
|
SkFILEStream::kCallerRetains_Ownership));
|
|
|
|
|
|
|
|
|
|
SkAutoTUnref<SkStreamRewindable> stream;
|
|
|
|
|
// 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.
|
|
|
|
|
SkAutoTUnref<SkStreamRewindable> stream(SkFrontBufferedStream::Create(fileStream,
|
|
|
|
|
BYTES_TO_BUFFER));
|
|
|
|
|
|
|
|
|
|
// Retain the old behavior of allowing purgeable if both purgeable and
|
|
|
|
|
// shareable are set to true.
|
|
|
|
|
bool isPurgeable = optionsPurgeable(env, bitmapFactoryOptions)
|
|
|
|
|
&& optionsShareable(env, bitmapFactoryOptions);
|
|
|
|
|
if (isPurgeable) {
|
|
|
|
|
// Copy the stream, so the image can be decoded multiple times without
|
|
|
|
|
// continuing to modify the original file descriptor.
|
|
|
|
|
// Copy beginning from the current position.
|
|
|
|
|
const size_t fileSize = fileStream->getLength() - fileStream->getPosition();
|
|
|
|
|
void* buffer = sk_malloc_flags(fileSize, 0);
|
|
|
|
|
if (buffer == NULL) {
|
|
|
|
|
return nullObjectReturn("Could not make a copy for ashmem");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SkAutoTUnref<SkData> data(SkData::NewFromMalloc(buffer, fileSize));
|
|
|
|
|
|
|
|
|
|
if (fileStream->read(buffer, fileSize) != fileSize) {
|
|
|
|
|
return nullObjectReturn("Could not read the file.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream.reset(new SkMemoryStream(data));
|
|
|
|
|
} else {
|
|
|
|
|
// 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.
|
|
|
|
|
stream.reset(SkFrontBufferedStream::Create(fileStream, BYTES_TO_BUFFER));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return doDecode(env, stream, padding, bitmapFactoryOptions, isPurgeable);
|
|
|
|
|
return doDecode(env, stream, padding, bitmapFactoryOptions);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jlong native_asset,
|
|
|
|
|
jobject padding, jobject options) {
|
|
|
|
|
|
|
|
|
|
SkStreamRewindable* stream;
|
|
|
|
|
Asset* asset = reinterpret_cast<Asset*>(native_asset);
|
|
|
|
|
bool forcePurgeable = optionsPurgeable(env, options);
|
|
|
|
|
if (forcePurgeable) {
|
|
|
|
|
// if we could "ref/reopen" the asset, we may not need to copy it here
|
|
|
|
|
// and we could assume optionsShareable, since assets are always RO
|
|
|
|
|
stream = CopyAssetToStream(asset);
|
|
|
|
|
if (stream == NULL) {
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// since we know we'll be done with the asset when we return, we can
|
|
|
|
|
// just use a simple wrapper
|
|
|
|
|
stream = new AssetStreamAdaptor(asset,
|
|
|
|
|
AssetStreamAdaptor::kNo_OwnAsset,
|
|
|
|
|
AssetStreamAdaptor::kNo_HasMemoryBase);
|
|
|
|
|
}
|
|
|
|
|
SkAutoUnref aur(stream);
|
|
|
|
|
return doDecode(env, stream, padding, options, forcePurgeable, forcePurgeable);
|
|
|
|
|
// since we know we'll be done with the asset when we return, we can
|
|
|
|
|
// just use a simple wrapper
|
|
|
|
|
SkAutoTUnref<SkStreamRewindable> stream(new AssetStreamAdaptor(asset,
|
|
|
|
|
AssetStreamAdaptor::kNo_OwnAsset, AssetStreamAdaptor::kNo_HasMemoryBase));
|
|
|
|
|
return doDecode(env, stream, padding, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static jobject nativeDecodeByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
|
|
|
|
|
jint offset, jint length, jobject options) {
|
|
|
|
|
|
|
|
|
|
/* If optionsShareable() we could decide to just wrap the java array and
|
|
|
|
|
share it, but that means adding a globalref to the java array object
|
|
|
|
|
and managing its lifetime. For now we just always copy the array's data
|
|
|
|
|
if optionsPurgeable(), unless we're just decoding bounds.
|
|
|
|
|
*/
|
|
|
|
|
bool purgeable = optionsPurgeable(env, options) && !optionsJustBounds(env, options);
|
|
|
|
|
AutoJavaByteArray ar(env, byteArray);
|
|
|
|
|
SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, purgeable);
|
|
|
|
|
SkMemoryStream* stream = new SkMemoryStream(ar.ptr() + offset, length, false);
|
|
|
|
|
SkAutoUnref aur(stream);
|
|
|
|
|
return doDecode(env, stream, NULL, options, purgeable);
|
|
|
|
|
return doDecode(env, stream, NULL, options);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void nativeRequestCancel(JNIEnv*, jobject joptions) {
|
|
|
|
|
@@ -675,8 +584,6 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) {
|
|
|
|
|
gOptions_premultipliedFieldID = getFieldIDCheck(env, options_class, "inPremultiplied", "Z");
|
|
|
|
|
gOptions_mutableFieldID = getFieldIDCheck(env, options_class, "inMutable", "Z");
|
|
|
|
|
gOptions_ditherFieldID = getFieldIDCheck(env, options_class, "inDither", "Z");
|
|
|
|
|
gOptions_purgeableFieldID = getFieldIDCheck(env, options_class, "inPurgeable", "Z");
|
|
|
|
|
gOptions_shareableFieldID = getFieldIDCheck(env, options_class, "inInputShareable", "Z");
|
|
|
|
|
gOptions_preferQualityOverSpeedFieldID = getFieldIDCheck(env, options_class,
|
|
|
|
|
"inPreferQualityOverSpeed", "Z");
|
|
|
|
|
gOptions_scaledFieldID = getFieldIDCheck(env, options_class, "inScaled", "Z");
|
|
|
|
|
|