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

Commit c3d20c19 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Implement native compress API"

parents 6186ae60 9010e8ba
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