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:
committed by
Leon Scroggins
parent
700629d8c0
commit
9010e8ba91
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ LIBJNIGRAPHICS {
|
||||
AndroidBitmap_getDataSpace;
|
||||
AndroidBitmap_lockPixels;
|
||||
AndroidBitmap_unlockPixels;
|
||||
AndroidBitmap_compress; # introduced=30
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user