diff --git a/skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt b/skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt index 477100711..c0efa6ba7 100644 --- a/skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt +++ b/skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt @@ -1349,6 +1349,36 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int } } + fun saveLayer(layerRec: SaveLayerRec): Int { + return try { + Stats.onNativeCall() + if (layerRec.bounds != null) { + _nSaveLayerSaveLayerRecRect( + _ptr, + layerRec.bounds.left, + layerRec.bounds.top, + layerRec.bounds.right, + layerRec.bounds.bottom, + getPtr(layerRec.paint), + getPtr(layerRec.backdrop), + getPtr(layerRec.colorSpace), + layerRec.saveLayerFlags.mask + ) + } else { + _nSaveLayerSaveLayerRec( + _ptr, + getPtr(layerRec.paint), + getPtr(layerRec.backdrop), + getPtr(layerRec.colorSpace), + layerRec.saveLayerFlags.mask + ) + } + } finally { + reachabilityBarrier(this) + reachabilityBarrier(layerRec) + } + } + val saveCount: Int get() = try { Stats.onNativeCall() @@ -1369,6 +1399,39 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int return this } + class SaveLayerRec( + val bounds: Rect? = null, + val paint: Paint? = null, + val backdrop: ImageFilter? = null, + val colorSpace: ColorSpace? = null, + val saveLayerFlags: SaveLayerFlags = SaveLayerFlags() + ) + + enum class SaveLayerFlagsSet(val mask: Int) { + PreserveLCDText(1 shl 1), + InitWithPrevious(1 shl 2), + F16ColorType(1 shl 4) + } + + class SaveLayerFlags internal constructor(internal val mask: Int) { + constructor(vararg flagsSet: SaveLayerFlagsSet) : this(flagsSet.fold(0) { acc, flag -> acc or flag.mask }) + + operator fun contains(flag: SaveLayerFlagsSet): Boolean = (mask and flag.mask) != 0 + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as SaveLayerFlags + + return mask == other.mask + } + + override fun hashCode(): Int { + return mask + } + } + private object _FinalizerHolder { val PTR = Canvas_nGetFinalizer() } @@ -1645,6 +1708,29 @@ private external fun _nSaveLayerRect( paintPtr: NativePointer ): Int +@ExternalSymbolName("org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRec") +@ModuleImport("./skiko.mjs", "org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRec") +private external fun _nSaveLayerSaveLayerRec( + ptr: NativePointer, + paintPtr: NativePointer, + backdropImageFilterPtr: NativePointer, + colorSpacePtr: NativePointer, + saveLayerFlags: Int +): Int + +@ExternalSymbolName("org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect") +@ModuleImport("./skiko.mjs", "org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect") +private external fun _nSaveLayerSaveLayerRecRect( + ptr: NativePointer, + left: Float, + top: Float, + right: Float, + bottom: Float, + paintPtr: NativePointer, + backdropImageFilterPtr: NativePointer, + colorSpacePtr: NativePointer, + saveLayerFlags: Int +): Int @ExternalSymbolName("org_jetbrains_skia_Canvas__1nGetSaveCount") @ModuleImport("./skiko.mjs", "org_jetbrains_skia_Canvas__1nGetSaveCount") diff --git a/skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt b/skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt index 6aaedb3d1..dad51a6ff 100644 --- a/skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt +++ b/skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt @@ -99,7 +99,7 @@ class CanvasTest { fun drawString() = runTest { val surface = Surface.makeRasterN32Premul(100, 100) - val bytes = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!! + val bytes = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!! assertTrue { bytes.isNotEmpty() && bytes.all { it == 0.toByte() } } @@ -114,7 +114,7 @@ class CanvasTest { } ) - val bytes2 = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!! + val bytes2 = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!! assertTrue { bytes2.isNotEmpty() && bytes2.any { it != 0.toByte() } } @@ -210,7 +210,7 @@ class CanvasTest { surface.canvas.drawBlackPixel(1, 1) surface.assertPixelsMatch( - IntArray(16){ index -> + IntArray(16) { index -> when (index) { 10, 11, 14, 15 -> 0xff000000.toInt() else -> 0xffffffff.toInt() @@ -233,7 +233,7 @@ class CanvasTest { @Test fun testRotateXY() { val surface = whiteSurface(4, 4) - surface.canvas.rotate(deg = 90f, x = 2f, y=2f) + surface.canvas.rotate(deg = 90f, x = 2f, y = 2f) surface.canvas.drawBlackPixel(0, 0) surface.assertSingleBlackPixelAt(3, 0) @@ -247,7 +247,7 @@ class CanvasTest { surface.canvas.drawBlackPixel(0, 2) surface.assertPixelsMatch( - IntArray(16){ index -> + IntArray(16) { index -> when (index) { // Skewing skews the shape of the pixel itself, so it becomes a parallelogram 9 -> 0xff3f3f3f.toInt() @@ -258,6 +258,56 @@ class CanvasTest { ) } + @Test + fun testSaveLayerRecRect() { + val surface = whiteSurface(5, 5) + + surface.canvas.saveLayer( + Canvas.SaveLayerRec( + bounds = Rect(1f, 1f, 4f, 4f), + saveLayerFlags = Canvas.SaveLayerFlags(Canvas.SaveLayerFlagsSet.InitWithPrevious) + ) + ) + + val black = Paint().also { it.setARGB(255, 0, 0, 0) } + surface.canvas.drawRect(Rect(1f, 1f, 4f, 4f), black) + + surface.canvas.restore() + + surface.assertPixelsMatch( + intArrayOf( + Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, + Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE, + Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE, + Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE, + Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, + ) + ) + } + + + @Test + fun testSaveLayerRec() { + val surface = whiteSurface(5, 5) + + surface.canvas.saveLayer(Canvas.SaveLayerRec(saveLayerFlags = Canvas.SaveLayerFlags(Canvas.SaveLayerFlagsSet.InitWithPrevious))) + + val black = Paint().also { it.setARGB(255, 0, 0, 0) } + surface.canvas.drawRect(Rect(1f, 1f, 4f, 4f), black) + + surface.canvas.restore() + + surface.assertPixelsMatch( + intArrayOf( + Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, + Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE, + Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE, + Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE, + Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, + ) + ) + } + private fun whiteSurface(width: Int, height: Int): Surface { val surface = Surface.makeRasterN32Premul(width, height) @@ -286,8 +336,8 @@ class CanvasTest { private fun Surface.assertSingleBlackPixelAt(x: Int, y: Int) { - val pixArray = IntArray(width * height){ 0xffffffff.toInt() } - pixArray[y*width + x] = 0xff000000.toInt() + val pixArray = IntArray(width * height) { 0xffffffff.toInt() } + pixArray[y * width + x] = 0xff000000.toInt() assertPixelsMatch(pixArray) } diff --git a/skiko/src/jvmMain/cpp/common/Canvas.cc b/skiko/src/jvmMain/cpp/common/Canvas.cc index 2e03930b0..ab74d2fe3 100644 --- a/skiko/src/jvmMain/cpp/common/Canvas.cc +++ b/skiko/src/jvmMain/cpp/common/Canvas.cc @@ -335,6 +335,27 @@ extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nSaveLayerR return canvas->saveLayer(&bounds, paint); } +extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nSaveLayerSaveLayerRec + (JNIEnv* env, jclass jclass, jlong ptr, jlong paintPtr, jlong backdropImageFilterPtr, jlong colorSpacePtr, jint saveLayerFlags) { + SkCanvas* canvas = reinterpret_cast(static_cast(ptr)); + SkPaint* paint = reinterpret_cast(static_cast(paintPtr)); + SkImageFilter* backdrop = reinterpret_cast(backdropImageFilterPtr); + SkColorSpace* colorSpace = reinterpret_cast(colorSpacePtr); + + return canvas->saveLayer(SkCanvas::SaveLayerRec(nullptr, paint, backdrop, colorSpace, saveLayerFlags)); +} + +extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nSaveLayerSaveLayerRecRect + (JNIEnv* env, jclass jclass, jlong ptr, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintPtr, jlong backdropImageFilterPtr, jlong colorSpacePtr, jint saveLayerFlags) { + SkCanvas* canvas = reinterpret_cast(static_cast(ptr)); + SkRect bounds {left, top, right, bottom}; + SkPaint* paint = reinterpret_cast(static_cast(paintPtr)); + SkImageFilter* backdrop = reinterpret_cast(backdropImageFilterPtr); + SkColorSpace* colorSpace = reinterpret_cast(colorSpacePtr); + + return canvas->saveLayer(SkCanvas::SaveLayerRec(&bounds, paint, backdrop, colorSpace, saveLayerFlags)); +} + extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nGetSaveCount(JNIEnv* env, jclass jclass, jlong ptr) { return reinterpret_cast(static_cast(ptr))->getSaveCount(); } diff --git a/skiko/src/nativeJsMain/cpp/Canvas.cc b/skiko/src/nativeJsMain/cpp/Canvas.cc index ce7a20a27..83160e6c0 100644 --- a/skiko/src/nativeJsMain/cpp/Canvas.cc +++ b/skiko/src/nativeJsMain/cpp/Canvas.cc @@ -330,6 +330,27 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nSaveLayerRect return canvas->saveLayer(&bounds, paint); } +SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRec + (KNativePointer ptr, KNativePointer paintPtr, KNativePointer backdropImageFilterPtr, KNativePointer colorSpacePtr, KInt saveLayerFlags) { + SkCanvas* canvas = reinterpret_cast((ptr)); + SkPaint* paint = reinterpret_cast((paintPtr)); + SkImageFilter* backdrop = reinterpret_cast(backdropImageFilterPtr); + SkColorSpace* colorSpace = reinterpret_cast(colorSpacePtr); + + return canvas->saveLayer(SkCanvas::SaveLayerRec(nullptr, paint, backdrop, colorSpace, saveLayerFlags)); +} + +SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect + (KNativePointer ptr, KFloat left, KFloat top, KFloat right, KFloat bottom, KNativePointer paintPtr, KNativePointer backdropImageFilterPtr, KNativePointer colorSpacePtr, KInt saveLayerFlags) { + SkCanvas* canvas = reinterpret_cast((ptr)); + SkRect bounds {left, top, right, bottom}; + SkPaint* paint = reinterpret_cast((paintPtr)); + SkImageFilter* backdrop = reinterpret_cast(backdropImageFilterPtr); + SkColorSpace* colorSpace = reinterpret_cast(colorSpacePtr); + + return canvas->saveLayer(SkCanvas::SaveLayerRec(&bounds, paint, backdrop, colorSpace, saveLayerFlags)); +} + SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nGetSaveCount(KNativePointer ptr) { return reinterpret_cast((ptr))->getSaveCount(); }