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