Skip to content

Commit

Permalink
Add a new saveLayer method to Canvas (already exists in Skia) (#995)
Browse files Browse the repository at this point in the history
Co-authored-by: Artem Bobrov <[email protected]>
  • Loading branch information
aibobrov and Artem Bobrov authored Oct 18, 2024
1 parent 1bd3058 commit b73b374
Show file tree
Hide file tree
Showing 4 changed files with 185 additions and 7 deletions.
86 changes: 86 additions & 0 deletions skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
}
Expand Down Expand Up @@ -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")
Expand Down
64 changes: 57 additions & 7 deletions skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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() }
}
Expand All @@ -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() }
}
Expand Down Expand Up @@ -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()
Expand All @@ -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)
Expand All @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
21 changes: 21 additions & 0 deletions skiko/src/jvmMain/cpp/common/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkCanvas*>(static_cast<uintptr_t>(ptr));
SkPaint* paint = reinterpret_cast<SkPaint*>(static_cast<uintptr_t>(paintPtr));
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(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<SkCanvas*>(static_cast<uintptr_t>(ptr));
SkRect bounds {left, top, right, bottom};
SkPaint* paint = reinterpret_cast<SkPaint*>(static_cast<uintptr_t>(paintPtr));
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(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<SkCanvas*>(static_cast<uintptr_t>(ptr))->getSaveCount();
}
Expand Down
21 changes: 21 additions & 0 deletions skiko/src/nativeJsMain/cpp/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<SkCanvas*>((ptr));
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(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<SkCanvas*>((ptr));
SkRect bounds {left, top, right, bottom};
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(colorSpacePtr);

return canvas->saveLayer(SkCanvas::SaveLayerRec(&bounds, paint, backdrop, colorSpace, saveLayerFlags));
}

SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nGetSaveCount(KNativePointer ptr) {
return reinterpret_cast<SkCanvas*>((ptr))->getSaveCount();
}
Expand Down

0 comments on commit b73b374

Please sign in to comment.