Unverified Commit d2537c3e authored by Hubert Błaszczyk's avatar Hubert Błaszczyk Committed by GitHub

Expose png compression level (#1220)

`encodeToData` has been deprecated in Skia some time ago:
https://skia.googlesource.com/skia/+/refs/heads/main/RELEASE_NOTES.md#milestone-114

Previously, for png format the `quality` was ignored as it is a lossless
format.
The current skiko behavior for png format converts quality to zlib
compression level which makes the default value to 9 compared to the
default value 6 in skia.

Also, it was confusing as with lower quality you expect smaller size
which was not the case for png images. Quality stayed the same but
the size increased as the compression level was smaller
parent 642e3008
......@@ -215,30 +215,27 @@ class Image internal constructor(ptr: NativePointer) : RefCnt(ptr), IHasImageInf
*
* quality is a platform and format specific metric trading off size and encoding
* error. When used, quality equaling 100 encodes with the least error. quality may
* be ignored by the encoder.
* be ignored by the encoder. PNG is lossless, so quality is ignored for PNG. Use
* `pngCompressionLevel` to control PNG compression.
*
* @param format one of: [EncodedImageFormat.JPEG], [EncodedImageFormat.PNG], [EncodedImageFormat.WEBP]
* @param quality encoder specific metric with 100 equaling best
* @return encoded Image, or null
*
* @see [https://fiddle.skia.org/c/@Image_encodeToData](https://fiddle.skia.org/c/@Image_encodeToData)
*/
/**
*
* Encodes Image pixels, returning result as Data. Returns existing encoded data
* if present; otherwise, Image is encoded with [EncodedImageFormat.PNG].
* pngCompressionLevel is used only for PNG and is passed as zlib compression level.
* It must be in the 0..9 range, where 6 is the default, 9 is maximal compression,
* and 0 skips zlib compression.
*
* @param format one of: [EncodedImageFormat.JPEG], [EncodedImageFormat.PNG], [EncodedImageFormat.WEBP]
* @param quality encoder specific metric with 100 equaling best, ignored for PNG
* @param pngCompressionLevel PNG zlib compression level in 0..9
* @return encoded Image, or null
*
* Returns null if existing encoded data is missing or invalid, and encoding fails.
*
* @return encoded Image, or null
*
* @see [https://fiddle.skia.org/c/@Image_encodeToData_2](https://fiddle.skia.org/c/@Image_encodeToData_2)
*/
fun encodeToData(format: EncodedImageFormat = EncodedImageFormat.PNG, quality: Int = 100): Data? {
fun encodeToData(
format: EncodedImageFormat = EncodedImageFormat.PNG,
quality: Int = 100,
pngCompressionLevel: Int = 6
): Data? {
return try {
Stats.onNativeCall()
val ptr = _nEncodeToData(_ptr, format.ordinal, quality)
val ptr = _nEncodeToData(_ptr, format.ordinal, quality, pngCompressionLevel)
if (ptr == NullPointer) null else org.jetbrains.skia.Data(ptr)
} finally {
reachabilityBarrier(this)
......@@ -461,7 +458,12 @@ private external fun _nMakeFromPixmap(pixmapPtr: NativePointer): NativePointer
internal external fun _nMakeFromEncoded(bytes: InteropPointer, encodedLength: Int): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nEncodeToData")
private external fun _nEncodeToData(ptr: NativePointer, format: Int, quality: Int): NativePointer
private external fun _nEncodeToData(
ptr: NativePointer,
format: Int,
quality: Int,
pngCompressionLevel: Int
): NativePointer
@ExternalSymbolName("org_jetbrains_skia_Image__1nPeekPixelsToPixmap")
private external fun _nPeekPixelsToPixmap(ptr: NativePointer, pixmapPtr: NativePointer): Boolean
......
......@@ -16,6 +16,8 @@ class ImageTest {
assertEquals(100, image.height)
assertTrue(image.encodeToData()?.bytes!!.isNotEmpty())
assertTrue(image.encodeToData(EncodedImageFormat.PNG, pngCompressionLevel = 1)?.bytes!!.isNotEmpty())
assertTrue(image.encodeToData(EncodedImageFormat.PNG, pngCompressionLevel = 0)?.bytes!!.isNotEmpty())
assertTrue(image.encodeToData(EncodedImageFormat.JPEG)?.bytes!!.isNotEmpty())
assertTrue(image.encodeToData(EncodedImageFormat.JPEG, 50)?.bytes!!.isNotEmpty())
assertTrue(image.encodeToData(EncodedImageFormat.WEBP)?.bytes!!.isNotEmpty())
......
......@@ -78,14 +78,14 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_ImageKt_Image_1nGetIma
}
extern "C" JNIEXPORT jlong JNICALL Java_org_jetbrains_skia_ImageKt__1nEncodeToData
(JNIEnv* env, jclass jclass, jlong ptr, jint format, jint quality) {
(JNIEnv* env, jclass jclass, jlong ptr, jint format, jint quality, jint pngCompressionLevel) {
SkImage* instance = reinterpret_cast<SkImage*>(static_cast<uintptr_t>(ptr));
SkEncodedImageFormat skFormat = static_cast<SkEncodedImageFormat>(format);
if (!instance->isTextureBacked()) {
switch (skFormat) {
case SkEncodedImageFormat::kPNG: {
SkPngEncoder::Options options = SkPngEncoder::Options();
options.fZLibLevel = std::max(0, std::min((int)(quality / 10), 9));
options.fZLibLevel = pngCompressionLevel;
SkData* data = SkPngEncoder::Encode(nullptr, instance, options).release();
return reinterpret_cast<jlong>(data);
}
......
......@@ -69,14 +69,14 @@ SKIKO_EXPORT void org_jetbrains_skia_Image__1nGetImageInfo
SKIKO_EXPORT KNativePointer org_jetbrains_skia_Image__1nEncodeToData
(KNativePointer ptr, KInt format, KInt quality) {
(KNativePointer ptr, KInt format, KInt quality, KInt pngCompressionLevel) {
SkImage* instance = reinterpret_cast<SkImage*>((ptr));
SkEncodedImageFormat skFormat = static_cast<SkEncodedImageFormat>(format);
if (!instance->isTextureBacked()) {
switch (skFormat) {
case SkEncodedImageFormat::kPNG: {
SkPngEncoder::Options options = SkPngEncoder::Options();
options.fZLibLevel = std::max(0, std::min(quality / 10, 9));
options.fZLibLevel = pngCompressionLevel;
SkData* data = SkPngEncoder::Encode(nullptr, instance, options).release();
return reinterpret_cast<KNativePointer>(data);
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment