diff options
author | Leon Scroggins III <scroggo@google.com> | 2019-08-20 11:27:17 -0400 |
---|---|---|
committer | Leon Scroggins <scroggo@google.com> | 2020-01-16 22:21:10 +0000 |
commit | 9010e8ba9163a5c8670d3b1b701d97686db1175f (patch) | |
tree | 1ecdffdd8ede0920da3fe89b31abf962a617eed7 | |
parent | 700629d8c08d9114091cc605f417514a25e79a9c (diff) |
Implement native compress API
Bug: 135133301
Test: Ifbcb41388a48afc64bb22623bb7e981b288b2457
Refactor the bulk of Bitmap_compress into hwui/Bitmap::compress, so that
it can be shared by the new API. Update its enum to match the proper
style. Also make the enum a class so it does not need to have a special
return value for a bad parameter, which is now handled by the caller.
Add ABitmap_compress, which implements the new API by calling
hwui/Bitmap::compress.
Change-Id: Ia8ba4c17b517a05b664c6e317e235836473fd7f6
-rwxr-xr-x | core/jni/android/graphics/Bitmap.cpp | 57 | ||||
-rw-r--r-- | core/jni/android/graphics/apex/android_bitmap.cpp | 135 | ||||
-rw-r--r-- | core/jni/android/graphics/apex/include/android/graphics/bitmap.h | 5 | ||||
-rw-r--r-- | libs/hwui/hwui/Bitmap.cpp | 57 | ||||
-rw-r--r-- | libs/hwui/hwui/Bitmap.h | 22 | ||||
-rw-r--r-- | native/graphics/jni/bitmap.cpp | 18 | ||||
-rw-r--r-- | native/graphics/jni/libjnigraphics.map.txt | 1 |
7 files changed, 240 insertions, 55 deletions
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 984f93caa937..593728350037 100755 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -457,15 +457,6 @@ static void Bitmap_reconfigure(JNIEnv* env, jobject clazz, jlong bitmapHandle, sk_ref_sp(bitmap->info().colorSpace()))); } -// These must match the int values in Bitmap.java -enum JavaEncodeFormat { - kJPEG_JavaEncodeFormat = 0, - kPNG_JavaEncodeFormat = 1, - kWEBP_JavaEncodeFormat = 2, - kWEBP_LOSSY_JavaEncodeFormat = 3, - kWEBP_LOSSLESS_JavaEncodeFormat = 4, -}; - static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, jint format, jint quality, jobject jstream, jbyteArray jstorage) { @@ -479,51 +470,9 @@ static jboolean Bitmap_compress(JNIEnv* env, jobject clazz, jlong bitmapHandle, return JNI_FALSE; } - SkBitmap skbitmap; - bitmap->getSkBitmap(&skbitmap); - if (skbitmap.colorType() == kRGBA_F16_SkColorType) { - // Convert to P3 before encoding. This matches SkAndroidCodec::computeOutputColorSpace - // for wide gamuts. - auto cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); - auto info = skbitmap.info().makeColorType(kRGBA_8888_SkColorType) - .makeColorSpace(std::move(cs)); - SkBitmap p3; - if (!p3.tryAllocPixels(info)) { - return JNI_FALSE; - } - - SkPixmap pm; - SkAssertResult(p3.peekPixels(&pm)); // should always work if tryAllocPixels() did. - if (!skbitmap.readPixels(pm)) { - return JNI_FALSE; - } - skbitmap = p3; - } - SkEncodedImageFormat fm; - switch (format) { - case kJPEG_JavaEncodeFormat: - fm = SkEncodedImageFormat::kJPEG; - break; - case kPNG_JavaEncodeFormat: - fm = SkEncodedImageFormat::kPNG; - break; - case kWEBP_JavaEncodeFormat: - fm = SkEncodedImageFormat::kWEBP; - break; - case kWEBP_LOSSY_JavaEncodeFormat: - case kWEBP_LOSSLESS_JavaEncodeFormat: { - SkWebpEncoder::Options options; - options.fQuality = quality; - options.fCompression = format == kWEBP_LOSSY_JavaEncodeFormat ? - SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; - return SkWebpEncoder::Encode(strm.get(), skbitmap.pixmap(), options) ? - JNI_TRUE : JNI_FALSE; - } - default: - return JNI_FALSE; - } - - return SkEncodeImage(strm.get(), skbitmap, fm, quality) ? JNI_TRUE : JNI_FALSE; + auto fm = static_cast<Bitmap::JavaCompressFormat>(format); + auto result = bitmap->bitmap().compress(fm, quality, strm.get()); + return result == Bitmap::CompressResult::Success ? JNI_TRUE : JNI_FALSE; } static inline void bitmapErase(SkBitmap bitmap, const SkColor4f& color, diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp index 6a3c01efe98c..b8e04a7e9a2b 100644 --- a/core/jni/android/graphics/apex/android_bitmap.cpp +++ b/core/jni/android/graphics/apex/android_bitmap.cpp @@ -23,6 +23,7 @@ #include <GraphicsJNI.h> #include <hwui/Bitmap.h> +#include <utils/Color.h> using namespace android; @@ -122,6 +123,7 @@ AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmapHandle) { return getInfo(bitmap->info(), bitmap->rowBytes()); } +namespace { static bool nearlyEqual(float a, float b) { // By trial and error, this is close enough to match for the ADataSpaces we // compare for. @@ -156,6 +158,7 @@ static constexpr skcms_Matrix3x3 kDCIP3 = {{ {0.226676, 0.710327, 0.0629966}, {0.000800549, 0.0432385, 0.78275}, }}; +} // anonymous namespace ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) { Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle); @@ -243,3 +246,135 @@ void ABitmap_notifyPixelsChanged(ABitmap* bitmapHandle) { } return bitmap->notifyPixelsChanged(); } + +namespace { +SkAlphaType getAlphaType(const AndroidBitmapInfo* info) { + switch (info->flags & ANDROID_BITMAP_FLAGS_ALPHA_MASK) { + case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE: + return kOpaque_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL: + return kPremul_SkAlphaType; + case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL: + return kUnpremul_SkAlphaType; + default: + return kUnknown_SkAlphaType; + } +} + +class CompressWriter : public SkWStream { +public: + CompressWriter(void* userContext, AndroidBitmap_compress_write_fn fn) + : mUserContext(userContext), mFn(fn), mBytesWritten(0) {} + + bool write(const void* buffer, size_t size) override { + if (mFn(mUserContext, buffer, size)) { + mBytesWritten += size; + return true; + } + return false; + } + + size_t bytesWritten() const override { return mBytesWritten; } + +private: + void* mUserContext; + AndroidBitmap_compress_write_fn mFn; + size_t mBytesWritten; +}; + +} // anonymous namespace + +int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels, + AndroidBitmapCompressFormat inFormat, int32_t quality, void* userContext, + AndroidBitmap_compress_write_fn fn) { + Bitmap::JavaCompressFormat format; + switch (inFormat) { + case ANDROID_BITMAP_COMPRESS_FORMAT_JPEG: + format = Bitmap::JavaCompressFormat::Jpeg; + break; + case ANDROID_BITMAP_COMPRESS_FORMAT_PNG: + format = Bitmap::JavaCompressFormat::Png; + break; + case ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY: + format = Bitmap::JavaCompressFormat::WebpLossy; + break; + case ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS: + format = Bitmap::JavaCompressFormat::WebpLossless; + break; + default: + // kWEBP_JavaEncodeFormat is a valid parameter for Bitmap::compress, + // for the deprecated Bitmap.CompressFormat.WEBP, but it should not + // be provided via the NDK. Other integers are likewise invalid. + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + SkColorType colorType; + switch (info->format) { + case ANDROID_BITMAP_FORMAT_RGBA_8888: + colorType = kN32_SkColorType; + break; + case ANDROID_BITMAP_FORMAT_RGB_565: + colorType = kRGB_565_SkColorType; + break; + case ANDROID_BITMAP_FORMAT_A_8: + // FIXME b/146637821: Should this encode as grayscale? We should + // make the same decision as for encoding an android.graphics.Bitmap. + // Note that encoding kAlpha_8 as WebP or JPEG will fail. Encoding + // it to PNG encodes as GRAY+ALPHA with a secret handshake that we + // only care about the alpha. I'm not sure whether Android decoding + // APIs respect that handshake. + colorType = kAlpha_8_SkColorType; + break; + case ANDROID_BITMAP_FORMAT_RGBA_F16: + colorType = kRGBA_F16_SkColorType; + break; + default: + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + auto alphaType = getAlphaType(info); + if (alphaType == kUnknown_SkAlphaType) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + sk_sp<SkColorSpace> cs; + if (info->format == ANDROID_BITMAP_FORMAT_A_8) { + // FIXME: A Java Bitmap with ALPHA_8 never has a ColorSpace. So should + // we force that here (as I'm doing now) or should we treat anything + // besides ADATASPACE_UNKNOWN as an error? + cs = nullptr; + } else { + cs = uirenderer::DataSpaceToColorSpace((android_dataspace) dataSpace); + // DataSpaceToColorSpace treats UNKNOWN as SRGB, but compress forces the + // client to specify SRGB if that is what they want. + if (!cs || dataSpace == ADATASPACE_UNKNOWN) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + } + + { + size_t size; + if (!Bitmap::computeAllocationSize(info->stride, info->height, &size)) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + } + + auto imageInfo = + SkImageInfo::Make(info->width, info->height, colorType, alphaType, std::move(cs)); + SkBitmap bitmap; + // We are not going to modify the pixels, but installPixels expects them to + // not be const, since for all it knows we might want to draw to the SkBitmap. + if (!bitmap.installPixels(imageInfo, const_cast<void*>(pixels), info->stride)) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + CompressWriter stream(userContext, fn); + switch (Bitmap::compress(bitmap, format, quality, &stream)) { + case Bitmap::CompressResult::Success: + return ANDROID_BITMAP_RESULT_SUCCESS; + case Bitmap::CompressResult::AllocationFailed: + return ANDROID_BITMAP_RESULT_ALLOCATION_FAILED; + case Bitmap::CompressResult::Error: + return ANDROID_BITMAP_RESULT_JNI_EXCEPTION; + } +} diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h index 32b8a450e147..683851d09d93 100644 --- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h +++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h @@ -58,6 +58,11 @@ void ABitmap_notifyPixelsChanged(ABitmap* bitmap); AndroidBitmapFormat ABitmapConfig_getFormatFromConfig(JNIEnv* env, jobject bitmapConfigObj); jobject ABitmapConfig_getConfigFromFormat(JNIEnv* env, AndroidBitmapFormat format); +// NDK access +int ABitmap_compress(const AndroidBitmapInfo* info, ADataSpace dataSpace, const void* pixels, + AndroidBitmapCompressFormat format, int32_t quality, void* userContext, + AndroidBitmap_compress_write_fn); + __END_DECLS #ifdef __cplusplus diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 84549e8ce6e4..3c402e996218 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -38,7 +38,7 @@ #include <SkCanvas.h> #include <SkImagePriv.h> - +#include <SkWebpEncoder.h> #include <SkHighContrastFilter.h> #include <limits> @@ -471,4 +471,59 @@ BitmapPalette Bitmap::computePalette(const SkImageInfo& info, const void* addr, return BitmapPalette::Unknown; } +Bitmap::CompressResult Bitmap::compress(JavaCompressFormat format, int32_t quality, + SkWStream* stream) { + SkBitmap skbitmap; + getSkBitmap(&skbitmap); + return compress(skbitmap, format, quality, stream); +} + +Bitmap::CompressResult Bitmap::compress(const SkBitmap& bitmap, JavaCompressFormat format, + int32_t quality, SkWStream* stream) { + SkBitmap skbitmap = bitmap; + if (skbitmap.colorType() == kRGBA_F16_SkColorType) { + // Convert to P3 before encoding. This matches + // SkAndroidCodec::computeOutputColorSpace for wide gamuts. Now that F16 + // could already be P3, we still want to convert to 8888. + auto cs = SkColorSpace::MakeRGB(SkNamedTransferFn::kSRGB, SkNamedGamut::kDCIP3); + auto info = skbitmap.info().makeColorType(kRGBA_8888_SkColorType) + .makeColorSpace(std::move(cs)); + SkBitmap p3; + if (!p3.tryAllocPixels(info)) { + return CompressResult::AllocationFailed; + } + + SkPixmap pm; + SkAssertResult(p3.peekPixels(&pm)); // should always work if tryAllocPixels() did. + if (!skbitmap.readPixels(pm)) { + return CompressResult::Error; + } + skbitmap = p3; + } + + SkEncodedImageFormat fm; + switch (format) { + case JavaCompressFormat::Jpeg: + fm = SkEncodedImageFormat::kJPEG; + break; + case JavaCompressFormat::Png: + fm = SkEncodedImageFormat::kPNG; + break; + case JavaCompressFormat::Webp: + fm = SkEncodedImageFormat::kWEBP; + break; + case JavaCompressFormat::WebpLossy: + case JavaCompressFormat::WebpLossless: { + SkWebpEncoder::Options options; + options.fQuality = quality; + options.fCompression = format == JavaCompressFormat::WebpLossy ? + SkWebpEncoder::Compression::kLossy : SkWebpEncoder::Compression::kLossless; + return SkWebpEncoder::Encode(stream, skbitmap.pixmap(), options) + ? CompressResult::Success : CompressResult::Error; + } + } + + return SkEncodeImage(stream, skbitmap, fm, quality) + ? CompressResult::Success : CompressResult::Error; +} } // namespace android diff --git a/libs/hwui/hwui/Bitmap.h b/libs/hwui/hwui/Bitmap.h index 1cda0465ae64..ee365af2f7be 100644 --- a/libs/hwui/hwui/Bitmap.h +++ b/libs/hwui/hwui/Bitmap.h @@ -27,6 +27,8 @@ #include <android/hardware_buffer.h> #endif +class SkWStream; + namespace android { enum class PixelStorageType { @@ -142,6 +144,26 @@ public: // and places that value in size. static bool computeAllocationSize(size_t rowBytes, int height, size_t* size); + // These must match the int values of CompressFormat in Bitmap.java, as well as + // AndroidBitmapCompressFormat. + enum class JavaCompressFormat { + Jpeg = 0, + Png = 1, + Webp = 2, + WebpLossy = 3, + WebpLossless = 4, + }; + + enum class CompressResult { + Success, + AllocationFailed, + Error, + }; + + CompressResult compress(JavaCompressFormat format, int32_t quality, SkWStream* stream); + + static CompressResult compress(const SkBitmap& bitmap, JavaCompressFormat format, + int32_t quality, SkWStream* stream); private: static sk_sp<Bitmap> allocateAshmemBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); static sk_sp<Bitmap> allocateHeapBitmap(size_t size, const SkImageInfo& i, size_t rowBytes); diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp index 26c7f8d709e7..ea8a521c9d5f 100644 --- a/native/graphics/jni/bitmap.cpp +++ b/native/graphics/jni/bitmap.cpp @@ -15,6 +15,7 @@ */ #include <android/bitmap.h> +#include <android/data_space.h> #include <android/graphics/bitmap.h> #include <android/data_space.h> @@ -74,3 +75,20 @@ int AndroidBitmap_unlockPixels(JNIEnv* env, jobject jbitmap) { ABitmap_releaseRef(bitmap.get()); return ANDROID_BITMAP_RESULT_SUCCESS; } + +int AndroidBitmap_compress(const AndroidBitmapInfo* info, + int32_t dataSpace, + const void* pixels, + int32_t format, int32_t quality, + void* userContext, + AndroidBitmap_compress_write_fn fn) { + if (NULL == info || NULL == pixels || NULL == fn) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + if (quality < 0 || quality > 100) { + return ANDROID_BITMAP_RESULT_BAD_PARAMETER; + } + + return ABitmap_compress(info, (ADataSpace) dataSpace, pixels, + (AndroidBitmapCompressFormat) format, quality, userContext, fn); +} diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt index 832770ffb97e..6f6624ba0bd2 100644 --- a/native/graphics/jni/libjnigraphics.map.txt +++ b/native/graphics/jni/libjnigraphics.map.txt @@ -21,6 +21,7 @@ LIBJNIGRAPHICS { AndroidBitmap_getDataSpace; AndroidBitmap_lockPixels; AndroidBitmap_unlockPixels; + AndroidBitmap_compress; # introduced=30 local: *; }; |