Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9010e8ba authored by Leon Scroggins III's avatar Leon Scroggins III Committed by Leon Scroggins
Browse files

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
parent 700629d8
Loading
Loading
Loading
Loading
+3 −54
Original line number Diff line number Diff line
@@ -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,
+135 −0
Original line number Diff line number Diff line
@@ -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;
    }
}
+5 −0
Original line number Diff line number Diff line
@@ -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
+56 −1
Original line number Diff line number Diff line
@@ -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
+22 −0
Original line number Diff line number Diff line
@@ -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);
Loading