summaryrefslogtreecommitdiff
path: root/libs/hwui/jni/ImageDecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libs/hwui/jni/ImageDecoder.cpp')
-rw-r--r--libs/hwui/jni/ImageDecoder.cpp526
1 files changed, 526 insertions, 0 deletions
diff --git a/libs/hwui/jni/ImageDecoder.cpp b/libs/hwui/jni/ImageDecoder.cpp
new file mode 100644
index 000000000000..41d939bd6373
--- /dev/null
+++ b/libs/hwui/jni/ImageDecoder.cpp
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2017 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 "Bitmap.h"
+#include "BitmapFactory.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "CreateJavaOutputStreamAdaptor.h"
+#include "GraphicsJNI.h"
+#include "ImageDecoder.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+
+#include <hwui/Bitmap.h>
+#include <hwui/ImageDecoder.h>
+#include <HardwareBitmapUploader.h>
+
+#include <SkAndroidCodec.h>
+#include <SkEncodedImageFormat.h>
+#include <SkFrontBufferedStream.h>
+#include <SkStream.h>
+
+#include <androidfw/Asset.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+using namespace android;
+
+static jclass gImageDecoder_class;
+static jclass gSize_class;
+static jclass gDecodeException_class;
+static jclass gCanvas_class;
+static jmethodID gImageDecoder_constructorMethodID;
+static jmethodID gImageDecoder_postProcessMethodID;
+static jmethodID gSize_constructorMethodID;
+static jmethodID gDecodeException_constructorMethodID;
+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();
+ if (jexception) {
+ env->ExceptionClear();
+ }
+ return jexception;
+}
+
+// Throw a new ImageDecoder.DecodeException. Returns null for convenience.
+static jobject throw_exception(JNIEnv* env, Error error, const char* msg,
+ jthrowable cause, jobject source) {
+ jstring jstr = nullptr;
+ if (msg) {
+ jstr = env->NewStringUTF(msg);
+ if (!jstr) {
+ // Out of memory.
+ return nullptr;
+ }
+ }
+ jthrowable exception = (jthrowable) env->NewObject(gDecodeException_class,
+ gDecodeException_constructorMethodID, error, jstr, cause, source);
+ // Only throw if not out of memory.
+ if (exception) {
+ env->Throw(exception);
+ }
+ return nullptr;
+}
+
+static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream,
+ jobject source, jboolean preferAnimation) {
+ if (!stream.get()) {
+ return throw_exception(env, kSourceMalformedData, "Failed to create a stream",
+ nullptr, source);
+ }
+ sk_sp<NinePatchPeeker> peeker(new NinePatchPeeker);
+ SkCodec::Result result;
+ auto codec = SkCodec::MakeFromStream(
+ 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, kSourceException, "", jexception, source);
+ }
+ if (!codec) {
+ switch (result) {
+ case SkCodec::kIncompleteInput:
+ 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, kSourceMalformedData, msg.c_str(),
+ nullptr, source);
+
+ }
+ }
+
+ const bool animated = codec->getFrameCount() > 1;
+ if (jthrowable jexception = get_and_clear_exception(env)) {
+ return throw_exception(env, kSourceException, "", jexception, source);
+ }
+
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+ SkAndroidCodec::ExifOrientationBehavior::kRespect);
+ if (!androidCodec.get()) {
+ return throw_exception(env, kSourceMalformedData, "", nullptr, source);
+ }
+
+ const auto& info = androidCodec->getInfo();
+ const int width = info.width();
+ const int height = info.height();
+ 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), width, height,
+ animated, isNinePatch);
+}
+
+static jobject ImageDecoder_nCreateFd(JNIEnv* env, jobject /*clazz*/,
+ jobject fileDescriptor, jboolean preferAnimation, jobject source) {
+#ifndef __ANDROID__ // LayoutLib for Windows does not support F_DUPFD_CLOEXEC
+ return throw_exception(env, kSourceException, "Only supported on Android", nullptr, source);
+#else
+ int descriptor = jniGetFDFromFileDescriptor(env, fileDescriptor);
+
+ struct stat fdStat;
+ if (fstat(descriptor, &fdStat) == -1) {
+ return throw_exception(env, kSourceMalformedData,
+ "broken file descriptor; fstat returned -1", nullptr, source);
+ }
+
+ int dupDescriptor = fcntl(descriptor, F_DUPFD_CLOEXEC, 0);
+ FILE* file = fdopen(dupDescriptor, "r");
+ if (file == NULL) {
+ close(dupDescriptor);
+ return throw_exception(env, kSourceMalformedData, "Could not open file",
+ nullptr, source);
+ }
+
+ std::unique_ptr<SkFILEStream> fileStream(new SkFILEStream(file));
+ return native_create(env, std::move(fileStream), source, preferAnimation);
+#endif
+}
+
+static jobject ImageDecoder_nCreateInputStream(JNIEnv* env, jobject /*clazz*/,
+ jobject is, jbyteArray storage, jboolean preferAnimation, jobject source) {
+ std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage, false));
+
+ if (!stream.get()) {
+ return throw_exception(env, kSourceMalformedData, "Failed to create a stream",
+ nullptr, source);
+ }
+
+ std::unique_ptr<SkStream> bufferedStream(
+ SkFrontBufferedStream::Make(std::move(stream),
+ SkCodec::MinBufferedBytesNeeded()));
+ return native_create(env, std::move(bufferedStream), source, preferAnimation);
+}
+
+static jobject ImageDecoder_nCreateAsset(JNIEnv* env, jobject /*clazz*/,
+ jlong assetPtr, jboolean preferAnimation, jobject source) {
+ Asset* asset = reinterpret_cast<Asset*>(assetPtr);
+ std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
+ return native_create(env, std::move(stream), source, preferAnimation);
+}
+
+static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/,
+ jobject jbyteBuffer, jint initialPosition, jint limit,
+ jboolean preferAnimation, jobject source) {
+ std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
+ initialPosition, limit);
+ if (!stream) {
+ return throw_exception(env, kSourceMalformedData, "Failed to read ByteBuffer",
+ nullptr, source);
+ }
+ return native_create(env, std::move(stream), source, preferAnimation);
+}
+
+static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/,
+ jbyteArray byteArray, jint offset, jint length,
+ jboolean preferAnimation, jobject source) {
+ std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length));
+ return native_create(env, std::move(stream), source, preferAnimation);
+}
+
+jint postProcessAndRelease(JNIEnv* env, jobject jimageDecoder, std::unique_ptr<Canvas> canvas) {
+ jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
+ reinterpret_cast<jlong>(canvas.get()));
+ if (!jcanvas) {
+ doThrowOOME(env, "Failed to create Java Canvas for PostProcess!");
+ return kUnknown;
+ }
+
+ // jcanvas now owns canvas.
+ canvas.release();
+
+ return env->CallIntMethod(jimageDecoder, gImageDecoder_postProcessMethodID, jcanvas);
+}
+
+static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jobject jdecoder, jboolean jpostProcess,
+ 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);
+ if (!decoder->setTargetSize(targetWidth, targetHeight)) {
+ doThrowISE(env, "Could not scale to target size!");
+ return nullptr;
+ }
+ if (requireUnpremul && !decoder->setUnpremultipliedRequired(true)) {
+ doThrowISE(env, "Cannot scale unpremultiplied pixels!");
+ return nullptr;
+ }
+
+ SkColorType colorType = kN32_SkColorType;
+ if (asAlphaMask && decoder->gray()) {
+ // We have to trick Skia to decode this to a single channel.
+ colorType = kGray_8_SkColorType;
+ } else if (preferRamOverQuality) {
+ // FIXME: The post-process might add alpha, which would make a 565
+ // 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 (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 != kHardware_Allocator &&
+ (allocator != kDefault_Allocator || requireMutable)) {
+ colorType = kRGB_565_SkColorType;
+ }
+ }
+ // Otherwise, stick with N32
+ } else if (extended) {
+ colorType = kRGBA_F16_SkColorType;
+ } else {
+ colorType = decoder->mCodec->computeOutputColorType(colorType);
+ }
+
+ const bool isHardware = !requireMutable
+ && (allocator == kDefault_Allocator ||
+ allocator == kHardware_Allocator)
+ && colorType != kGray_8_SkColorType;
+
+ if (colorType == kRGBA_F16_SkColorType && isHardware &&
+ !uirenderer::HardwareBitmapUploader::hasFP16Support()) {
+ colorType = kN32_SkColorType;
+ }
+
+ 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;
+ }
+ }
+
+ SkImageInfo bitmapInfo = decoder->getOutputInfo();
+ if (asAlphaMask && colorType == kGray_8_SkColorType) {
+ bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+ }
+
+ SkBitmap bm;
+ if (!bm.setInfo(bitmapInfo)) {
+ doThrowIOE(env, "Failed to setInfo properly");
+ return nullptr;
+ }
+
+ sk_sp<Bitmap> nativeBitmap;
+ if (allocator == kSharedMemory_Allocator) {
+ nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+ } else {
+ nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+ }
+ if (!nativeBitmap) {
+ SkString msg;
+ msg.printf("OOM allocating Bitmap with dimensions %i x %i",
+ bitmapInfo.width(), bitmapInfo.height());
+ doThrowOOME(env, msg.c_str());
+ return nullptr;
+ }
+
+ SkCodec::Result result = decoder->decode(bm.getPixels(), bm.rowBytes());
+ jthrowable jexception = get_and_clear_exception(env);
+ int onPartialImageError = jexception ? kSourceException
+ : 0; // No error.
+ switch (result) {
+ case SkCodec::kSuccess:
+ // Ignore the exception, since the decode was successful anyway.
+ jexception = nullptr;
+ onPartialImageError = 0;
+ break;
+ case SkCodec::kIncompleteInput:
+ if (!jexception) {
+ onPartialImageError = kSourceIncomplete;
+ }
+ break;
+ case SkCodec::kErrorInInput:
+ if (!jexception) {
+ onPartialImageError = kSourceMalformedData;
+ }
+ break;
+ default:
+ SkString msg;
+ msg.printf("getPixels failed with error %s", SkCodec::ResultToString(result));
+ doThrowIOE(env, msg.c_str());
+ return nullptr;
+ }
+
+ if (onPartialImageError) {
+ env->CallVoidMethod(jdecoder, gCallback_onPartialImageMethodID, onPartialImageError,
+ jexception);
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+ }
+
+ jbyteArray ninePatchChunk = nullptr;
+ jobject ninePatchInsets = nullptr;
+
+ // Ignore ninepatch when post-processing.
+ if (!jpostProcess) {
+ // FIXME: Share more code with BitmapFactory.cpp.
+ 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, peeker->mPatchSize,
+ reinterpret_cast<jbyte*>(peeker->mPatch));
+ }
+
+ if (peeker->mHasInsets) {
+ ninePatchInsets = peeker->createNinePatchInsets(env, 1.0f);
+ if (ninePatchInsets == nullptr) {
+ doThrowOOME(env, "Failed to allocate nine patch insets.");
+ return nullptr;
+ }
+ }
+ }
+
+ if (jpostProcess) {
+ std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
+
+ jint pixelFormat = postProcessAndRelease(env, jdecoder, std::move(canvas));
+ if (env->ExceptionCheck()) {
+ return nullptr;
+ }
+
+ SkAlphaType newAlphaType = bm.alphaType();
+ switch (pixelFormat) {
+ case kUnknown:
+ break;
+ case kTranslucent:
+ newAlphaType = kPremul_SkAlphaType;
+ break;
+ case kOpaque:
+ newAlphaType = kOpaque_SkAlphaType;
+ break;
+ default:
+ SkString msg;
+ msg.printf("invalid return from postProcess: %i", pixelFormat);
+ doThrowIAE(env, msg.c_str());
+ return nullptr;
+ }
+
+ if (newAlphaType != bm.alphaType()) {
+ if (!bm.setAlphaType(newAlphaType)) {
+ SkString msg;
+ msg.printf("incompatible return from postProcess: %i", pixelFormat);
+ doThrowIAE(env, msg.c_str());
+ return nullptr;
+ }
+ nativeBitmap->setAlphaType(newAlphaType);
+ }
+ }
+
+ int bitmapCreateFlags = 0x0;
+ if (!requireUnpremul) {
+ // Even if the image is opaque, setting this flag means that
+ // if alpha is added (e.g. by PostProcess), it will be marked as
+ // premultiplied.
+ bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied;
+ }
+
+ if (requireMutable) {
+ bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable;
+ } else {
+ if (isHardware) {
+ sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
+ if (hwBitmap) {
+ hwBitmap->setImmutable();
+ return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
+ ninePatchChunk, ninePatchInsets);
+ }
+ if (allocator == kHardware_Allocator) {
+ doThrowOOME(env, "failed to allocate hardware Bitmap!");
+ return nullptr;
+ }
+ // If we failed to create a hardware bitmap, go ahead and create a
+ // software one.
+ }
+
+ nativeBitmap->setImmutable();
+ }
+ return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
+ ninePatchInsets);
+}
+
+static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jint sampleSize) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
+ return env->NewObject(gSize_class, gSize_constructorMethodID, size.width(), size.height());
+}
+
+static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+ jobject outPadding) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ reinterpret_cast<NinePatchPeeker*>(decoder->mPeeker.get())->getPadding(env, outPadding);
+}
+
+static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
+ delete reinterpret_cast<ImageDecoder*>(nativePtr);
+}
+
+static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+ return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat());
+}
+
+static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
+ auto* codec = reinterpret_cast<ImageDecoder*>(nativePtr)->mCodec.get();
+ auto colorType = codec->computeOutputColorType(kN32_SkColorType);
+ sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType);
+ return GraphicsJNI::getColorSpace(env, colorSpace.get(), colorType);
+}
+
+static const JNINativeMethod gImageDecoderMethods[] = {
+ { "nCreate", "(JZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateAsset },
+ { "nCreate", "(Ljava/nio/ByteBuffer;IIZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
+ { "nCreate", "([BIIZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
+ { "nCreate", "(Ljava/io/InputStream;[BZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateInputStream },
+ { "nCreate", "(Ljava/io/FileDescriptor;ZLandroid/graphics/ImageDecoder$Source;)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateFd },
+ { "nDecodeBitmap", "(JLandroid/graphics/ImageDecoder;ZIILandroid/graphics/Rect;ZIZZZJZ)Landroid/graphics/Bitmap;",
+ (void*) ImageDecoder_nDecodeBitmap },
+ { "nGetSampledSize","(JI)Landroid/util/Size;", (void*) ImageDecoder_nGetSampledSize },
+ { "nGetPadding", "(JLandroid/graphics/Rect;)V", (void*) ImageDecoder_nGetPadding },
+ { "nClose", "(J)V", (void*) ImageDecoder_nClose},
+ { "nGetMimeType", "(J)Ljava/lang/String;", (void*) ImageDecoder_nGetMimeType },
+ { "nGetColorSpace", "(J)Landroid/graphics/ColorSpace;", (void*) ImageDecoder_nGetColorSpace },
+};
+
+int register_android_graphics_ImageDecoder(JNIEnv* env) {
+ gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
+ gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JIIZZ)V");
+ gImageDecoder_postProcessMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "postProcessAndRelease", "(Landroid/graphics/Canvas;)I");
+
+ gSize_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/util/Size"));
+ gSize_constructorMethodID = GetMethodIDOrDie(env, gSize_class, "<init>", "(II)V");
+
+ gDecodeException_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$DecodeException"));
+ gDecodeException_constructorMethodID = GetMethodIDOrDie(env, gDecodeException_class, "<init>", "(ILjava/lang/String;Ljava/lang/Throwable;Landroid/graphics/ImageDecoder$Source;)V");
+
+ gCallback_onPartialImageMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "onPartialImage", "(ILjava/lang/Throwable;)V");
+
+ gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
+ gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
+ gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V");
+
+ return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods,
+ NELEM(gImageDecoderMethods));
+}