diff options
-rw-r--r-- | core/jni/android/graphics/BitmapFactory.cpp | 41 | ||||
-rw-r--r-- | core/jni/android/graphics/BitmapFactory.h | 2 | ||||
-rw-r--r-- | core/jni/android/graphics/BitmapRegionDecoder.cpp | 2 | ||||
-rw-r--r-- | core/jni/android/graphics/ImageDecoder.cpp | 2 | ||||
-rw-r--r-- | core/jni/android/graphics/MimeType.h | 21 | ||||
-rw-r--r-- | native/graphics/jni/Android.bp | 6 | ||||
-rw-r--r-- | native/graphics/jni/imagedecoder.cpp | 320 | ||||
-rw-r--r-- | native/graphics/jni/libjnigraphics.map.txt | 17 |
8 files changed, 382 insertions, 29 deletions
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index 3f05c3b57c69..c89d3b0bf826 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -53,42 +53,34 @@ jmethodID gBitmapConfig_nativeToConfigMethodID; using namespace android; -jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) { - const char* mimeType; +const char* getMimeType(SkEncodedImageFormat format) { switch (format) { case SkEncodedImageFormat::kBMP: - mimeType = "image/bmp"; - break; + return "image/bmp"; case SkEncodedImageFormat::kGIF: - mimeType = "image/gif"; - break; + return "image/gif"; case SkEncodedImageFormat::kICO: - mimeType = "image/x-ico"; - break; + return "image/x-ico"; case SkEncodedImageFormat::kJPEG: - mimeType = "image/jpeg"; - break; + return "image/jpeg"; case SkEncodedImageFormat::kPNG: - mimeType = "image/png"; - break; + return "image/png"; case SkEncodedImageFormat::kWEBP: - mimeType = "image/webp"; - break; + return "image/webp"; case SkEncodedImageFormat::kHEIF: - mimeType = "image/heif"; - break; + return "image/heif"; case SkEncodedImageFormat::kWBMP: - mimeType = "image/vnd.wap.wbmp"; - break; + return "image/vnd.wap.wbmp"; case SkEncodedImageFormat::kDNG: - mimeType = "image/x-adobe-dng"; - break; + return "image/x-adobe-dng"; default: - mimeType = nullptr; - break; + return nullptr; } +} +jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) { jstring jstr = nullptr; + const char* mimeType = getMimeType(format); if (mimeType) { // NOTE: Caller should env->ExceptionCheck() for OOM // (can't check for nullptr as it's a valid return value) @@ -290,10 +282,9 @@ static jobject doDecode(JNIEnv* env, std::unique_ptr<SkStreamRewindable> stream, // Set the options and return if the client only wants the size. if (options != NULL) { - jstring mimeType = encodedFormatToString( - env, (SkEncodedImageFormat)codec->getEncodedFormat()); + jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat()); if (env->ExceptionCheck()) { - return nullObjectReturn("OOM in encodedFormatToString()"); + return nullObjectReturn("OOM in getMimeTypeAsJavaString()"); } env->SetIntField(options, gOptions_widthFieldID, scaledWidth); env->SetIntField(options, gOptions_heightFieldID, scaledHeight); diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h index e37c98dc66ff..45bffc44967d 100644 --- a/core/jni/android/graphics/BitmapFactory.h +++ b/core/jni/android/graphics/BitmapFactory.h @@ -26,6 +26,6 @@ extern jfieldID gOptions_bitmapFieldID; extern jclass gBitmapConfig_class; extern jmethodID gBitmapConfig_nativeToConfigMethodID; -jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format); +jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat); #endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_ diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index f18632dfc403..06b4ff849097 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -197,7 +197,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, jlong brdHandle, jint in env->SetIntField(options, gOptions_heightFieldID, bitmap.height()); env->SetObjectField(options, gOptions_mimeFieldID, - encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat())); + getMimeTypeAsJavaString(env, brd->getEncodedFormat())); if (env->ExceptionCheck()) { return nullObjectReturn("OOM in encodedFormatToString()"); } diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp index 627f8f5b3e49..a9002867ae10 100644 --- a/core/jni/android/graphics/ImageDecoder.cpp +++ b/core/jni/android/graphics/ImageDecoder.cpp @@ -475,7 +475,7 @@ static void ImageDecoder_nClose(JNIEnv* /*env*/, jobject /*clazz*/, jlong native static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr); - return encodedFormatToString(env, decoder->mCodec->getEncodedFormat()); + return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat()); } static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) { diff --git a/core/jni/android/graphics/MimeType.h b/core/jni/android/graphics/MimeType.h new file mode 100644 index 000000000000..38a579c595e4 --- /dev/null +++ b/core/jni/android/graphics/MimeType.h @@ -0,0 +1,21 @@ +/* + * Copyright 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 "SkEncodedImageFormat.h" + +const char* getMimeType(SkEncodedImageFormat); diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp index d47de110be3a..376ea77740c2 100644 --- a/native/graphics/jni/Android.bp +++ b/native/graphics/jni/Android.bp @@ -25,16 +25,20 @@ cc_library_shared { // our source files // srcs: [ - "bitmap.cpp", "aassetstreamadaptor.cpp", + "bitmap.cpp", + "imagedecoder.cpp", ], shared_libs: [ "libandroid", "libandroid_runtime", + "libhwui", "liblog", ], + static_libs: ["libarect"], + arch: { arm: { // TODO: This is to work around b/24465209. Remove after root cause is fixed diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp new file mode 100644 index 000000000000..2ef203dd466f --- /dev/null +++ b/native/graphics/jni/imagedecoder.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 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 "aassetstreamadaptor.h" + +#include <android/asset_manager.h> +#include <android/bitmap.h> +#include <android/imagedecoder.h> +#include <android/graphics/MimeType.h> +#include <android/rect.h> +#include <hwui/ImageDecoder.h> +#include <log/log.h> +#include <SkAndroidCodec.h> + +#include <fcntl.h> +#include <optional> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +using namespace android; + +int ResultToErrorCode(SkCodec::Result result) { + switch (result) { + case SkCodec::kIncompleteInput: + return ANDROID_IMAGE_DECODER_INCOMPLETE; + case SkCodec::kErrorInInput: + return ANDROID_IMAGE_DECODER_ERROR; + case SkCodec::kInvalidInput: + return ANDROID_IMAGE_DECODER_INVALID_INPUT; + case SkCodec::kCouldNotRewind: + return ANDROID_IMAGE_DECODER_SEEK_ERROR; + case SkCodec::kUnimplemented: + return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT; + case SkCodec::kInvalidConversion: + return ANDROID_IMAGE_DECODER_INVALID_CONVERSION; + case SkCodec::kInvalidParameters: + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + case SkCodec::kSuccess: + return ANDROID_IMAGE_DECODER_SUCCESS; + case SkCodec::kInvalidScale: + return ANDROID_IMAGE_DECODER_INVALID_SCALE; + case SkCodec::kInternalError: + return ANDROID_IMAGE_DECODER_INTERNAL_ERROR; + } +} + +static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) { + SkCodec::Result result; + auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr, + SkCodec::SelectionPolicy::kPreferAnimation); + auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec), + SkAndroidCodec::ExifOrientationBehavior::kRespect); + if (!androidCodec) { + return ResultToErrorCode(result); + } + + *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec))); + return ANDROID_IMAGE_DECODER_SUCCESS; +} + +int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) { + if (!asset || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + *outDecoder = nullptr; + + auto stream = std::make_unique<AAssetStreamAdaptor>(asset); + return createFromStream(std::move(stream), outDecoder); +} + +static bool isSeekable(int descriptor) { + return ::lseek64(descriptor, 0, SEEK_CUR) != -1; +} + +int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) { + if (fd <= 0 || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + struct stat fdStat; + if (fstat(fd, &fdStat) == -1) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + if (!isSeekable(fd)) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + // SkFILEStream will close its descriptor. Duplicate it so the client will + // still be responsible for closing the original. + int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0); + FILE* file = fdopen(dupDescriptor, "r"); + if (!file) { + close(dupDescriptor); + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file)); + return createFromStream(std::move(stream), outDecoder); +} + +int AImageDecoder_createFromBuffer(const void* buffer, size_t length, + AImageDecoder** outDecoder) { + if (!buffer || !length || !outDecoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + *outDecoder = nullptr; + + // The client is expected to keep the buffer alive as long as the + // AImageDecoder, so we do not need to copy the buffer. + auto stream = std::unique_ptr<SkStreamRewindable>( + new SkMemoryStream(buffer, length, false /* copyData */)); + return createFromStream(std::move(stream), outDecoder); +} + +static ImageDecoder* toDecoder(AImageDecoder* d) { + return reinterpret_cast<ImageDecoder*>(d); +} + +// Note: This differs from the version in android_bitmap.cpp in that this +// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec +// allows decoding single channel images to gray, which Android then treats +// as A_8/ALPHA_8. +static SkColorType getColorType(AndroidBitmapFormat format) { + switch (format) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + return kN32_SkColorType; + case ANDROID_BITMAP_FORMAT_RGB_565: + return kRGB_565_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_4444: + return kARGB_4444_SkColorType; + case ANDROID_BITMAP_FORMAT_A_8: + return kGray_8_SkColorType; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + return kRGBA_F16_SkColorType; + default: + return kUnknown_SkColorType; + } +} + +int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) { + if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE + || format > ANDROID_BITMAP_FORMAT_RGBA_F16) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format)) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; +} + +const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) { + return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder); +} + +static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) { + return reinterpret_cast<const ImageDecoder*>(info); +} + +int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) { + if (!info) { + return 0; + } + return toDecoder(info)->mCodec->getInfo().width(); +} + +int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) { + if (!info) { + return 0; + } + return toDecoder(info)->mCodec->getInfo().height(); +} + +const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) { + if (!info) { + return nullptr; + } + return getMimeType(toDecoder(info)->mCodec->getEncodedFormat()); +} + +bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) { + if (!info) { + return false; + } + return toDecoder(info)->mCodec->codec()->getFrameCount() > 1; +} + +// FIXME: Share with getFormat in android_bitmap.cpp? +static AndroidBitmapFormat getFormat(SkColorType colorType) { + switch (colorType) { + case kN32_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_8888; + case kRGB_565_SkColorType: + return ANDROID_BITMAP_FORMAT_RGB_565; + case kARGB_4444_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_4444; + case kAlpha_8_SkColorType: + return ANDROID_BITMAP_FORMAT_A_8; + case kRGBA_F16_SkColorType: + return ANDROID_BITMAP_FORMAT_RGBA_F16; + default: + return ANDROID_BITMAP_FORMAT_NONE; + } +} + +AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat( + const AImageDecoderHeaderInfo* info) { + if (!info) { + return ANDROID_BITMAP_FORMAT_NONE; + } + return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType)); +} + +int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) { + if (!info) { + // FIXME: Better invalid? + return -1; + } + switch (toDecoder(info)->mCodec->getInfo().alphaType()) { + case kUnknown_SkAlphaType: + LOG_ALWAYS_FATAL("Invalid alpha type"); + return -1; + case kUnpremul_SkAlphaType: + // fall through. premul is the default. + case kPremul_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL; + case kOpaque_SkAlphaType: + return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE; + } +} + +SkAlphaType toAlphaType(int androidBitmapFlags) { + switch (androidBitmapFlags) { + case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL: + return kPremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL: + return kUnpremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE: + return kOpaque_SkAlphaType; + default: + return kUnknown_SkAlphaType; + } +} + +int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) { + if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL + || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag)) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION; +} + +int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) { + if (!decoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return toDecoder(decoder)->setTargetSize(width, height) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE; +} + +int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) { + if (!decoder) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + SkIRect cropIRect; + cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom); + SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect; + return toDecoder(decoder)->setCropRect(cropPtr) + ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER; +} + + +size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) { + if (!decoder) { + return 0; + } + + SkImageInfo info = toDecoder(decoder)->getOutputInfo(); + return info.minRowBytes(); +} + +int AImageDecoder_decodeImage(AImageDecoder* decoder, + void* pixels, size_t stride, + size_t size) { + if (!decoder || !pixels || !stride) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + ImageDecoder* imageDecoder = toDecoder(decoder); + + const int height = imageDecoder->getOutputInfo().height(); + const size_t minStride = AImageDecoder_getMinimumStride(decoder); + // If this calculation were to overflow, it would have been caught in + // setTargetSize. + if (stride < minStride || size < stride * (height - 1) + minStride) { + return ANDROID_IMAGE_DECODER_BAD_PARAMETER; + } + + return ResultToErrorCode(imageDecoder->decode(pixels, stride)); +} + +void AImageDecoder_delete(AImageDecoder* decoder) { + delete toDecoder(decoder); +} diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index a601d8af2830..bdd7f63b2d78 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -1,5 +1,22 @@ LIBJNIGRAPHICS { global: + AImageDecoder_createFromAAsset; + AImageDecoder_createFromFd; + AImageDecoder_createFromBuffer; + AImageDecoder_delete; + AImageDecoder_setAndroidBitmapFormat; + AImageDecoder_setAlphaFlags; + AImageDecoder_getHeaderInfo; + AImageDecoder_getMinimumStride; + AImageDecoder_decodeImage; + AImageDecoder_setTargetSize; + AImageDecoder_setCrop; + AImageDecoderHeaderInfo_getWidth; + AImageDecoderHeaderInfo_getHeight; + AImageDecoderHeaderInfo_getMimeType; + AImageDecoderHeaderInfo_getAlphaFlags; + AImageDecoderHeaderInfo_isAnimated; + AImageDecoderHeaderInfo_getAndroidBitmapFormat; AndroidBitmap_getInfo; AndroidBitmap_lockPixels; AndroidBitmap_unlockPixels; |