blob: 491066b059522a142024026cfbe3e87fc650882f [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "BitmapRegionDecoder.h"
#include <HardwareBitmapUploader.h>
#include <androidfw/Asset.h>
#include <sys/stat.h>
#include <utils/StatsUtils.h>
#include <memory>
#include "BitmapFactory.h"
#include "CreateJavaOutputStreamAdaptor.h"
#include "Gainmap.h"
#include "GraphicsJNI.h"
#include "SkBitmap.h"
#include "SkCodec.h"
#include "SkColorSpace.h"
#include "SkData.h"
#include "SkGainmapInfo.h"
#include "SkStream.h"
#include "SkStreamPriv.h"
#include "Utils.h"
using namespace android;
namespace android {
class BitmapRegionDecoderWrapper {
public:
static std::unique_ptr<BitmapRegionDecoderWrapper> Make(sk_sp<SkData> data) {
std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD =
skia::BitmapRegionDecoder::Make(std::move(data));
if (!mainImageBRD) {
return nullptr;
}
SkGainmapInfo gainmapInfo;
std::unique_ptr<SkAndroidCodec> gainmapCodec;
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD = nullptr;
if (!mainImageBRD->getGainmapBitmapRegionDecoder(&gainmapInfo, &gainmapBRD)) {
gainmapBRD = nullptr;
}
return std::unique_ptr<BitmapRegionDecoderWrapper>(new BitmapRegionDecoderWrapper(
std::move(mainImageBRD), std::move(gainmapBRD), gainmapInfo));
}
SkEncodedImageFormat getEncodedFormat() { return mMainImageBRD->getEncodedFormat(); }
SkColorType computeOutputColorType(SkColorType requestedColorType) {
return mMainImageBRD->computeOutputColorType(requestedColorType);
}
sk_sp<SkColorSpace> computeOutputColorSpace(SkColorType outputColorType,
sk_sp<SkColorSpace> prefColorSpace = nullptr) {
return mMainImageBRD->computeOutputColorSpace(outputColorType, prefColorSpace);
}
bool decodeRegion(SkBitmap* bitmap, skia::BRDAllocator* allocator, const SkIRect& desiredSubset,
int sampleSize, SkColorType colorType, bool requireUnpremul,
sk_sp<SkColorSpace> prefColorSpace) {
return mMainImageBRD->decodeRegion(bitmap, allocator, desiredSubset, sampleSize, colorType,
requireUnpremul, prefColorSpace);
}
// Decodes the gainmap region. If decoding succeeded, returns true and
// populate outGainmap with the decoded gainmap. Otherwise, returns false.
//
// Note that the desiredSubset is the logical region within the source
// gainmap that we want to decode. This is used for scaling into the final
// bitmap, since we do not want to include portions of the gainmap outside
// of this region. desiredSubset is also _not_ guaranteed to be
// pixel-aligned, so it's not possible to simply resize the resulting
// bitmap to accomplish this.
bool decodeGainmapRegion(sp<uirenderer::Gainmap>* outGainmap, SkISize bitmapDimensions,
const SkRect& desiredSubset, int sampleSize, bool requireUnpremul) {
SkColorType decodeColorType = mGainmapBRD->computeOutputColorType(kN32_SkColorType);
sk_sp<SkColorSpace> decodeColorSpace =
mGainmapBRD->computeOutputColorSpace(decodeColorType, nullptr);
SkBitmap bm;
// Because we must match the dimensions of the base bitmap, we always use a
// recycling allocator even though we are allocating a new bitmap. This is to ensure
// that if a recycled bitmap was used for the base image that we match the relative
// dimensions of that base image. The behavior of BRD here is:
// if inBitmap is specified -> output dimensions are always equal to the inBitmap's
// if no bitmap is reused -> output dimensions are the intersect of the desiredSubset &
// the image bounds
// The handling of the above conditionals are baked into the desiredSubset, so we
// simply need to ensure that the resulting bitmap is the exact same width/height as
// the specified desiredSubset regardless of the intersection to the image bounds.
// kPremul_SkAlphaType is used just as a placeholder as it doesn't change the underlying
// allocation type. RecyclingClippingPixelAllocator will populate this with the
// actual alpha type in either allocPixelRef() or copyIfNecessary()
sk_sp<Bitmap> nativeBitmap = Bitmap::allocateHeapBitmap(SkImageInfo::Make(
bitmapDimensions, decodeColorType, kPremul_SkAlphaType, decodeColorSpace));
if (!nativeBitmap) {
ALOGE("OOM allocating Bitmap for Gainmap");
return false;
}
sampleSize = std::max(sampleSize, 1);
// Map the desired subset to the space of the decoded gainmap. The
// subset is repositioned relative to the resulting bitmap, and then
// scaled to respect the sampleSize.
// This assumes that the subset will not be modified by the decoder, which is true
// for existing gainmap formats.
SkRect logicalSubset = desiredSubset.makeOffset(-std::floorf(desiredSubset.left()),
-std::floorf(desiredSubset.top()));
logicalSubset = scale(logicalSubset, 1.0f / sampleSize);
// Round out the subset so that we decode a slightly larger region, in
// case the subset has fractional components. When we round, we need to
// round the downsampled subset to avoid possibly rounding down by accident.
// Consider this concrete example if we round the desired subset directly:
//
// * We are decoding a 18x18 corner of an image
//
// * Gainmap is 1/4 resolution, which is logically a 4.5x4.5 gainmap
// that we would want
//
// * The app wants to downsample by a factor of 2x
//
// * The desired gainmap dimensions are computed to be 3x3 to fit the
// downsampled gainmap, since we need to fill a 2.25x2.25 region that's
// later upscaled to 3x3
//
// * But, if we round out the desired gainmap region _first_, then we
// request to decode a 5x5 region, downsampled by 2, which actually
// decodes a 2x2 region since skia rounds down internally. But then we transfer
// the result to a 3x3 bitmap using a clipping allocator, which leaves an inset.
// Not only did we get a smaller region than we expected (so, our desired subset is
// not valid), but because the API allows for decoding regions using a recycled
// bitmap, we can't really safely fill in the inset since then we might
// extend the gainmap beyond intended the image bounds. Oops.
//
// * If we instead round out as if we downsampled, then we downsample
// the desired region to 2.25x2.25, round out to 3x3, then upsample back
// into the source gainmap space to get 6x6. Then we decode a 6x6 region
// downsampled into a 3x3 region, and everything's now correct.
//
// Note that we don't always run into this problem, because
// decoders actually round *up* for subsampling when decoding a subset
// that matches the dimensions of the image. E.g., if the original image
// size in the above example was a 20x20 image, so that the gainmap was
// 5x5, then we still manage to downsample into a 3x3 bitmap even with
// the "wrong" math. but that's what we wanted!
//
// Note also that if we overshoot the gainmap bounds with the requested
// subset it isn't a problem either, since now the decoded bitmap is too
// large, rather than too small, so now we can use the desired subset to
// avoid sampling "invalid" colors.
SkRect scaledSubset = scale(desiredSubset, 1.0f / sampleSize);
SkIRect roundedSubset = scale(scaledSubset.roundOut(), static_cast<float>(sampleSize));
RecyclingClippingPixelAllocator allocator(nativeBitmap.get(), false, logicalSubset);
if (!mGainmapBRD->decodeRegion(&bm, &allocator, roundedSubset, sampleSize, decodeColorType,
requireUnpremul, decodeColorSpace)) {
ALOGE("Error decoding Gainmap region");
return false;
}
allocator.copyIfNecessary();
auto gainmap = sp<uirenderer::Gainmap>::make();
if (!gainmap) {
ALOGE("OOM allocating Gainmap");
return false;
}
gainmap->info = mGainmapInfo;
gainmap->bitmap = std::move(nativeBitmap);
*outGainmap = std::move(gainmap);
return true;
}
struct Projection {
SkRect srcRect;
SkISize destSize;
};
Projection calculateGainmapRegion(const SkIRect& mainImageRegion, SkISize dimensions) {
const float scaleX = ((float)mGainmapBRD->width()) / mMainImageBRD->width();
const float scaleY = ((float)mGainmapBRD->height()) / mMainImageBRD->height();
if (uirenderer::Properties::resampleGainmapRegions()) {
const auto srcRect = SkRect::MakeLTRB(
mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY);
// Request a slightly larger destination size so that the gainmap
// subset we want fits entirely in this size.
const auto destSize = SkISize::Make(std::ceil(dimensions.width() * scaleX),
std::ceil(dimensions.height() * scaleY));
return Projection{.srcRect = srcRect, .destSize = destSize};
} else {
const auto srcRect = SkRect::Make(SkIRect::MakeLTRB(
mainImageRegion.left() * scaleX, mainImageRegion.top() * scaleY,
mainImageRegion.right() * scaleX, mainImageRegion.bottom() * scaleY));
const auto destSize =
SkISize::Make(dimensions.width() * scaleX, dimensions.height() * scaleY);
return Projection{.srcRect = srcRect, .destSize = destSize};
}
}
bool hasGainmap() { return mGainmapBRD != nullptr; }
int width() const { return mMainImageBRD->width(); }
int height() const { return mMainImageBRD->height(); }
private:
BitmapRegionDecoderWrapper(std::unique_ptr<skia::BitmapRegionDecoder> mainImageBRD,
std::unique_ptr<skia::BitmapRegionDecoder> gainmapBRD,
SkGainmapInfo info)
: mMainImageBRD(std::move(mainImageBRD))
, mGainmapBRD(std::move(gainmapBRD))
, mGainmapInfo(info) {}
SkRect scale(SkRect rect, float scale) const {
rect.fLeft *= scale;
rect.fTop *= scale;
rect.fRight *= scale;
rect.fBottom *= scale;
return rect;
}
SkIRect scale(SkIRect rect, float scale) const {
rect.fLeft *= scale;
rect.fTop *= scale;
rect.fRight *= scale;
rect.fBottom *= scale;
return rect;
}
std::unique_ptr<skia::BitmapRegionDecoder> mMainImageBRD;
std::unique_ptr<skia::BitmapRegionDecoder> mGainmapBRD;
SkGainmapInfo mGainmapInfo;
};
} // namespace android
static jobject createBitmapRegionDecoder(JNIEnv* env, sk_sp<SkData> data) {
auto brd = android::BitmapRegionDecoderWrapper::Make(std::move(data));
if (!brd) {
doThrowIOE(env, "Image format not supported");
return nullObjectReturn("CreateBitmapRegionDecoder returned null");
}
return GraphicsJNI::createBitmapRegionDecoder(env, brd.release());
}
static jobject nativeNewInstanceFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray,
jint offset, jint length) {
AutoJavaByteArray ar(env, byteArray);
return createBitmapRegionDecoder(env, SkData::MakeWithCopy(ar.ptr() + offset, length));
}
static jobject nativeNewInstanceFromFileDescriptor(JNIEnv* env, jobject clazz,
jobject fileDescriptor) {
NPE_CHECK_RETURN_ZERO(env, fileDescriptor);
jint descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
struct stat fdStat;
if (fstat(descriptor, &fdStat) == -1) {
doThrowIOE(env, "broken file descriptor");
return nullObjectReturn("fstat return -1");
}
return createBitmapRegionDecoder(env, SkData::MakeFromFD(descriptor));
}
static jobject nativeNewInstanceFromStream(JNIEnv* env, jobject clazz, jobject is, // InputStream
jbyteArray storage) { // byte[]
jobject brd = nullptr;
sk_sp<SkData> data = CopyJavaInputStream(env, is, storage);
if (data) {
brd = createBitmapRegionDecoder(env, std::move(data));
}
return brd;
}
static jobject nativeNewInstanceFromAsset(JNIEnv* env, jobject clazz, jlong native_asset) {
Asset* asset = reinterpret_cast<Asset*>(native_asset);
sk_sp<SkData> data = CopyAssetToData(asset);
if (!data) {
return nullptr;
}
return createBitmapRegionDecoder(env, data);
}
/*
* nine patch not supported
* purgeable not supported
* reportSizeToVM not supported
*/
static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint inputX,
jint inputY, jint inputWidth, jint inputHeight, jobject options, jlong inBitmapHandle,
jlong colorSpaceHandle) {
// Set default options.
int sampleSize = 1;
SkColorType colorType = kN32_SkColorType;
bool requireUnpremul = false;
jobject javaBitmap = nullptr;
bool isHardware = false;
sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
// Update the default options with any options supplied by the client.
if (NULL != options) {
sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID);
jobject jconfig = env->GetObjectField(options, gOptions_configFieldID);
colorType = GraphicsJNI::getNativeBitmapColorType(env, jconfig);
isHardware = GraphicsJNI::isHardwareConfig(env, jconfig);
requireUnpremul = !env->GetBooleanField(options, gOptions_premultipliedFieldID);
javaBitmap = env->GetObjectField(options, gOptions_bitmapFieldID);
// The Java options of ditherMode and preferQualityOverSpeed are deprecated. We will
// ignore the values of these fields.
// Initialize these fields to indicate a failure. If the decode succeeds, we
// will update them 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);
}
// Recycle a bitmap if possible.
android::Bitmap* recycledBitmap = nullptr;
if (javaBitmap) {
recycledBitmap = &bitmap::toBitmap(inBitmapHandle);
if (recycledBitmap->isImmutable()) {
ALOGW("Warning: Reusing an immutable bitmap as an image decoder target.");
}
}
auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
SkColorType decodeColorType = brd->computeOutputColorType(colorType);
if (isHardware) {
if (decodeColorType == kRGBA_F16_SkColorType &&
!uirenderer::HardwareBitmapUploader::hasFP16Support()) {
decodeColorType = kN32_SkColorType;
}
if (decodeColorType == kRGBA_1010102_SkColorType &&
!uirenderer::HardwareBitmapUploader::has1010102Support()) {
decodeColorType = kN32_SkColorType;
}
}
// Set up the pixel allocator
skia::BRDAllocator* allocator = nullptr;
RecyclingClippingPixelAllocator recycleAlloc(recycledBitmap);
HeapAllocator heapAlloc;
if (javaBitmap) {
allocator = &recycleAlloc;
// We are required to match the color type of the recycled bitmap.
decodeColorType = recycledBitmap->info().colorType();
} else {
allocator = &heapAlloc;
}
sk_sp<SkColorSpace> decodeColorSpace = brd->computeOutputColorSpace(
decodeColorType, colorSpace);
// Decode the region.
const SkIRect subset = SkIRect::MakeXYWH(inputX, inputY, inputWidth, inputHeight);
SkBitmap bitmap;
if (!brd->decodeRegion(&bitmap, allocator, subset, sampleSize,
decodeColorType, requireUnpremul, decodeColorSpace)) {
return nullObjectReturn("Failed to decode region.");
}
// If the client provided options, indicate that the decode was successful.
if (NULL != options) {
env->SetIntField(options, gOptions_widthFieldID, bitmap.width());
env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
env->SetObjectField(options, gOptions_mimeFieldID,
getMimeTypeAsJavaString(env, brd->getEncodedFormat()));
if (env->ExceptionCheck()) {
return nullObjectReturn("OOM in encodedFormatToString()");
}
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 (javaBitmap) {
recycleAlloc.copyIfNecessary();
}
sp<uirenderer::Gainmap> gainmap;
bool hasGainmap = brd->hasGainmap();
if (hasGainmap) {
SkISize gainmapDims = SkISize::Make(bitmap.width(), bitmap.height());
if (javaBitmap) {
// If we are recycling we must match the inBitmap's relative dimensions
gainmapDims.fWidth = recycledBitmap->width();
gainmapDims.fHeight = recycledBitmap->height();
}
BitmapRegionDecoderWrapper::Projection gainmapProjection =
brd->calculateGainmapRegion(subset, gainmapDims);
if (!brd->decodeGainmapRegion(&gainmap, gainmapProjection.destSize,
gainmapProjection.srcRect, sampleSize, requireUnpremul)) {
// If there is an error decoding Gainmap - we don't fail. We just don't provide Gainmap
hasGainmap = false;
}
}
// If we may have reused a bitmap, we need to indicate that the pixels have changed.
if (javaBitmap) {
if (hasGainmap) {
recycledBitmap->setGainmap(std::move(gainmap));
}
bitmap::reinitBitmap(env, javaBitmap, recycledBitmap->info(), !requireUnpremul);
uirenderer::logBitmapDecode(*recycledBitmap);
return javaBitmap;
}
int bitmapCreateFlags = 0;
if (!requireUnpremul) {
bitmapCreateFlags |= android::bitmap::kBitmapCreateFlag_Premultiplied;
}
if (isHardware) {
sk_sp<Bitmap> hardwareBitmap = Bitmap::allocateHardwareBitmap(bitmap);
if (hasGainmap) {
auto gm = uirenderer::Gainmap::allocateHardwareGainmap(gainmap);
if (gm) {
hardwareBitmap->setGainmap(std::move(gm));
}
}
uirenderer::logBitmapDecode(*hardwareBitmap);
return bitmap::createBitmap(env, hardwareBitmap.release(), bitmapCreateFlags);
}
Bitmap* heapBitmap = heapAlloc.getStorageObjAndReset();
if (hasGainmap && heapBitmap != nullptr) {
heapBitmap->setGainmap(std::move(gainmap));
}
uirenderer::logBitmapDecode(*heapBitmap);
return android::bitmap::createBitmap(env, heapBitmap, bitmapCreateFlags);
}
static jint nativeGetHeight(JNIEnv* env, jobject, jlong brdHandle) {
auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
return static_cast<jint>(brd->height());
}
static jint nativeGetWidth(JNIEnv* env, jobject, jlong brdHandle) {
auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
return static_cast<jint>(brd->width());
}
static void nativeClean(JNIEnv* env, jobject, jlong brdHandle) {
auto* brd = reinterpret_cast<BitmapRegionDecoderWrapper*>(brdHandle);
delete brd;
}
///////////////////////////////////////////////////////////////////////////////
static const JNINativeMethod gBitmapRegionDecoderMethods[] = {
{ "nativeDecodeRegion",
"(JIIIILandroid/graphics/BitmapFactory$Options;JJ)Landroid/graphics/Bitmap;",
(void*)nativeDecodeRegion},
{ "nativeGetHeight", "(J)I", (void*)nativeGetHeight},
{ "nativeGetWidth", "(J)I", (void*)nativeGetWidth},
{ "nativeClean", "(J)V", (void*)nativeClean},
{ "nativeNewInstance",
"([BII)Landroid/graphics/BitmapRegionDecoder;",
(void*)nativeNewInstanceFromByteArray
},
{ "nativeNewInstance",
"(Ljava/io/InputStream;[B)Landroid/graphics/BitmapRegionDecoder;",
(void*)nativeNewInstanceFromStream
},
{ "nativeNewInstance",
"(Ljava/io/FileDescriptor;)Landroid/graphics/BitmapRegionDecoder;",
(void*)nativeNewInstanceFromFileDescriptor
},
{ "nativeNewInstance",
"(J)Landroid/graphics/BitmapRegionDecoder;",
(void*)nativeNewInstanceFromAsset
},
};
int register_android_graphics_BitmapRegionDecoder(JNIEnv* env)
{
return android::RegisterMethodsOrDie(env, "android/graphics/BitmapRegionDecoder",
gBitmapRegionDecoderMethods, NELEM(gBitmapRegionDecoderMethods));
}