From a4a6ee8fdb00bd747b9af820c608f41411690cf9 Mon Sep 17 00:00:00 2001 From: Nikita Prokopov Date: Tue, 8 Aug 2023 22:10:56 +0200 Subject: [PATCH] Unbundled Image encoders to their own classes, kept backward-compatibility methods --- .github/workflows/build.yml | 15 +- CHANGELOG.md | 11 +- platform/cc/EncoderJPEG.cc | 13 ++ platform/cc/EncoderPNG.cc | 14 ++ platform/cc/EncoderWEBP.cc | 13 ++ platform/cc/Image.cc | 41 +--- platform/cc/Surface.cc | 3 +- script/build.py | 2 +- script/build_utils.py | 4 +- script/common.py | 2 +- shared/java/EncodeJPEGAlphaMode.java | 6 + shared/java/EncodeJPEGDownsampleMode.java | 18 ++ shared/java/EncodeJPEGOptions.java | 28 +-- shared/java/EncodePNGFilterFlag.java | 19 ++ shared/java/EncodePNGOptions.java | 22 +- shared/java/EncodeWEBPCompressionMode.java | 8 + shared/java/EncodeWEBPOptions.java | 11 +- shared/java/EncodedImageFormat.java | 4 + shared/java/EncoderJPEG.java | 55 +++++ shared/java/EncoderPNG.java | 55 +++++ shared/java/EncoderWEBP.java | 55 +++++ shared/java/Image.java | 233 ++++++++------------- tests/java/ImageTest.java | 27 ++- 23 files changed, 393 insertions(+), 266 deletions(-) create mode 100644 platform/cc/EncoderJPEG.cc create mode 100644 platform/cc/EncoderPNG.cc create mode 100644 platform/cc/EncoderWEBP.cc create mode 100644 shared/java/EncodeJPEGAlphaMode.java create mode 100644 shared/java/EncodeJPEGDownsampleMode.java create mode 100644 shared/java/EncodePNGFilterFlag.java create mode 100644 shared/java/EncodeWEBPCompressionMode.java create mode 100644 shared/java/EncoderJPEG.java create mode 100644 shared/java/EncoderPNG.java create mode 100644 shared/java/EncoderWEBP.java diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 369e2613..db096738 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -66,13 +66,14 @@ jobs: with: name: linux-x64-jars path: target/skija-linux-x64-* - - run: python3 script/clean.py - - run: python3 script/build.py --arch arm64 - - run: python3 script/package_platform.py --arch arm64 - - uses: actions/upload-artifact@v3 - with: - name: linux-arm64-jars - path: target/skija-linux-arm64-* + # Disabled until https://github.com/HumbleUI/SkiaBuild/issues/8 + # - run: python3 script/clean.py + # - run: python3 script/build.py --arch arm64 + # - run: python3 script/package_platform.py --arch arm64 + # - uses: actions/upload-artifact@v3 + # with: + # name: linux-arm64-jars + # path: target/skija-linux-arm64-* build_windows: runs-on: windows-2019 diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ffe690..4bba316c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,5 @@ # 0.116.0 - Aug 8, 2023 -Added: - -- ARM64 jars thx @Glavo - Fixed: - Race condition in shared library unpacking #54 #56 thx @dzaima @@ -13,8 +9,13 @@ Changed: - Skia version m109-664500fa93 -> m116-f44dbc40d8 - [ BREAKING ] `Bitmap::erase(Color4f)` no longer takes ColorSpace -- [ BREAKING ] `Image::encodeToData` has been replaced with `::encodeJPEG`, `::encodePNG`, `::encodeWEBP`, plus the encoding options - [ BREAKING ] `ImageFilter::MakeMagnifier` required two more arguments: `zoomAmount` and `SamplingMode` +- `Image::makeRaster(ImageInfo, byte[], long)` -> `makeRasterFromBytes` +- `Image::makeRaster(ImageInfo, Data, long)` -> `makeRasterFromData` +- `Image::makeRasterBitmap(Bitmap)` -> `makeRasterFromBitmap` +- `Image::makeRasterPixmap(Pixmap)` -> `makeRasterFromPixmap` +- `Image::makeFromEncoded(byte[])` -> `makeDeferredFromEncodedBytes` +- `Image::encodeToData` has been replaced with `EncoderJPEG`, `EncoderPNG`, `EncoderWEBP`, plus the encoding options Removed: diff --git a/platform/cc/EncoderJPEG.cc b/platform/cc/EncoderJPEG.cc new file mode 100644 index 00000000..1d770832 --- /dev/null +++ b/platform/cc/EncoderJPEG.cc @@ -0,0 +1,13 @@ +#include +#include "SkData.h" +#include "SkImage.h" +#include "SkJpegEncoder.h" + +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_EncoderJPEG__1nEncode + (JNIEnv* env, jclass jclass, jlong ctxPtr, jlong imagePtr, jint quality, jint alphaMode, jint downsampleMode) { + GrDirectContext* ctx = reinterpret_cast(static_cast(ctxPtr)); + SkImage* image = reinterpret_cast(static_cast(imagePtr)); + SkJpegEncoder::Options opts {quality, (SkJpegEncoder::Downsample) downsampleMode, (SkJpegEncoder::AlphaOption) alphaMode}; + sk_sp data = SkJpegEncoder::Encode(ctx, image, opts); + return reinterpret_cast(data.release()); +} diff --git a/platform/cc/EncoderPNG.cc b/platform/cc/EncoderPNG.cc new file mode 100644 index 00000000..68c69fec --- /dev/null +++ b/platform/cc/EncoderPNG.cc @@ -0,0 +1,14 @@ +#include +#include "SkData.h" +#include "SkImage.h" +#include "SkPngEncoder.h" + +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_EncoderPNG__1nEncode + (JNIEnv* env, jclass jclass, jlong ctxPtr, jlong imagePtr, jint flagsInt, jint zlibLevel) { + GrDirectContext* ctx = reinterpret_cast(static_cast(ctxPtr)); + SkImage* image = reinterpret_cast(static_cast(imagePtr)); + SkPngEncoder::FilterFlag flags = static_cast(flagsInt); + SkPngEncoder::Options opts {flags, zlibLevel}; + sk_sp data = SkPngEncoder::Encode(ctx, image, opts); + return reinterpret_cast(data.release()); +} diff --git a/platform/cc/EncoderWEBP.cc b/platform/cc/EncoderWEBP.cc new file mode 100644 index 00000000..04bafc0a --- /dev/null +++ b/platform/cc/EncoderWEBP.cc @@ -0,0 +1,13 @@ +#include +#include "SkData.h" +#include "SkImage.h" +#include "SkWebpEncoder.h" + +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_EncoderWEBP__1nEncode + (JNIEnv* env, jclass jclass, jlong ctxPtr, jlong imagePtr, jint compressionMode, jfloat quality) { + GrDirectContext* ctx = reinterpret_cast(static_cast(ctxPtr)); + SkImage* image = reinterpret_cast(static_cast(imagePtr)); + SkWebpEncoder::Options opts {(SkWebpEncoder::Compression) compressionMode, quality}; + sk_sp data = SkWebpEncoder::Encode(ctx, image, opts); + return reinterpret_cast(data.release()); +} diff --git a/platform/cc/Image.cc b/platform/cc/Image.cc index 7297ed72..b2ca79f8 100644 --- a/platform/cc/Image.cc +++ b/platform/cc/Image.cc @@ -3,13 +3,10 @@ #include "SkBitmap.h" #include "SkData.h" #include "SkImage.h" -#include "SkJpegEncoder.h" -#include "SkPngEncoder.h" -#include "SkWebpEncoder.h" #include "SkShader.h" #include "interop.hh" -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRaster +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRasterFromBytes (JNIEnv* env, jclass jclass, jint width, jint height, jint colorType, jint alphaType, jlong colorSpacePtr, jbyteArray bytesArr, jlong rowBytes) { SkColorSpace* colorSpace = reinterpret_cast(static_cast(colorSpacePtr)); SkImageInfo imageInfo = SkImageInfo::Make(width, @@ -23,7 +20,7 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRa return reinterpret_cast(image.release()); } -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRasterData +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRasterFromData (JNIEnv* env, jclass jclass, jint width, jint height, jint colorType, jint alphaType, jlong colorSpacePtr, jlong dataPtr, jlong rowBytes) { SkColorSpace* colorSpace = reinterpret_cast(static_cast(colorSpacePtr)); SkImageInfo imageInfo = SkImageInfo::Make(width, @@ -36,21 +33,21 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRa return reinterpret_cast(image.release()); } -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeFromBitmap +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRasterFromBitmap (JNIEnv* env, jclass jclass, jlong bitmapPtr) { SkBitmap* bitmap = reinterpret_cast(static_cast(bitmapPtr)); sk_sp image = SkImages::RasterFromBitmap(*bitmap); return reinterpret_cast(image.release()); } -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeFromPixmap +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeRasterFromPixmap (JNIEnv* env, jclass jclass, jlong pixmapPtr) { SkPixmap* pixmap = reinterpret_cast(static_cast(pixmapPtr)); sk_sp image = SkImages::RasterFromPixmap(*pixmap, nullptr, nullptr); return reinterpret_cast(image.release()); } -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeFromEncoded +extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeDeferredFromEncodedBytes (JNIEnv* env, jclass jclass, jbyteArray encodedArray) { jsize encodedLen = env->GetArrayLength(encodedArray); jbyte* encoded = env->GetByteArrayElements(encodedArray, 0); @@ -68,34 +65,6 @@ extern "C" JNIEXPORT jobject JNICALL Java_io_github_humbleui_skija_Image__1nGetI return skija::ImageInfo::toJava(env, instance->imageInfo()); } -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nEncodePNG - (JNIEnv* env, jclass jclass, jlong ptr, jlong ctxPtr, jint flagsInt, jint zlibLevel) { - SkImage* instance = reinterpret_cast(static_cast(ptr)); - GrDirectContext* ctx = reinterpret_cast(static_cast(ctxPtr)); - SkPngEncoder::FilterFlag flags = static_cast(flagsInt); - SkPngEncoder::Options opts {flags, zlibLevel}; - sk_sp data = SkPngEncoder::Encode(ctx, instance, opts); - return reinterpret_cast(data.release()); -} - -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nEncodeJPEG - (JNIEnv* env, jclass jclass, jlong ptr, jlong ctxPtr, jint quality, jint alphaMode, jint downsampleMode) { - SkImage* instance = reinterpret_cast(static_cast(ptr)); - GrDirectContext* ctx = reinterpret_cast(static_cast(ctxPtr)); - SkJpegEncoder::Options opts {quality, (SkJpegEncoder::Downsample) downsampleMode, (SkJpegEncoder::AlphaOption) alphaMode}; - sk_sp data = SkJpegEncoder::Encode(ctx, instance, opts); - return reinterpret_cast(data.release()); -} - -extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nEncodeWEBP - (JNIEnv* env, jclass jclass, jlong ptr, jlong ctxPtr, jint compressionMode, jfloat quality) { - SkImage* instance = reinterpret_cast(static_cast(ptr)); - GrDirectContext* ctx = reinterpret_cast(static_cast(ctxPtr)); - SkWebpEncoder::Options opts {(SkWebpEncoder::Compression) compressionMode, quality}; - sk_sp data = SkWebpEncoder::Encode(ctx, instance, opts); - return reinterpret_cast(data.release()); -} - extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Image__1nMakeShader (JNIEnv* env, jclass jclass, jlong ptr, jint tmx, jint tmy, jlong sampling, jfloatArray localMatrixArr) { SkImage* instance = reinterpret_cast(static_cast(ptr)); diff --git a/platform/cc/Surface.cc b/platform/cc/Surface.cc index 41af7645..0c6538db 100644 --- a/platform/cc/Surface.cc +++ b/platform/cc/Surface.cc @@ -3,7 +3,6 @@ #include "GrDirectContext.h" #include "SkSurface.h" #include "ganesh/SkSurfaceGanesh.h" -#include "ganesh/mtl/SkSurfaceMetal.h" #include "interop.hh" extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Surface__1nMakeRasterDirect @@ -93,6 +92,8 @@ extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Surface__1nMake } #ifdef SK_METAL +#include "ganesh/mtl/SkSurfaceMetal.h" + extern "C" JNIEXPORT jlong JNICALL Java_io_github_humbleui_skija_Surface__1nMakeFromMTKView (JNIEnv* env, jclass jclass, jlong contextPtr, jlong mtkViewPtr, jint surfaceOrigin, jint sampleCount, jint colorType, jlong colorSpacePtr, jobject surfacePropsObj) { GrDirectContext* context = reinterpret_cast(static_cast(contextPtr)); diff --git a/script/build.py b/script/build.py index 8776c410..540e5b12 100755 --- a/script/build.py +++ b/script/build.py @@ -5,7 +5,7 @@ def main(): parser = argparse.ArgumentParser() parser.add_argument('--debug', action='store_true') parser.add_argument('--arch', default=build_utils.arch) - parser.add_argument('--skia-dir') # , default='/Users/tonsky/ws/skia-build/skia') + parser.add_argument('--skia-dir') parser.add_argument('--skia-release', default='m116-f44dbc40d8') parser.add_argument('--cmake-toolchain-file') (args, _) = parser.parse_known_args() diff --git a/script/build_utils.py b/script/build_utils.py index 1e409381..f5640fd8 100644 --- a/script/build_utils.py +++ b/script/build_utils.py @@ -93,7 +93,7 @@ def fetch_maven(group, name, version, classifier=None, repo='https://repo1.maven return file def fetch_all_maven(deps, repo='https://repo1.maven.org/maven2'): - return [fetch_maven(dep['group'], dep['name'], dep['version'], repo=repo) for dep in deps] + return [fetch_maven(dep['group'], dep['name'], dep['version'], repo=dep.get('repo', repo)) for dep in deps] def javac(sources, target, classpath = [], modulepath = [], add_modules = [], release = '11', opts=[]): makedirs(target) @@ -125,7 +125,7 @@ def jar(target: str, *content: List[Tuple[str, str]], opts=[]) -> str: @functools.lru_cache(maxsize=1) def lombok(): - return fetch_maven('org.projectlombok', 'lombok', '1.18.26') + return fetch_maven('org.projectlombok', 'lombok', '1.18.28') def delombok(dirs: List[str], target: str, classpath: List[str] = [], modulepath: List[str] = []): sources = files(*[dir + "/**/*.java" for dir in dirs]) diff --git a/script/common.py b/script/common.py index 9eb1596c..fa2e14da 100755 --- a/script/common.py +++ b/script/common.py @@ -12,7 +12,7 @@ compile_deps = [ {'group': 'org.jetbrains', 'name': 'annotations', 'version': '20.1.0'}, - {'group': 'org.projectlombok', 'name': 'lombok', 'version': '1.18.26'}, + {'group': 'org.projectlombok', 'name': 'lombok', 'version': '1.18.28'}, ] @functools.lru_cache(maxsize=1) diff --git a/shared/java/EncodeJPEGAlphaMode.java b/shared/java/EncodeJPEGAlphaMode.java new file mode 100644 index 00000000..98b332fa --- /dev/null +++ b/shared/java/EncodeJPEGAlphaMode.java @@ -0,0 +1,6 @@ +package io.github.humbleui.skija; + +public enum EncodeJPEGAlphaMode { + IGNORE, + BLEND_ON_BLACK +} diff --git a/shared/java/EncodeJPEGDownsampleMode.java b/shared/java/EncodeJPEGDownsampleMode.java new file mode 100644 index 00000000..1ea65173 --- /dev/null +++ b/shared/java/EncodeJPEGDownsampleMode.java @@ -0,0 +1,18 @@ +package io.github.humbleui.skija; + +public enum EncodeJPEGDownsampleMode { + /** + * Reduction by a factor of two in both the horizontal and vertical directions. + */ + DS_420, + + /** + * Reduction by a factor of two in the horizontal direction. + */ + DS_422, + + /** + * No downsampling. + */ + DS_444 +} diff --git a/shared/java/EncodeJPEGOptions.java b/shared/java/EncodeJPEGOptions.java index 9cdd7e91..6f09a030 100644 --- a/shared/java/EncodeJPEGOptions.java +++ b/shared/java/EncodeJPEGOptions.java @@ -6,36 +6,14 @@ @lombok.Data @With public class EncodeJPEGOptions { - public enum DownsampleMode { - /** - * Reduction by a factor of two in both the horizontal and vertical directions. - */ - DS_420, - - /** - * Reduction by a factor of two in the horizontal direction. - */ - DS_422, - - /** - * No downsampling. - */ - DS_444 - } - - public enum AlphaMode { - IGNORE, - BLEND_ON_BLACK - } - - public static final EncodeJPEGOptions DEFAULT = new EncodeJPEGOptions(100, DownsampleMode.DS_420, AlphaMode.IGNORE); + public static final EncodeJPEGOptions DEFAULT = new EncodeJPEGOptions(100, EncodeJPEGDownsampleMode.DS_420, EncodeJPEGAlphaMode.IGNORE); @ApiStatus.Internal public final int _quality; @ApiStatus.Internal - public final DownsampleMode _downsampleMode; + public final EncodeJPEGDownsampleMode _downsampleMode; @ApiStatus.Internal - public final AlphaMode _alphaMode; + public final EncodeJPEGAlphaMode _alphaMode; } diff --git a/shared/java/EncodePNGFilterFlag.java b/shared/java/EncodePNGFilterFlag.java new file mode 100644 index 00000000..9032effc --- /dev/null +++ b/shared/java/EncodePNGFilterFlag.java @@ -0,0 +1,19 @@ +package io.github.humbleui.skija; + +import org.jetbrains.annotations.*; + +public enum EncodePNGFilterFlag { + ZERO (0x00), + NONE (0x08), + SUB (0x10), + UP (0x20), + AVG (0x40), + PAETH (0x80), + ALL (0x08 | 0x10 | 0x20 | 0x40 | 0x80); + + @ApiStatus.Internal public final int _value; + + EncodePNGFilterFlag(int value) { + this._value = value; + } +}; diff --git a/shared/java/EncodePNGOptions.java b/shared/java/EncodePNGOptions.java index cf2d8c11..235d970d 100644 --- a/shared/java/EncodePNGOptions.java +++ b/shared/java/EncodePNGOptions.java @@ -3,23 +3,7 @@ import org.jetbrains.annotations.*; public class EncodePNGOptions { - public enum FilterFlag { - ZERO (0x00), - NONE (0x08), - SUB (0x10), - UP (0x20), - AVG (0x40), - PAETH (0x80), - ALL (0x08 | 0x10 | 0x20 | 0x40 | 0x80); - - @ApiStatus.Internal public final int _value; - - FilterFlag(int value) { - this._value = value; - } - }; - - public static final EncodePNGOptions DEFAULT = new EncodePNGOptions(FilterFlag.ALL._value, 6); + public static final EncodePNGOptions DEFAULT = new EncodePNGOptions(EncodePNGFilterFlag.ALL._value, 6); @ApiStatus.Internal public final int _flags; @@ -33,9 +17,9 @@ public EncodePNGOptions(int flags, int level) { _zlibLevel = level; } - public EncodePNGOptions withFlags(FilterFlag... flags) { + public EncodePNGOptions withFlags(EncodePNGFilterFlag... flags) { int _flags = 0; - for (FilterFlag flag: flags) { + for (EncodePNGFilterFlag flag: flags) { _flags = _flags | flag._value; } return new EncodePNGOptions(_flags, _zlibLevel); diff --git a/shared/java/EncodeWEBPCompressionMode.java b/shared/java/EncodeWEBPCompressionMode.java new file mode 100644 index 00000000..39cdc583 --- /dev/null +++ b/shared/java/EncodeWEBPCompressionMode.java @@ -0,0 +1,8 @@ +package io.github.humbleui.skija; + +import org.jetbrains.annotations.*; + +public enum EncodeWEBPCompressionMode { + LOSSY, + LOSSLESS +} diff --git a/shared/java/EncodeWEBPOptions.java b/shared/java/EncodeWEBPOptions.java index cf1f3563..9beec2c5 100644 --- a/shared/java/EncodeWEBPOptions.java +++ b/shared/java/EncodeWEBPOptions.java @@ -6,21 +6,16 @@ @lombok.Data @With public class EncodeWEBPOptions { - public enum CompressionMode { - LOSSY, - LOSSLESS - } - - public static final EncodeWEBPOptions DEFAULT = new EncodeWEBPOptions(CompressionMode.LOSSY, 100f); + public static final EncodeWEBPOptions DEFAULT = new EncodeWEBPOptions(EncodeWEBPCompressionMode.LOSSY, 100f); @ApiStatus.Internal - public final CompressionMode _compressionMode; + public final EncodeWEBPCompressionMode _compressionMode; @ApiStatus.Internal public final float _quality; @ApiStatus.Internal - public EncodeWEBPOptions(CompressionMode compression, float quality) { + public EncodeWEBPOptions(EncodeWEBPCompressionMode compression, float quality) { _compressionMode = compression; _quality = quality; } diff --git a/shared/java/EncodedImageFormat.java b/shared/java/EncodedImageFormat.java index b12f722c..fde82150 100644 --- a/shared/java/EncodedImageFormat.java +++ b/shared/java/EncodedImageFormat.java @@ -2,6 +2,10 @@ import org.jetbrains.annotations.*; +/** + * @deprecated - use {@link EncoderJPEG}, {@link EncoderPNG} or {@link EncoderWEBP} directly + */ +@Deprecated public enum EncodedImageFormat { BMP, GIF, diff --git a/shared/java/EncoderJPEG.java b/shared/java/EncoderJPEG.java new file mode 100644 index 00000000..074d0ffd --- /dev/null +++ b/shared/java/EncoderJPEG.java @@ -0,0 +1,55 @@ +package io.github.humbleui.skija; + +import io.github.humbleui.skija.impl.*; +import org.jetbrains.annotations.*; + +public class EncoderJPEG { + /** + * Encode the provided image and return the resulting bytes. + * + * @param image image to encode + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@NotNull Image image) { + return encode(null, image, EncodeJPEGOptions.DEFAULT); + } + + /** + * Encode the provided image and return the resulting bytes. + * + * @param image image to encode + * @param opts may be used to control the encoding behavior + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@NotNull Image image, @NotNull EncodeJPEGOptions opts) { + return encode(null, image, opts); + } + + /** + * Encode the provided image and return the resulting Data. + * + * @param ctx If the image was created as a texture-backed image on a GPU context, + * that ctx must be provided so the pixels can be read before being encoded. + * For raster-backed images, ctx can be null. + * @param image image to encode + * @param opts may be used to control the encoding behavior + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@Nullable DirectContext ctx, @NotNull Image image, @NotNull EncodeJPEGOptions opts) { + try { + assert image != null : "Can’t EncoderJPEG::encode with image == null"; + assert opts != null : "Can’t EncoderJPEG::encode with opts == null"; + Stats.onNativeCall(); + long ptr = _nEncode(Native.getPtr(ctx), Native.getPtr(image), opts._quality, opts._downsampleMode.ordinal(), opts._alphaMode.ordinal()); + return ptr == 0 ? null : new Data(ptr); + } finally { + ReferenceUtil.reachabilityFence(ctx); + ReferenceUtil.reachabilityFence(image); + } + } + + @ApiStatus.Internal public static native long _nEncode(long ctxPtr, long imagePtr, int quality, int downsampleMode, int alphaMode); +} diff --git a/shared/java/EncoderPNG.java b/shared/java/EncoderPNG.java new file mode 100644 index 00000000..452dba69 --- /dev/null +++ b/shared/java/EncoderPNG.java @@ -0,0 +1,55 @@ +package io.github.humbleui.skija; + +import io.github.humbleui.skija.impl.*; +import org.jetbrains.annotations.*; + +public class EncoderPNG { + /** + * Encode the provided image and return the resulting bytes. + * + * @param image image to encode + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@NotNull Image image) { + return encode(null, image, EncodePNGOptions.DEFAULT); + } + + /** + * Encode the provided image and return the resulting bytes. + * + * @param image image to encode + * @param opts may be used to control the encoding behavior + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@NotNull Image image, @NotNull EncodePNGOptions opts) { + return encode(null, image, opts); + } + + /** + * Encode the provided image and return the resulting Data. + * + * @param ctx If the image was created as a texture-backed image on a GPU context, + * that ctx must be provided so the pixels can be read before being encoded. + * For raster-backed images, ctx can be null. + * @param image image to encode + * @param opts may be used to control the encoding behavior + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@Nullable DirectContext ctx, @NotNull Image image, @NotNull EncodePNGOptions opts) { + try { + assert image != null : "Can’t EncoderPNG::encode with image == null"; + assert opts != null : "Can’t EncoderPNG::encode with opts == null"; + Stats.onNativeCall(); + long ptr = _nEncode(Native.getPtr(ctx), Native.getPtr(image), opts._flags, opts._zlibLevel); + return ptr == 0 ? null : new Data(ptr); + } finally { + ReferenceUtil.reachabilityFence(ctx); + ReferenceUtil.reachabilityFence(image); + } + } + + @ApiStatus.Internal public static native long _nEncode(long ctxPtr, long imagePtr, int flags, int zlibLevel); +} diff --git a/shared/java/EncoderWEBP.java b/shared/java/EncoderWEBP.java new file mode 100644 index 00000000..e0075a8b --- /dev/null +++ b/shared/java/EncoderWEBP.java @@ -0,0 +1,55 @@ +package io.github.humbleui.skija; + +import io.github.humbleui.skija.impl.*; +import org.jetbrains.annotations.*; + +public class EncoderWEBP { + /** + * Encode the provided image and return the resulting bytes. + * + * @param image image to encode + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@NotNull Image image) { + return encode(null, image, EncodeWEBPOptions.DEFAULT); + } + + /** + * Encode the provided image and return the resulting bytes. + * + * @param image image to encode + * @param opts may be used to control the encoding behavior + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@NotNull Image image, @NotNull EncodeWEBPOptions opts) { + return encode(null, image, opts); + } + + /** + * Encode the provided image and return the resulting Data. + * + * @param ctx If the image was created as a texture-backed image on a GPU context, + * that ctx must be provided so the pixels can be read before being encoded. + * For raster-backed images, ctx can be null. + * @param image image to encode + * @param opts may be used to control the encoding behavior + * @return nullptr if the pixels could not be read or encoding otherwise fails. + */ + @Nullable + public static Data encode(@Nullable DirectContext ctx, @NotNull Image image, @NotNull EncodeWEBPOptions opts) { + try { + assert image != null : "Can’t EncoderWEBP::encode with image == null"; + assert opts != null : "Can’t EncoderWEBP::encode with opts == null"; + Stats.onNativeCall(); + long ptr = _nEncode(Native.getPtr(ctx), Native.getPtr(image), opts._compressionMode.ordinal(), opts._quality); + return ptr == 0 ? null : new Data(ptr); + } finally { + ReferenceUtil.reachabilityFence(ctx); + ReferenceUtil.reachabilityFence(image); + } + } + + @ApiStatus.Internal public static native long _nEncode(long ctxPtr, long imagePtr, int compressionMode, float quality); +} diff --git a/shared/java/Image.java b/shared/java/Image.java index 65b950c1..aaeab356 100644 --- a/shared/java/Image.java +++ b/shared/java/Image.java @@ -15,6 +15,14 @@ public Image(long ptr) { super(ptr); } + /** + * @deprecated - use {@link #makeRasterFromBytes(ImageInfo, byte[], long)} + */ + @Deprecated + public static Image makeRaster(ImageInfo imageInfo, byte[] bytes, long rowBytes) { + return makeRasterFromBytes(imageInfo, bytes, rowBytes); + } + /** *

Creates Image from pixels.

* @@ -34,16 +42,16 @@ public Image(long ptr) { * * @see https://fiddle.skia.org/c/@Image_MakeRasterCopy */ - public static Image makeRaster(ImageInfo imageInfo, byte[] bytes, long rowBytes) { + public static Image makeRasterFromBytes(ImageInfo imageInfo, byte[] bytes, long rowBytes) { try { Stats.onNativeCall(); - long ptr = _nMakeRaster(imageInfo._width, - imageInfo._height, - imageInfo._colorInfo._colorType.ordinal(), - imageInfo._colorInfo._alphaType.ordinal(), - Native.getPtr(imageInfo._colorInfo._colorSpace), - bytes, - rowBytes); + long ptr = _nMakeRasterFromBytes(imageInfo._width, + imageInfo._height, + imageInfo._colorInfo._colorType.ordinal(), + imageInfo._colorInfo._alphaType.ordinal(), + Native.getPtr(imageInfo._colorInfo._colorSpace), + bytes, + rowBytes); if (ptr == 0) throw new RuntimeException("Failed to makeRaster " + imageInfo + " " + bytes + " " + rowBytes); return new Image(ptr); @@ -52,6 +60,14 @@ public static Image makeRaster(ImageInfo imageInfo, byte[] bytes, long rowBytes) } } + /** + * @deprecated - use {@link #makeRasterFromData(ImageInfo, Data, long)} + */ + @Deprecated + public static Image makeRaster(ImageInfo imageInfo, Data data, long rowBytes) { + return makeRasterFromData(imageInfo, data, rowBytes); + } + /** *

Creates Image from pixels.

* @@ -69,16 +85,16 @@ public static Image makeRaster(ImageInfo imageInfo, byte[] bytes, long rowBytes) * @param rowBytes how many bytes in a row * @return Image */ - public static Image makeRaster(ImageInfo imageInfo, Data data, long rowBytes) { + public static Image makeRasterFromData(ImageInfo imageInfo, Data data, long rowBytes) { try { Stats.onNativeCall(); - long ptr = _nMakeRasterData(imageInfo._width, - imageInfo._height, - imageInfo._colorInfo._colorType.ordinal(), - imageInfo._colorInfo._alphaType.ordinal(), - Native.getPtr(imageInfo._colorInfo._colorSpace), - Native.getPtr(data), - rowBytes); + long ptr = _nMakeRasterFromData(imageInfo._width, + imageInfo._height, + imageInfo._colorInfo._colorType.ordinal(), + imageInfo._colorInfo._alphaType.ordinal(), + Native.getPtr(imageInfo._colorInfo._colorSpace), + Native.getPtr(data), + rowBytes); if (ptr == 0) throw new RuntimeException("Failed to makeRaster " + imageInfo + " " + data + " " + rowBytes); return new Image(ptr); @@ -88,6 +104,14 @@ public static Image makeRaster(ImageInfo imageInfo, Data data, long rowBytes) { } } + /** + * @deprecated - use {@link #makeRasterFromBitmap(Bitmap)} + */ + @Deprecated + public static Image makeFromBitmap(@NotNull Bitmap bitmap) { + return makeRasterFromBitmap(bitmap); + } + /** *

Creates Image from bitmap, sharing or copying bitmap pixels. If the bitmap * is marked immutable, and its pixel memory is shareable, it may be shared @@ -108,11 +132,11 @@ public static Image makeRaster(ImageInfo imageInfo, Data data, long rowBytes) { * @see https://fiddle.skia.org/c/@Image_MakeFromBitmap */ @NotNull @Contract("_ -> new") - public static Image makeFromBitmap(@NotNull Bitmap bitmap) { + public static Image makeRasterFromBitmap(@NotNull Bitmap bitmap) { try { assert bitmap != null : "Can’t makeFromBitmap with bitmap == null"; Stats.onNativeCall(); - long ptr = _nMakeFromBitmap(Native.getPtr(bitmap)); + long ptr = _nMakeRasterFromBitmap(Native.getPtr(bitmap)); if (ptr == 0) throw new RuntimeException("Failed to Image::makeFromBitmap " + bitmap); return new Image(ptr); @@ -121,12 +145,20 @@ public static Image makeFromBitmap(@NotNull Bitmap bitmap) { } } - @NotNull @Contract("_ -> new") + /** + * @deprecated - use {@link #makeRasterFromPixmap(Pixmap)} + */ + @Deprecated public static Image makeFromPixmap(@NotNull Pixmap pixmap) { + return makeRasterFromPixmap(pixmap); + } + + @NotNull @Contract("_ -> new") + public static Image makeRasterFromPixmap(@NotNull Pixmap pixmap) { try { assert pixmap != null : "Can’t makeFromPixmap with pixmap == null"; Stats.onNativeCall(); - long ptr = _nMakeFromPixmap(Native.getPtr(pixmap)); + long ptr = _nMakeRasterFromPixmap(Native.getPtr(pixmap)); if (ptr == 0) throw new RuntimeException("Failed to Image::makeFromRaster " + pixmap); return new Image(ptr); @@ -135,10 +167,18 @@ public static Image makeFromPixmap(@NotNull Pixmap pixmap) { } } - @NotNull @Contract("_ -> new") + /** + * @deprecated - use {@link #makeDeferredFromEncodedBytes(bytes)} + */ + @Deprecated public static Image makeFromEncoded(byte[] bytes) { + return makeDeferredFromEncodedBytes(bytes); + } + + @NotNull @Contract("_ -> new") + public static Image makeDeferredFromEncodedBytes(byte[] bytes) { Stats.onNativeCall(); - long ptr = _nMakeFromEncoded(bytes); + long ptr = _nMakeDeferredFromEncodedBytes(bytes); if (ptr == 0) throw new IllegalArgumentException("Failed to Image::makeFromEncoded"); return new Image(ptr); @@ -168,131 +208,35 @@ public ImageInfo getImageInfo() { } /** - * Encode the provided image and return the resulting bytes. - * - * @return nullptr if the pixels could not be read or encoding otherwise fails. - */ - @Nullable - public Data encodePNG() { - return encodePNG(null, EncodePNGOptions.DEFAULT); - } - - /** - * Encode the provided image and return the resulting bytes. - * - * @param opts may be used to control the encoding behavior - * @return nullptr if the pixels could not be read or encoding otherwise fails. - */ - @Nullable - public Data encodePNG(@NotNull EncodePNGOptions opts) { - return encodePNG(null, opts); - } - - /** - * Encode the provided image and return the resulting Data. - * - * @param ctx If the image was created as a texture-backed image on a GPU context, - * that ctx must be provided so the pixels can be read before being encoded. - * For raster-backed images, ctx can be null. - * @param opts may be used to control the encoding behavior - * @return nullptr if the pixels could not be read or encoding otherwise fails. - */ - @Nullable - public Data encodePNG(@Nullable DirectContext ctx, @NotNull EncodePNGOptions opts) { - try { - assert opts != null : "Can’t encodePNG with opts == null"; - Stats.onNativeCall(); - long ptr = _nEncodePNG(_ptr, Native.getPtr(ctx), opts._flags, opts._zlibLevel); - return ptr == 0 ? null : new Data(ptr); - } finally { - ReferenceUtil.reachabilityFence(ctx); - ReferenceUtil.reachabilityFence(this); - } - } - - /** - * Encode the provided image and return the resulting bytes. - * - * @return nullptr if the pixels could not be read or encoding otherwise fails. + * @deprecated - use {@link EncoderPNG#encode(Image)}, {@link EncoderJPEG#encode(Image)} or {@link EncoderWEBP#encode(Image)} */ - @Nullable - public Data encodeJPEG() { - return encodeJPEG(null, EncodeJPEGOptions.DEFAULT); + @Deprecated + public Data encodeToData() { + return encodeToData(EncodedImageFormat.PNG, 100); } /** - * Encode the provided image and return the resulting bytes. - * - * @param opts may be used to control the encoding behavior - * @return nullptr if the pixels could not be read or encoding otherwise fails. + * @deprecated - use {@link EncoderPNG#encode(Image)}, {@link EncoderJPEG#encode(Image)} or {@link EncoderWEBP#encode(Image)} */ - @Nullable - public Data encodeJPEG(@NotNull EncodeJPEGOptions opts) { - return encodeJPEG(null, opts); + @Deprecated + public Data encodeToData(EncodedImageFormat format) { + return encodeToData(format, 100); } /** - * Encode the provided image and return the resulting Data. - * - * @param ctx If the image was created as a texture-backed image on a GPU context, - * that ctx must be provided so the pixels can be read before being encoded. - * For raster-backed images, ctx can be null. - * @param opts may be used to control the encoding behavior - * @return nullptr if the pixels could not be read or encoding otherwise fails. + * @deprecated - use {@link EncoderPNG#encode(Image, EncodePNGOptions)}, {@link EncoderJPEG#encode(Image, EncodeJPEGOptions)} or {@link EncoderWEBP#encode(Image, EncodeWEBPOptions)} */ - @Nullable - public Data encodeJPEG(@Nullable DirectContext ctx, @NotNull EncodeJPEGOptions opts) { - try { - assert opts != null : "Can’t encodeJPEG with opts == null"; - Stats.onNativeCall(); - long ptr = _nEncodeJPEG(_ptr, Native.getPtr(ctx), opts._quality, opts._downsampleMode.ordinal(), opts._alphaMode.ordinal()); - return ptr == 0 ? null : new Data(ptr); - } finally { - ReferenceUtil.reachabilityFence(ctx); - ReferenceUtil.reachabilityFence(this); - } - } - - /** - * Encode the provided image and return the resulting bytes. - * - * @return nullptr if the pixels could not be read or encoding otherwise fails. - */ - @Nullable - public Data encodeWEBP() { - return encodeWEBP(null, EncodeWEBPOptions.DEFAULT); - } - - /** - * Encode the provided image and return the resulting bytes. - * - * @param opts may be used to control the encoding behavior - * @return nullptr if the pixels could not be read or encoding otherwise fails. - */ - @Nullable - public Data encodeWEBP(@NotNull EncodeWEBPOptions opts) { - return encodeWEBP(null, opts); - } - - /** - * Encode the provided image and return the resulting Data. - * - * @param ctx If the image was created as a texture-backed image on a GPU context, - * that ctx must be provided so the pixels can be read before being encoded. - * For raster-backed images, ctx can be null. - * @param opts may be used to control the encoding behavior - * @return nullptr if the pixels could not be read or encoding otherwise fails. - */ - @Nullable - public Data encodeWEBP(@Nullable DirectContext ctx, @NotNull EncodeWEBPOptions opts) { - try { - assert opts != null : "Can’t encodeWEBP with opts == null"; - Stats.onNativeCall(); - long ptr = _nEncodeWEBP(_ptr, Native.getPtr(ctx), opts._compressionMode.ordinal(), opts._quality); - return ptr == 0 ? null : new Data(ptr); - } finally { - ReferenceUtil.reachabilityFence(ctx); - ReferenceUtil.reachabilityFence(this); + @Deprecated + public Data encodeToData(EncodedImageFormat format, int quality) { + switch(format) { + case PNG: + return EncoderPNG.encode(this); + case JPEG: + return EncoderJPEG.encode(this, EncodeJPEGOptions.DEFAULT.withQuality(quality)); + case WEBP: + return EncoderWEBP.encode(this, EncodeWEBPOptions.DEFAULT.withQuality(quality)); + default: + throw new IllegalArgumentException("Format " + format + " is not supported"); } } @@ -438,15 +382,12 @@ public boolean scalePixels(@NotNull Pixmap dst, SamplingMode samplingMode, boole } } - @ApiStatus.Internal public static native long _nMakeRaster(int width, int height, int colorType, int alphaType, long colorSpacePtr, byte[] pixels, long rowBytes); - @ApiStatus.Internal public static native long _nMakeRasterData(int width, int height, int colorType, int alphaType, long colorSpacePtr, long dataPtr, long rowBytes); - @ApiStatus.Internal public static native long _nMakeFromBitmap(long bitmapPtr); - @ApiStatus.Internal public static native long _nMakeFromPixmap(long pixmapPtr); - @ApiStatus.Internal public static native long _nMakeFromEncoded(byte[] bytes); + @ApiStatus.Internal public static native long _nMakeRasterFromBytes(int width, int height, int colorType, int alphaType, long colorSpacePtr, byte[] pixels, long rowBytes); + @ApiStatus.Internal public static native long _nMakeRasterFromData(int width, int height, int colorType, int alphaType, long colorSpacePtr, long dataPtr, long rowBytes); + @ApiStatus.Internal public static native long _nMakeRasterFromBitmap(long bitmapPtr); + @ApiStatus.Internal public static native long _nMakeRasterFromPixmap(long pixmapPtr); + @ApiStatus.Internal public static native long _nMakeDeferredFromEncodedBytes(byte[] bytes); @ApiStatus.Internal public static native ImageInfo _nGetImageInfo(long ptr); - @ApiStatus.Internal public static native long _nEncodePNG(long ptr, long ctxPtr, int flags, int zlibLevel); - @ApiStatus.Internal public static native long _nEncodeJPEG(long ptr, long ctxPtr, int quality, int downsampleMode, int alphaMode); - @ApiStatus.Internal public static native long _nEncodeWEBP(long ptr, long ctxPtr, int compressionMode, float quality); @ApiStatus.Internal public static native long _nMakeShader(long ptr, int tmx, int tmy, long samplingMode, float[] localMatrix); @ApiStatus.Internal public static native ByteBuffer _nPeekPixels(long ptr); @ApiStatus.Internal public static native boolean _nPeekPixelsToPixmap(long ptr, long pixmapPtr); diff --git a/tests/java/ImageTest.java b/tests/java/ImageTest.java index 0f4265d8..b4b4c48f 100644 --- a/tests/java/ImageTest.java +++ b/tests/java/ImageTest.java @@ -4,13 +4,9 @@ import java.io.*; import java.nio.file.*; +import java.nio.file.Path; -import io.github.humbleui.skija.EncodeJPEGOptions; -import io.github.humbleui.skija.EncodePNGOptions; -import io.github.humbleui.skija.EncodeWEBPOptions; -import io.github.humbleui.skija.Paint; -import io.github.humbleui.skija.Surface; -import io.github.humbleui.skija.Path; +import io.github.humbleui.skija.*; import io.github.humbleui.skija.test.runner.*; public class ImageTest implements Executable { @@ -18,19 +14,20 @@ public class ImageTest implements Executable { public void execute() throws Exception { try (var surface = Surface.makeRasterN32Premul(100, 100); var paint = new Paint().setColor(0xFFFF0000); - var path = new Path().moveTo(20, 80).lineTo(50, 20).lineTo(80, 80).closePath();) + var path = new io.github.humbleui.skija.Path().moveTo(20, 80).lineTo(50, 20).lineTo(80, 80).closePath();) { var canvas = surface.getCanvas(); canvas.drawPath(path, paint); try (var image = surface.makeImageSnapshot()) { - new File("target/tests/ImageTest/").mkdirs(); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_default.png"), image.encodePNG().getBytes()); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_default_none_1.png"), image.encodePNG(EncodePNGOptions.DEFAULT.withFlags(EncodePNGOptions.FilterFlag.NONE).withZlibLevel(1)).getBytes()); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_jpeg_default.jpeg"), image.encodeJPEG().getBytes()); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_jpeg_50.jpeg"), image.encodeJPEG(EncodeJPEGOptions.DEFAULT.withQuality(50)).getBytes()); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_webp_default.webp"), image.encodeWEBP().getBytes()); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_webp_50.webp"), image.encodeWEBP(EncodeWEBPOptions.DEFAULT.withQuality(50)).getBytes()); - Files.write(java.nio.file.Path.of("target/tests/ImageTest/polygon_webp_lossless.webp"), image.encodeWEBP(EncodeWEBPOptions.DEFAULT.withCompressionMode(EncodeWEBPOptions.CompressionMode.LOSSLESS)).getBytes()); + var dir = "target/tests/ImageTest/"; + new File(dir).mkdirs(); + Files.write(Path.of(dir, "polygon_default.png"), EncoderPNG.encode(image).getBytes()); + Files.write(Path.of(dir, "polygon_default_none_1.png"), EncoderPNG.encode(image, EncodePNGOptions.DEFAULT.withFlags(EncodePNGFilterFlag.NONE).withZlibLevel(1)).getBytes()); + Files.write(Path.of(dir, "polygon_jpeg_default.jpeg"), EncoderJPEG.encode(image).getBytes()); + Files.write(Path.of(dir, "polygon_jpeg_50.jpeg"), EncoderJPEG.encode(image, EncodeJPEGOptions.DEFAULT.withQuality(50)).getBytes()); + Files.write(Path.of(dir, "polygon_webp_default.webp"), EncoderWEBP.encode(image).getBytes()); + Files.write(Path.of(dir, "polygon_webp_50.webp"), EncoderWEBP.encode(image,EncodeWEBPOptions.DEFAULT.withQuality(50)).getBytes()); + Files.write(Path.of(dir, "polygon_webp_lossless.webp"), EncoderWEBP.encode(image,EncodeWEBPOptions.DEFAULT.withCompressionMode(EncodeWEBPCompressionMode.LOSSLESS)).getBytes()); } } }