BitmapFactory.java
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
}
Bitmap bm = null;
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
return bm;
}
jni
\frameworks\base\core\jni\android\graphics\BitmapFactory.cpp
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage,
jobject padding, jobject options) {
jobject bitmap = NULL;
std::unique_ptr stream(CreateJavaInputStreamAdaptor(env, is, storage));
if (stream.get()) {
std::unique_ptr bufferedStream(
SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded()));
SkASSERT(bufferedStream.get() != NULL);
bitmap = doDecode(env, bufferedStream.release(), padding, options);
}
return bitmap;
}
static jobject doDecode(JNIEnv* env, SkStreamRewindable* stream, jobject padding, jobject options) {
// This function takes ownership of the input stream. Since the SkAndroidCodec
// will take ownership of the stream, we don't necessarily need to take ownership
// here. This is a precaution - if we were to return before creating the codec,
// we need to make sure that we delete the stream.
std::unique_ptr streamDeleter(stream);
// Set default values for the options parameters.
int sampleSize = 1;
bool onlyDecodeSize = false;
SkColorType prefColorType = kN32_SkColorType;
bool isMutable = false;
float scale = 1.0f;
bool requireUnpremultiplied = false;
jobject javaBitmap = NULL;
// 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);
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
prefColorType = GraphicsJNI::getNativeBitmapColorType(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;
}
}
}
//#include "SkAndroidCodec.h" 解码器
//\external\skia\src\codec\SkAndroidCodec.cpp
// Create the codec.
NinePatchPeeker peeker;
std::unique_ptr codec(SkAndroidCodec::NewFromStream(streamDeleter.release(),
&peeker));
if (!codec.get()) {
return nullObjectReturn("SkAndroidCodec::NewFromStream 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 options and return if the client only wants the size.
if (options != NULL) {
jstring mimeType = encodedFormatToString(env, codec->getEncodedFormat());
if (env->ExceptionCheck()) {
return nullObjectReturn("OOM in encodedFormatToString()");
}
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
env->SetObjectField(options, gOptions_mimeFieldID, mimeType);
if (onlyDecodeSize) {
return nullptr;
}
}
// Scale is necessary due to density differences.
if (scale != 1.0f) {
willScale = true;
scaledWidth = static_cast(scaledWidth * scale + 0.5f);
scaledHeight = static_cast(scaledHeight * scale + 0.5f);
}
android::Bitmap* reuseBitmap = nullptr;
unsigned int existingBufferSize = 0;
if (javaBitmap != NULL) {
reuseBitmap = GraphicsJNI::getBitmap(env, javaBitmap);
if (reuseBitmap->peekAtPixelRef()->isImmutable()) {
ALOGW("Unable to reuse an immutable bitmap as an image decoder target.");
javaBitmap = NULL;
reuseBitmap = nullptr;
} else {
existingBufferSize = GraphicsJNI::getBitmapAllocationByteCount(env, javaBitmap);
}
}
JavaPixelAllocator javaAllocator(env);
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) {
// This will allocate pixels using a HeapAllocator, since there will be an extra
// scaling step that copies these pixels into Java memory.
decodeAllocator = &heapAllocator;
} else {
decodeAllocator = &javaAllocator;
}
// Set the decode colorType. This is necessary because we can't always support
// the requested colorType.
SkColorType decodeColorType = codec->computeOutputColorType(prefColorType);
// Construct a color table for the decode if necessary
SkAutoTUnref colorTable(nullptr);
SkPMColor* colorPtr = nullptr;
int* colorCount = nullptr;
int maxColors = 256;
SkPMColor colors[256];
if (kIndex_8_SkColorType == decodeColorType) {
colorTable.reset(new SkColorTable(colors, maxColors));
// SkColorTable expects us to initialize all of the colors before creating an
// SkColorTable. However, we are using SkBitmap with an Allocator to allocate
// memory for the decode, so we need to create the SkColorTable before decoding.
// It is safe for SkAndroidCodec to modify the colors because this SkBitmap is
// not being used elsewhere.
colorPtr = const_cast(colorTable->readColors());
colorCount = &maxColors;
}
// Set the alpha type for the decode.
SkAlphaType alphaType = codec->computeOutputAlphaType(requireUnpremultiplied);
const SkImageInfo decodeInfo = SkImageInfo::Make(size.width(), size.height(), decodeColorType,
alphaType);
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 = SkImageInfo::MakeA8(size.width(), size.height());
}
SkBitmap decodingBitmap;
if (!decodingBitmap.setInfo(bitmapInfo) ||
!decodingBitmap.tryAllocPixels(decodeAllocator, colorTable)) {
// 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 == &javaAllocator) ?
SkCodec::kYes_ZeroInitialized : SkCodec::kNo_ZeroInitialized;
codecOptions.fColorPtr = colorPtr;
codecOptions.fColorCount = colorCount;
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.");
}
jbyteArray ninePatchChunk = NULL;
if (peeker.mPatch != NULL) {
if (willScale) {
scaleNinePatchChunk(peeker.mPatch, scale, 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 = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
peeker.mOpticalInsets[0], peeker.mOpticalInsets[1], peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
peeker.mOutlineInsets[0], peeker.mOutlineInsets[1], peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
if (ninePatchInsets == NULL) {
return nullObjectReturn("nine patch insets == null");
}
if (javaBitmap != NULL) {
env->SetObjectField(javaBitmap, gBitmap_ninePatchInsetsFieldID, ninePatchInsets);
}
}
SkBitmap outputBitmap;
if (willScale) {
// 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 sx = scaledWidth / float(decodingBitmap.width());
const float sy = scaledHeight / float(decodingBitmap.height());
// Set the allocator for the outputBitmap.
SkBitmap::Allocator* outputAllocator;
if (javaBitmap != nullptr) {
outputAllocator = &recyclingAllocator;
} else {
outputAllocator = &javaAllocator;
}
SkColorType scaledColorType = colorTypeForScaledOutput(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(SkImageInfo::Make(scaledWidth, scaledHeight,
scaledColorType, decodingBitmap.alphaType()));
if (!outputBitmap.tryAllocPixels(outputAllocator, NULL)) {
// 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 unininitialized pixels in
// outputBitmap. Otherwise we would blend by default, which is not
// what we want.
paint.setXfermodeMode(SkXfermode::kSrc_Mode);
paint.setFilterQuality(kLow_SkFilterQuality);
SkCanvas canvas(outputBitmap);
canvas.scale(sx, sy);
canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
} else {
outputBitmap.swap(decodingBitmap);
}
if (padding) {
if (peeker.mPatch != NULL) {
GraphicsJNI::set_jrect(env, padding,
peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
} else {
GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
}
}
// 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) {
GraphicsJNI::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 |= GraphicsJNI::kBitmapCreateFlag_Mutable;
if (isPremultiplied) bitmapCreateFlags |= GraphicsJNI::kBitmapCreateFlag_Premultiplied;
// now create the java bitmap
return GraphicsJNI::createBitmap(env, javaAllocator.getStorageObjAndReset(),
bitmapCreateFlags, ninePatchChunk, ninePatchInsets, -1);
}
jobject GraphicsJNI::createBitmap(JNIEnv* env, android::Bitmap* bitmap,
int bitmapCreateFlags, jbyteArray ninePatchChunk, jobject ninePatchInsets,
int density) {
bool isMutable = bitmapCreateFlags & kBitmapCreateFlag_Mutable;
bool isPremultiplied = bitmapCreateFlags & kBitmapCreateFlag_Premultiplied;
// The caller needs to have already set the alpha type properly, so the
// native SkBitmap stays in sync with the Java Bitmap.
assert_premultiplied(bitmap->info(), isPremultiplied);
jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID,
reinterpret_cast(bitmap), bitmap->javaByteArray(),
bitmap->width(), bitmap->height(), density, isMutable, isPremultiplied,
ninePatchChunk, ninePatchInsets);
hasException(env); // For the side effect of logging.
return obj;
}
android\graphics\Bitmap.java
/**
* Private constructor that must received an already allocated native bitmap
* int (pointer).
*/
// called from JNI
Bitmap(long nativeBitmap, byte[] buffer, int width, int height, int density,
boolean isMutable, boolean requestPremultiplied,
byte[] ninePatchChunk, NinePatch.InsetStruct ninePatchInsets) {
if (nativeBitmap == 0) {
throw new RuntimeException("internal error: native bitmap is 0");
}
mWidth = width;
mHeight = height;
mIsMutable = isMutable;
mRequestPremultiplied = requestPremultiplied;
mBuffer = buffer;
mNinePatchChunk = ninePatchChunk;
mNinePatchInsets = ninePatchInsets;
if (density >= 0) {
mDensity = density;
}
mNativePtr = nativeBitmap;
long nativeSize = NATIVE_ALLOCATION_SIZE;
if (buffer == null) {
nativeSize += getByteCount();
}
NativeAllocationRegistry registry = new NativeAllocationRegistry(
Bitmap.class.getClassLoader(), nativeGetNativeFinalizer(), nativeSize);
registry.registerNativeAllocation(this, nativeBitmap);
}
参考文章
- Java中JNI的使用详解第三篇:JNIEnv类型中方法的使用
- Android 之 Bitmap
- AndroidXRef