Separate ImageDecoder JNI from actual work
Bug: 135133301
Test: CtsGraphicsTestCases ImageDecoderTest
Make the native ImageDecoder a class, with methods that do the bulk of
the work that was previously done in JNI. This will allow the
forthcoming NDK API (AImageDecoder) to share the same code for decoding.
Move enums into implementation file, as no other code needs them.
Make computeAllocationSize a public static method on hwui/Bitmap, for
use by ImageDecoder.
Change-Id: I4e4dae338a951761614aed42ca2cc157e3d526dd
diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp
index 1290026..6c2a5a3 100644
--- a/core/jni/android/graphics/AnimatedImageDrawable.cpp
+++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp
@@ -25,6 +25,7 @@
#include <SkPicture.h>
#include <SkPictureRecorder.h>
#include <hwui/AnimatedImageDrawable.h>
+#include <hwui/ImageDecoder.h>
#include <hwui/Canvas.h>
#include <utils/Looper.h>
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 4d907f6..627f8f5 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -20,10 +20,12 @@
#include "CreateJavaOutputStreamAdaptor.h"
#include "GraphicsJNI.h"
#include "ImageDecoder.h"
+#include "NinePatchPeeker.h"
#include "Utils.h"
#include "core_jni_helpers.h"
#include <hwui/Bitmap.h>
+#include <hwui/ImageDecoder.h>
#include <HardwareBitmapUploader.h>
#include <SkAndroidCodec.h>
@@ -49,6 +51,28 @@
static jmethodID gCanvas_constructorMethodID;
static jmethodID gCanvas_releaseMethodID;
+// These need to stay in sync with ImageDecoder.java's Allocator constants.
+enum Allocator {
+ kDefault_Allocator = 0,
+ kSoftware_Allocator = 1,
+ kSharedMemory_Allocator = 2,
+ kHardware_Allocator = 3,
+};
+
+// These need to stay in sync with ImageDecoder.java's Error constants.
+enum Error {
+ kSourceException = 1,
+ kSourceIncomplete = 2,
+ kSourceMalformedData = 3,
+};
+
+// These need to stay in sync with PixelFormat.java's Format constants.
+enum PixelFormat {
+ kUnknown = 0,
+ kTranslucent = -3,
+ kOpaque = -1,
+};
+
// Clear and return any pending exception for handling other than throwing directly.
static jthrowable get_and_clear_exception(JNIEnv* env) {
jthrowable jexception = env->ExceptionOccurred();
@@ -59,7 +83,7 @@
}
// Throw a new ImageDecoder.DecodeException. Returns null for convenience.
-static jobject throw_exception(JNIEnv* env, ImageDecoder::Error error, const char* msg,
+static jobject throw_exception(JNIEnv* env, Error error, const char* msg,
jthrowable cause, jobject source) {
jstring jstr = nullptr;
if (msg) {
@@ -81,27 +105,27 @@
static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream,
jobject source, jboolean preferAnimation) {
if (!stream.get()) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream",
+ return throw_exception(env, kSourceMalformedData, "Failed to create a stream",
nullptr, source);
}
- std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
+ sk_sp<NinePatchPeeker> peeker(new NinePatchPeeker);
SkCodec::Result result;
auto codec = SkCodec::MakeFromStream(
- std::move(stream), &result, decoder->mPeeker.get(),
+ std::move(stream), &result, peeker.get(),
preferAnimation ? SkCodec::SelectionPolicy::kPreferAnimation
: SkCodec::SelectionPolicy::kPreferStillImage);
if (jthrowable jexception = get_and_clear_exception(env)) {
- return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source);
+ return throw_exception(env, kSourceException, "", jexception, source);
}
if (!codec) {
switch (result) {
case SkCodec::kIncompleteInput:
- return throw_exception(env, ImageDecoder::kSourceIncomplete, "", nullptr, source);
+ return throw_exception(env, kSourceIncomplete, "", nullptr, source);
default:
SkString msg;
msg.printf("Failed to create image decoder with message '%s'",
SkCodec::ResultToString(result));
- return throw_exception(env, ImageDecoder::kSourceMalformedData, msg.c_str(),
+ return throw_exception(env, kSourceMalformedData, msg.c_str(),
nullptr, source);
}
@@ -109,21 +133,22 @@
const bool animated = codec->getFrameCount() > 1;
if (jthrowable jexception = get_and_clear_exception(env)) {
- return throw_exception(env, ImageDecoder::kSourceException, "", jexception, source);
+ return throw_exception(env, kSourceException, "", jexception, source);
}
- decoder->mCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
SkAndroidCodec::ExifOrientationBehavior::kRespect);
- if (!decoder->mCodec.get()) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "", nullptr, source);
+ if (!androidCodec.get()) {
+ return throw_exception(env, kSourceMalformedData, "", nullptr, source);
}
- const auto& info = decoder->mCodec->getInfo();
+ const auto& info = androidCodec->getInfo();
const int width = info.width();
const int height = info.height();
- const bool isNinePatch = decoder->mPeeker->mPatch != nullptr;
+ const bool isNinePatch = peeker->mPatch != nullptr;
+ ImageDecoder* decoder = new ImageDecoder(std::move(androidCodec), std::move(peeker));
return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
- reinterpret_cast<jlong>(decoder.release()), width, height,
+ reinterpret_cast<jlong>(decoder), width, height,
animated, isNinePatch);
}
@@ -133,7 +158,7 @@
struct stat fdStat;
if (fstat(descriptor, &fdStat) == -1) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData,
+ return throw_exception(env, kSourceMalformedData,
"broken file descriptor; fstat returned -1", nullptr, source);
}
@@ -141,7 +166,7 @@
FILE* file = fdopen(dupDescriptor, "r");
if (file == NULL) {
close(dupDescriptor);
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Could not open file",
+ return throw_exception(env, kSourceMalformedData, "Could not open file",
nullptr, source);
}
@@ -154,7 +179,7 @@
std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false));
if (!stream.get()) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to create a stream",
+ return throw_exception(env, kSourceMalformedData, "Failed to create a stream",
nullptr, source);
}
@@ -177,7 +202,7 @@
std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
initialPosition, limit);
if (!stream) {
- return throw_exception(env, ImageDecoder::kSourceMalformedData, "Failed to read ByteBuffer",
+ return throw_exception(env, kSourceMalformedData, "Failed to read ByteBuffer",
nullptr, source);
}
return native_create(env, std::move(stream), source, preferAnimation);
@@ -195,7 +220,7 @@
reinterpret_cast<jlong>(canvas.get()));
if (!jcanvas) {
doThrowOOME(env, "Failed to create Java Canvas for PostProcess!");
- return ImageDecoder::kUnknown;
+ return kUnknown;
}
// jcanvas now owns canvas.
@@ -206,43 +231,23 @@
static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jobject jdecoder, jboolean jpostProcess,
- jint desiredWidth, jint desiredHeight, jobject jsubset,
+ jint targetWidth, jint targetHeight, jobject jsubset,
jboolean requireMutable, jint allocator,
jboolean requireUnpremul, jboolean preferRamOverQuality,
jboolean asAlphaMask, jlong colorSpaceHandle,
jboolean extended) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- SkAndroidCodec* codec = decoder->mCodec.get();
- const SkISize desiredSize = SkISize::Make(desiredWidth, desiredHeight);
- SkISize decodeSize = desiredSize;
- const int sampleSize = codec->computeSampleSize(&decodeSize);
- const bool scale = desiredSize != decodeSize;
- SkImageInfo decodeInfo = codec->getInfo().makeWH(decodeSize.width(), decodeSize.height());
- if (scale && requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) {
+ if (!decoder->setTargetSize(targetWidth, targetHeight)) {
+ doThrowISE(env, "Could not scale to target size!");
+ return nullptr;
+ }
+ if (requireUnpremul && !decoder->setOutAlphaType(kUnpremul_SkAlphaType)) {
doThrowISE(env, "Cannot scale unpremultiplied pixels!");
return nullptr;
}
- switch (decodeInfo.alphaType()) {
- case kUnpremul_SkAlphaType:
- if (!requireUnpremul) {
- decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
- }
- break;
- case kPremul_SkAlphaType:
- if (requireUnpremul) {
- decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType);
- }
- break;
- case kOpaque_SkAlphaType:
- break;
- case kUnknown_SkAlphaType:
- doThrowIOE(env, "Unknown alpha type");
- return nullptr;
- }
-
SkColorType colorType = kN32_SkColorType;
- if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) {
+ if (asAlphaMask && decoder->gray()) {
// We have to trick Skia to decode this to a single channel.
colorType = kGray_8_SkColorType;
} else if (preferRamOverQuality) {
@@ -250,12 +255,12 @@
// result incorrect. If we call the postProcess before now and record
// to a picture, we can know whether alpha was added, and if not, we
// can still use 565.
- if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) {
+ if (decoder->opaque() && !jpostProcess) {
// If the final result will be hardware, decoding to 565 and then
// uploading to the gpu as 8888 will not save memory. This still
// may save us from using F16, but do not go down to 565.
- if (allocator != ImageDecoder::kHardware_Allocator &&
- (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) {
+ if (allocator != kHardware_Allocator &&
+ (allocator != kDefault_Allocator || requireMutable)) {
colorType = kRGB_565_SkColorType;
}
}
@@ -263,12 +268,12 @@
} else if (extended) {
colorType = kRGBA_F16_SkColorType;
} else {
- colorType = codec->computeOutputColorType(colorType);
+ colorType = decoder->mCodec->computeOutputColorType(colorType);
}
const bool isHardware = !requireMutable
- && (allocator == ImageDecoder::kDefault_Allocator ||
- allocator == ImageDecoder::kHardware_Allocator)
+ && (allocator == kDefault_Allocator ||
+ allocator == kHardware_Allocator)
&& colorType != kGray_8_SkColorType;
if (colorType == kRGBA_F16_SkColorType && isHardware &&
@@ -276,12 +281,28 @@
colorType = kN32_SkColorType;
}
- sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
- colorSpace = codec->computeOutputColorSpace(colorType, colorSpace);
- decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
+ if (!decoder->setOutColorType(colorType)) {
+ doThrowISE(env, "Failed to set out color type!");
+ return nullptr;
+ }
+
+ {
+ sk_sp<SkColorSpace> colorSpace = GraphicsJNI::getNativeColorSpace(colorSpaceHandle);
+ colorSpace = decoder->mCodec->computeOutputColorSpace(colorType, colorSpace);
+ decoder->setOutColorSpace(std::move(colorSpace));
+ }
+
+ if (jsubset) {
+ SkIRect subset;
+ GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+ if (!decoder->setCropRect(&subset)) {
+ doThrowISE(env, "Invalid crop rect!");
+ return nullptr;
+ }
+ }
SkBitmap bm;
- auto bitmapInfo = decodeInfo;
+ SkImageInfo bitmapInfo = decoder->getOutputInfo();
if (asAlphaMask && colorType == kGray_8_SkColorType) {
bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
}
@@ -291,10 +312,7 @@
}
sk_sp<Bitmap> nativeBitmap;
- // If we are going to scale or subset, we will create a new bitmap later on,
- // so use the heap for the temporary.
- // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
- if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) {
+ if (allocator == kSharedMemory_Allocator) {
nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
} else {
nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
@@ -302,16 +320,14 @@
if (!nativeBitmap) {
SkString msg;
msg.printf("OOM allocating Bitmap with dimensions %i x %i",
- decodeInfo.width(), decodeInfo.height());
+ bitmapInfo.width(), bitmapInfo.height());
doThrowOOME(env, msg.c_str());
return nullptr;
}
- SkAndroidCodec::AndroidOptions options;
- options.fSampleSize = sampleSize;
- auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
+ SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes());
jthrowable jexception = get_and_clear_exception(env);
- int onPartialImageError = jexception ? ImageDecoder::kSourceException
+ int onPartialImageError = jexception ? kSourceException
: 0; // No error.
switch (result) {
case SkCodec::kSuccess:
@@ -321,12 +337,12 @@
break;
case SkCodec::kIncompleteInput:
if (!jexception) {
- onPartialImageError = ImageDecoder::kSourceIncomplete;
+ onPartialImageError = kSourceIncomplete;
}
break;
case SkCodec::kErrorInInput:
if (!jexception) {
- onPartialImageError = ImageDecoder::kSourceMalformedData;
+ onPartialImageError = kSourceMalformedData;
}
break;
default:
@@ -350,20 +366,21 @@
// Ignore ninepatch when post-processing.
if (!jpostProcess) {
// FIXME: Share more code with BitmapFactory.cpp.
- if (decoder->mPeeker->mPatch != nullptr) {
- size_t ninePatchArraySize = decoder->mPeeker->mPatch->serializedSize();
+ auto* peeker = reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get());
+ if (peeker->mPatch != nullptr) {
+ size_t ninePatchArraySize = peeker->mPatch->serializedSize();
ninePatchChunk = env->NewByteArray(ninePatchArraySize);
if (ninePatchChunk == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch chunk.");
return nullptr;
}
- env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker->mPatchSize,
- reinterpret_cast<jbyte*>(decoder->mPeeker->mPatch));
+ env->SetByteArrayRegion(ninePatchChunk, 0, peeker->mPatchSize,
+ reinterpret_cast<jbyte*>(peeker->mPatch));
}
- if (decoder->mPeeker->mHasInsets) {
- ninePatchInsets = decoder->mPeeker->createNinePatchInsets(env, 1.0f);
+ if (peeker->mHasInsets) {
+ ninePatchInsets = peeker->createNinePatchInsets(env, 1.0f);
if (ninePatchInsets == nullptr) {
doThrowOOME(env, "Failed to allocate nine patch insets.");
return nullptr;
@@ -371,58 +388,6 @@
}
}
- if (scale || jsubset) {
- int translateX = 0;
- int translateY = 0;
- SkImageInfo scaledInfo;
- if (jsubset) {
- SkIRect subset;
- GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
-
- translateX = -subset.fLeft;
- translateY = -subset.fTop;
- scaledInfo = bitmapInfo.makeWH(subset.width(), subset.height());
- } else {
- scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
- }
- SkBitmap scaledBm;
- if (!scaledBm.setInfo(scaledInfo)) {
- doThrowIOE(env, "Failed scaled setInfo");
- return nullptr;
- }
-
- sk_sp<Bitmap> scaledPixelRef;
- if (allocator == ImageDecoder::kSharedMemory_Allocator) {
- scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm);
- } else {
- scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
- }
- if (!scaledPixelRef) {
- SkString msg;
- msg.printf("OOM allocating scaled Bitmap with dimensions %i x %i",
- desiredWidth, desiredHeight);
- doThrowOOME(env, msg.c_str());
- return nullptr;
- }
-
- SkPaint paint;
- paint.setBlendMode(SkBlendMode::kSrc);
- paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
-
- SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
- canvas.translate(translateX, translateY);
- if (scale) {
- float scaleX = (float) desiredWidth / decodeInfo.width();
- float scaleY = (float) desiredHeight / decodeInfo.height();
- canvas.scale(scaleX, scaleY);
- }
-
- canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
-
- bm.swap(scaledBm);
- nativeBitmap = std::move(scaledPixelRef);
- }
-
if (jpostProcess) {
std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
@@ -433,12 +398,12 @@
SkAlphaType newAlphaType = bm.alphaType();
switch (pixelFormat) {
- case ImageDecoder::kUnknown:
+ case kUnknown:
break;
- case ImageDecoder::kTranslucent:
+ case kTranslucent:
newAlphaType = kPremul_SkAlphaType;
break;
- case ImageDecoder::kOpaque:
+ case kOpaque:
newAlphaType = kOpaque_SkAlphaType;
break;
default:
@@ -477,7 +442,7 @@
return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
ninePatchChunk, ninePatchInsets);
}
- if (allocator == ImageDecoder::kHardware_Allocator) {
+ if (allocator == kHardware_Allocator) {
doThrowOOME(env, "failed to allocate hardware Bitmap!");
return nullptr;
}
@@ -501,7 +466,7 @@
static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
jobject outPadding) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- decoder->mPeeker->getPadding(env, outPadding);
+ reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get())->getPadding(env, outPadding);
}
static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
diff --git a/core/jni/android/graphics/ImageDecoder.h b/core/jni/android/graphics/ImageDecoder.h
index fd9827b..8a7fa79 100644
--- a/core/jni/android/graphics/ImageDecoder.h
+++ b/core/jni/android/graphics/ImageDecoder.h
@@ -14,48 +14,12 @@
* limitations under the License.
*/
-#include "NinePatchPeeker.h"
-
#include <hwui/Canvas.h>
#include <jni.h>
-class SkAndroidCodec;
-
-using namespace android;
-
-struct ImageDecoder {
- // These need to stay in sync with ImageDecoder.java's Allocator constants.
- enum Allocator {
- kDefault_Allocator = 0,
- kSoftware_Allocator = 1,
- kSharedMemory_Allocator = 2,
- kHardware_Allocator = 3,
- };
-
- // These need to stay in sync with ImageDecoder.java's Error constants.
- enum Error {
- kSourceException = 1,
- kSourceIncomplete = 2,
- kSourceMalformedData = 3,
- };
-
- // These need to stay in sync with PixelFormat.java's Format constants.
- enum PixelFormat {
- kUnknown = 0,
- kTranslucent = -3,
- kOpaque = -1,
- };
-
- std::unique_ptr<SkAndroidCodec> mCodec;
- sk_sp<NinePatchPeeker> mPeeker;
-
- ImageDecoder()
- :mPeeker(new NinePatchPeeker)
- {}
-};
-
// Creates a Java Canvas object from canvas, calls jimageDecoder's PostProcess on it, and then
// releases the Canvas.
// Caller needs to check for exceptions.
-jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas);
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder,
+ std::unique_ptr<android::Canvas> canvas);
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index f670cf9..d945fc4 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -176,6 +176,7 @@
"hwui/AnimatedImageThread.cpp",
"hwui/Bitmap.cpp",
"hwui/Canvas.cpp",
+ "hwui/ImageDecoder.cpp",
"hwui/MinikinSkia.cpp",
"hwui/MinikinUtils.cpp",
"hwui/PaintImpl.cpp",
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index 2ba6fbe..f4149b9 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -44,9 +44,7 @@
namespace android {
-// returns true if rowBytes * height can be represented by a positive int32_t value
-// and places that value in size.
-static bool computeAllocationSize(size_t rowBytes, int height, size_t* size) {
+bool Bitmap::computeAllocationSize(size_t rowBytes, int height, size_t* size) {
return 0 <= height && height <= std::numeric_limits<size_t>::max() &&
!__builtin_mul_overflow(rowBytes, (size_t)height, size) &&
*size <= std::numeric_limits<int32_t>::max();
@@ -66,7 +64,7 @@
// we must respect the rowBytes value already set on the bitmap instead of
// attempting to compute our own.
const size_t rowBytes = bitmap->rowBytes();
- if (!computeAllocationSize(rowBytes, bitmap->height(), &size)) {
+ if (!Bitmap::computeAllocationSize(rowBytes, bitmap->height(), &size)) {
return nullptr;
}
diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h
index 00733c6..1cda046 100644
--- a/libs/hwui/hwui/Bitmap.h
+++ b/libs/hwui/hwui/Bitmap.h
@@ -138,6 +138,10 @@
return mPalette;
}
+ // returns true if rowBytes * height can be represented by a positive int32_t value
+ // and places that value in size.
+ static bool computeAllocationSize(size_t rowBytes, int height, size_t* size);
+
private:
static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes);
diff --git a/libs/hwui/hwui/ImageDecoder.cpp b/libs/hwui/hwui/ImageDecoder.cpp
new file mode 100644
index 0000000..4f2027d
--- /dev/null
+++ b/libs/hwui/hwui/ImageDecoder.cpp
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2019 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 "ImageDecoder.h"
+
+#include <hwui/Bitmap.h>
+
+#include <SkAndroidCodec.h>
+#include <SkCanvas.h>
+#include <SkPaint.h>
+
+using namespace android;
+
+ImageDecoder::ImageDecoder(std::unique_ptr<SkAndroidCodec> codec, sk_sp<SkPngChunkReader> peeker)
+ : mCodec(std::move(codec))
+ , mPeeker(std::move(peeker))
+ , mTargetSize(mCodec->getInfo().dimensions())
+ , mDecodeSize(mTargetSize)
+ , mOutColorType(mCodec->computeOutputColorType(kN32_SkColorType))
+ , mOutAlphaType(mCodec->getInfo().isOpaque() ?
+ kOpaque_SkAlphaType : kPremul_SkAlphaType)
+ , mOutColorSpace(mCodec->getInfo().refColorSpace())
+ , mSampleSize(1)
+{
+}
+
+bool ImageDecoder::setTargetSize(int width, int height) {
+ if (width <= 0 || height <= 0) {
+ return false;
+ }
+
+ auto info = SkImageInfo::Make(width, height, mOutColorType, mOutAlphaType);
+ size_t rowBytes = info.minRowBytes();
+ if (rowBytes == 0) {
+ // This would have overflowed.
+ return false;
+ }
+
+ size_t pixelMemorySize;
+ if (!Bitmap::computeAllocationSize(rowBytes, height, &pixelMemorySize)) {
+ return false;
+ }
+
+ if (mCropRect) {
+ if (mCropRect->right() > width || mCropRect->bottom() > height) {
+ return false;
+ }
+ }
+
+ SkISize targetSize = { width, height }, decodeSize = targetSize;
+ int sampleSize = mCodec->computeSampleSize(&decodeSize);
+
+ if (decodeSize != targetSize && mOutAlphaType == kUnpremul_SkAlphaType
+ && !mCodec->getInfo().isOpaque()) {
+ return false;
+ }
+
+ mTargetSize = targetSize;
+ mDecodeSize = decodeSize;
+ mSampleSize = sampleSize;
+ return true;
+}
+
+bool ImageDecoder::setCropRect(const SkIRect* crop) {
+ if (!crop) {
+ mCropRect.reset();
+ return true;
+ }
+
+ if (crop->left() >= crop->right() || crop->top() >= crop->bottom()) {
+ return false;
+ }
+
+ const auto& size = mTargetSize;
+ if (crop->left() < 0 || crop->top() < 0
+ || crop->right() > size.width() || crop->bottom() > size.height()) {
+ return false;
+ }
+
+ mCropRect.emplace(*crop);
+ return true;
+}
+
+bool ImageDecoder::setOutColorType(SkColorType colorType) {
+ switch (colorType) {
+ case kRGB_565_SkColorType:
+ if (!opaque()) {
+ return false;
+ }
+ break;
+ case kGray_8_SkColorType:
+ if (!gray()) {
+ return false;
+ }
+ mOutColorSpace = nullptr;
+ break;
+ case kN32_SkColorType:
+ break;
+ case kRGBA_F16_SkColorType:
+ break;
+ default:
+ return false;
+ }
+
+ mOutColorType = colorType;
+ return true;
+}
+
+bool ImageDecoder::setOutAlphaType(SkAlphaType alpha) {
+ switch (alpha) {
+ case kOpaque_SkAlphaType:
+ return opaque();
+ case kPremul_SkAlphaType:
+ if (opaque()) {
+ // Opaque can be treated as premul.
+ return true;
+ }
+ break;
+ case kUnpremul_SkAlphaType:
+ if (opaque()) {
+ // Opaque can be treated as unpremul.
+ return true;
+ }
+ if (mDecodeSize != mTargetSize) {
+ return false;
+ }
+ break;
+ default:
+ return false;
+ }
+ mOutAlphaType = alpha;
+ return true;
+}
+
+void ImageDecoder::setOutColorSpace(sk_sp<SkColorSpace> colorSpace) {
+ mOutColorSpace = std::move(colorSpace);
+}
+
+SkImageInfo ImageDecoder::getOutputInfo() const {
+ SkISize size = mCropRect ? mCropRect->size() : mTargetSize;
+ return SkImageInfo::Make(size, mOutColorType, mOutAlphaType, mOutColorSpace);
+}
+
+bool ImageDecoder::opaque() const {
+ return mOutAlphaType == kOpaque_SkAlphaType;
+}
+
+bool ImageDecoder::gray() const {
+ return mCodec->getInfo().colorType() == kGray_8_SkColorType;
+}
+
+SkCodec::Result ImageDecoder::decode(void* pixels, size_t rowBytes) {
+ void* decodePixels = pixels;
+ size_t decodeRowBytes = rowBytes;
+ auto decodeInfo = SkImageInfo::Make(mDecodeSize, mOutColorType, mOutAlphaType, mOutColorSpace);
+ // Used if we need a temporary before scaling or subsetting.
+ // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
+ SkBitmap tmp;
+ const bool scale = mDecodeSize != mTargetSize;
+ if (scale || mCropRect) {
+ if (!tmp.setInfo(decodeInfo)) {
+ return SkCodec::kInternalError;
+ }
+ if (!Bitmap::allocateHeapBitmap(&tmp)) {
+ return SkCodec::kInternalError;
+ }
+ decodePixels = tmp.getPixels();
+ decodeRowBytes = tmp.rowBytes();
+ }
+
+ SkAndroidCodec::AndroidOptions options;
+ options.fSampleSize = mSampleSize;
+ auto result = mCodec->getAndroidPixels(decodeInfo, decodePixels, decodeRowBytes, &options);
+
+ if (scale || mCropRect) {
+ SkBitmap scaledBm;
+ if (!scaledBm.installPixels(getOutputInfo(), pixels, rowBytes)) {
+ return SkCodec::kInternalError;
+ }
+
+ SkPaint paint;
+ paint.setBlendMode(SkBlendMode::kSrc);
+ paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
+
+ SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
+ if (mCropRect) {
+ canvas.translate(-mCropRect->fLeft, -mCropRect->fTop);
+ }
+ if (scale) {
+ float scaleX = (float) mTargetSize.width() / mDecodeSize.width();
+ float scaleY = (float) mTargetSize.height() / mDecodeSize.height();
+ canvas.scale(scaleX, scaleY);
+ }
+
+ canvas.drawBitmap(tmp, 0.0f, 0.0f, &paint);
+ }
+
+ return result;
+}
+
diff --git a/libs/hwui/hwui/ImageDecoder.h b/libs/hwui/hwui/ImageDecoder.h
new file mode 100644
index 0000000..b956f4a
--- /dev/null
+++ b/libs/hwui/hwui/ImageDecoder.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+#pragma once
+
+#include <SkCodec.h>
+#include <SkImageInfo.h>
+#include <SkPngChunkReader.h>
+#include <SkRect.h>
+#include <SkSize.h>
+#include <cutils/compiler.h>
+
+#include <optional>
+
+class SkAndroidCodec;
+
+namespace android {
+
+class ANDROID_API ImageDecoder {
+public:
+ std::unique_ptr<SkAndroidCodec> mCodec;
+ sk_sp<SkPngChunkReader> mPeeker;
+
+ ImageDecoder(std::unique_ptr<SkAndroidCodec> codec,
+ sk_sp<SkPngChunkReader> peeker = nullptr);
+
+ bool setTargetSize(int width, int height);
+ bool setCropRect(const SkIRect*);
+
+ bool setOutColorType(SkColorType outColorType);
+
+ bool setOutAlphaType(SkAlphaType outAlphaType);
+
+ void setOutColorSpace(sk_sp<SkColorSpace> cs);
+
+ // The size is the final size after scaling and cropping.
+ SkImageInfo getOutputInfo() const;
+ SkColorType getOutColorType() const { return mOutColorType; }
+ SkAlphaType getOutAlphaType() const { return mOutAlphaType; }
+
+ bool opaque() const;
+ bool gray() const;
+
+ SkCodec::Result decode(void* pixels, size_t rowBytes);
+
+private:
+ SkISize mTargetSize;
+ SkISize mDecodeSize;
+ SkColorType mOutColorType;
+ SkAlphaType mOutAlphaType;
+ sk_sp<SkColorSpace> mOutColorSpace;
+ int mSampleSize;
+ std::optional<SkIRect> mCropRect;
+
+ ImageDecoder(const ImageDecoder&) = delete;
+ ImageDecoder& operator=(const ImageDecoder&) = delete;
+};
+
+} // namespace android