summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLeon Scroggins III <scroggo@google.com>2019-08-20 11:27:17 -0400
committerLeon Scroggins <scroggo@google.com>2020-01-16 22:21:10 +0000
commit9010e8ba9163a5c8670d3b1b701d97686db1175f (patch)
tree1ecdffdd8ede0920da3fe89b31abf962a617eed7
parent700629d8c08d9114091cc605f417514a25e79a9c (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-xcore/jni/android/graphics/Bitmap.cpp57
-rw-r--r--core/jni/android/graphics/apex/android_bitmap.cpp135
-rw-r--r--core/jni/android/graphics/apex/include/android/graphics/bitmap.h5
-rw-r--r--libs/hwui/hwui/Bitmap.cpp57
-rw-r--r--libs/hwui/hwui/Bitmap.h22
-rw-r--r--native/graphics/jni/bitmap.cpp18
-rw-r--r--native/graphics/jni/libjnigraphics.map.txt1
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:
*;
};