From 753a56fa563e08aea0928eacf5317842388f4e24 Mon Sep 17 00:00:00 2001 From: Leon Scroggins III Date: Wed, 11 Dec 2019 11:02:15 -0500 Subject: [PATCH] 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 --- .../graphics/AnimatedImageDrawable.cpp | 1 + core/jni/android/graphics/ImageDecoder.cpp | 225 ++++++++---------- core/jni/android/graphics/ImageDecoder.h | 40 +--- libs/hwui/Android.bp | 1 + libs/hwui/hwui/Bitmap.cpp | 6 +- libs/hwui/hwui/Bitmap.h | 4 + libs/hwui/hwui/ImageDecoder.cpp | 213 +++++++++++++++++ libs/hwui/hwui/ImageDecoder.h | 71 ++++++ 8 files changed, 389 insertions(+), 172 deletions(-) create mode 100644 libs/hwui/hwui/ImageDecoder.cpp create mode 100644 libs/hwui/hwui/ImageDecoder.h diff --git a/core/jni/android/graphics/AnimatedImageDrawable.cpp b/core/jni/android/graphics/AnimatedImageDrawable.cpp index 12900261bddd6..6c2a5a3f3fcc4 100644 --- a/core/jni/android/graphics/AnimatedImageDrawable.cpp +++ b/core/jni/android/graphics/AnimatedImageDrawable.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 4d907f6d49420..627f8f5b3e496 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 +#include #include #include @@ -49,6 +51,28 @@ static jmethodID gCallback_onPartialImageMethodID; 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 @@ static jthrowable get_and_clear_exception(JNIEnv* env) { } // 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 throw_exception(JNIEnv* env, ImageDecoder::Error error, const cha static jobject native_create(JNIEnv* env, std::unique_ptr 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 decoder(new ImageDecoder); + sk_sp 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 @@ static jobject native_create(JNIEnv* env, std::unique_ptr stream, 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(decoder.release()), width, height, + reinterpret_cast(decoder), width, height, animated, isNinePatch); } @@ -133,7 +158,7 @@ static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, 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 @@ static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/, 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 @@ static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/, std::unique_ptr 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 @@ static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, std::unique_ptr 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 @@ jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr(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 @@ jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr(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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong } 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong colorType = kN32_SkColorType; } - sk_sp 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 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong } sk_sp 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong // 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(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(decoder->mPeeker->mPatch)); + env->SetByteArrayRegion(ninePatchChunk, 0, peeker->mPatchSize, + reinterpret_cast(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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong } } - 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 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::create_canvas(bm)); @@ -433,12 +398,12 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong 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 @@ static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong 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 jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlon static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr, jobject outPadding) { auto* decoder = reinterpret_cast(nativePtr); - decoder->mPeeker->getPadding(env, outPadding); + reinterpret_cast(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 fd9827bd74cab..8a7fa79503ba0 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 #include -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 mCodec; - sk_sp 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); +jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, + std::unique_ptr canvas); diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp index f670cf97e0c21..d945fc49ec882 100644 --- a/libs/hwui/Android.bp +++ b/libs/hwui/Android.bp @@ -176,6 +176,7 @@ cc_defaults { "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 2ba6fbe76de15..f4149b9b4d7dd 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::max() && !__builtin_mul_overflow(rowBytes, (size_t)height, size) && *size <= std::numeric_limits::max(); @@ -66,7 +64,7 @@ static sk_sp allocateBitmap(SkBitmap* bitmap, AllocPixelRef alloc) { // 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 00733c6b245bb..1cda0465ae64f 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -138,6 +138,10 @@ public: 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 allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); static sk_sp 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 0000000000000..4f2027d978a17 --- /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 + +#include +#include +#include + +using namespace android; + +ImageDecoder::ImageDecoder(std::unique_ptr codec, sk_sp 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 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 0000000000000..b956f4a77793d --- /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 +#include +#include +#include +#include +#include + +#include + +class SkAndroidCodec; + +namespace android { + +class ANDROID_API ImageDecoder { +public: + std::unique_ptr mCodec; + sk_sp mPeeker; + + ImageDecoder(std::unique_ptr codec, + sk_sp peeker = nullptr); + + bool setTargetSize(int width, int height); + bool setCropRect(const SkIRect*); + + bool setOutColorType(SkColorType outColorType); + + bool setOutAlphaType(SkAlphaType outAlphaType); + + void setOutColorSpace(sk_sp 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 mOutColorSpace; + int mSampleSize; + std::optional mCropRect; + + ImageDecoder(const ImageDecoder&) = delete; + ImageDecoder& operator=(const ImageDecoder&) = delete; +}; + +} // namespace android