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
This commit is contained in:
Leon Scroggins III
2019-08-20 11:27:17 -04:00
committed by Leon Scroggins
parent 700629d8c0
commit 9010e8ba91
7 changed files with 240 additions and 55 deletions

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -21,6 +21,7 @@ LIBJNIGRAPHICS {
AndroidBitmap_getDataSpace;
AndroidBitmap_lockPixels;
AndroidBitmap_unlockPixels;
AndroidBitmap_compress; # introduced=30
local:
*;
};