Skip to content

Commit b73b374

Browse files
aibobrovArtem Bobrov
andauthored
Add a new saveLayer method to Canvas (already exists in Skia) (#995)
Co-authored-by: Artem Bobrov <[email protected]>
1 parent 1bd3058 commit b73b374

File tree

4 files changed

+185
-7
lines changed

4 files changed

+185
-7
lines changed

skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,36 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int
13491349
}
13501350
}
13511351

1352+
fun saveLayer(layerRec: SaveLayerRec): Int {
1353+
return try {
1354+
Stats.onNativeCall()
1355+
if (layerRec.bounds != null) {
1356+
_nSaveLayerSaveLayerRecRect(
1357+
_ptr,
1358+
layerRec.bounds.left,
1359+
layerRec.bounds.top,
1360+
layerRec.bounds.right,
1361+
layerRec.bounds.bottom,
1362+
getPtr(layerRec.paint),
1363+
getPtr(layerRec.backdrop),
1364+
getPtr(layerRec.colorSpace),
1365+
layerRec.saveLayerFlags.mask
1366+
)
1367+
} else {
1368+
_nSaveLayerSaveLayerRec(
1369+
_ptr,
1370+
getPtr(layerRec.paint),
1371+
getPtr(layerRec.backdrop),
1372+
getPtr(layerRec.colorSpace),
1373+
layerRec.saveLayerFlags.mask
1374+
)
1375+
}
1376+
} finally {
1377+
reachabilityBarrier(this)
1378+
reachabilityBarrier(layerRec)
1379+
}
1380+
}
1381+
13521382
val saveCount: Int
13531383
get() = try {
13541384
Stats.onNativeCall()
@@ -1369,6 +1399,39 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int
13691399
return this
13701400
}
13711401

1402+
class SaveLayerRec(
1403+
val bounds: Rect? = null,
1404+
val paint: Paint? = null,
1405+
val backdrop: ImageFilter? = null,
1406+
val colorSpace: ColorSpace? = null,
1407+
val saveLayerFlags: SaveLayerFlags = SaveLayerFlags()
1408+
)
1409+
1410+
enum class SaveLayerFlagsSet(val mask: Int) {
1411+
PreserveLCDText(1 shl 1),
1412+
InitWithPrevious(1 shl 2),
1413+
F16ColorType(1 shl 4)
1414+
}
1415+
1416+
class SaveLayerFlags internal constructor(internal val mask: Int) {
1417+
constructor(vararg flagsSet: SaveLayerFlagsSet) : this(flagsSet.fold(0) { acc, flag -> acc or flag.mask })
1418+
1419+
operator fun contains(flag: SaveLayerFlagsSet): Boolean = (mask and flag.mask) != 0
1420+
1421+
override fun equals(other: Any?): Boolean {
1422+
if (this === other) return true
1423+
if (other == null || this::class != other::class) return false
1424+
1425+
other as SaveLayerFlags
1426+
1427+
return mask == other.mask
1428+
}
1429+
1430+
override fun hashCode(): Int {
1431+
return mask
1432+
}
1433+
}
1434+
13721435
private object _FinalizerHolder {
13731436
val PTR = Canvas_nGetFinalizer()
13741437
}
@@ -1645,6 +1708,29 @@ private external fun _nSaveLayerRect(
16451708
paintPtr: NativePointer
16461709
): Int
16471710

1711+
@ExternalSymbolName("org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRec")
1712+
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRec")
1713+
private external fun _nSaveLayerSaveLayerRec(
1714+
ptr: NativePointer,
1715+
paintPtr: NativePointer,
1716+
backdropImageFilterPtr: NativePointer,
1717+
colorSpacePtr: NativePointer,
1718+
saveLayerFlags: Int
1719+
): Int
1720+
1721+
@ExternalSymbolName("org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect")
1722+
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect")
1723+
private external fun _nSaveLayerSaveLayerRecRect(
1724+
ptr: NativePointer,
1725+
left: Float,
1726+
top: Float,
1727+
right: Float,
1728+
bottom: Float,
1729+
paintPtr: NativePointer,
1730+
backdropImageFilterPtr: NativePointer,
1731+
colorSpacePtr: NativePointer,
1732+
saveLayerFlags: Int
1733+
): Int
16481734

16491735
@ExternalSymbolName("org_jetbrains_skia_Canvas__1nGetSaveCount")
16501736
@ModuleImport("./skiko.mjs", "org_jetbrains_skia_Canvas__1nGetSaveCount")

skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class CanvasTest {
9999
fun drawString() = runTest {
100100
val surface = Surface.makeRasterN32Premul(100, 100)
101101

102-
val bytes = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!!
102+
val bytes = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!!
103103
assertTrue {
104104
bytes.isNotEmpty() && bytes.all { it == 0.toByte() }
105105
}
@@ -114,7 +114,7 @@ class CanvasTest {
114114
}
115115
)
116116

117-
val bytes2 = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!!
117+
val bytes2 = Bitmap.makeFromImage(surface.makeImageSnapshot()).readPixels()!!
118118
assertTrue {
119119
bytes2.isNotEmpty() && bytes2.any { it != 0.toByte() }
120120
}
@@ -210,7 +210,7 @@ class CanvasTest {
210210
surface.canvas.drawBlackPixel(1, 1)
211211

212212
surface.assertPixelsMatch(
213-
IntArray(16){ index ->
213+
IntArray(16) { index ->
214214
when (index) {
215215
10, 11, 14, 15 -> 0xff000000.toInt()
216216
else -> 0xffffffff.toInt()
@@ -233,7 +233,7 @@ class CanvasTest {
233233
@Test
234234
fun testRotateXY() {
235235
val surface = whiteSurface(4, 4)
236-
surface.canvas.rotate(deg = 90f, x = 2f, y=2f)
236+
surface.canvas.rotate(deg = 90f, x = 2f, y = 2f)
237237
surface.canvas.drawBlackPixel(0, 0)
238238

239239
surface.assertSingleBlackPixelAt(3, 0)
@@ -247,7 +247,7 @@ class CanvasTest {
247247
surface.canvas.drawBlackPixel(0, 2)
248248

249249
surface.assertPixelsMatch(
250-
IntArray(16){ index ->
250+
IntArray(16) { index ->
251251
when (index) {
252252
// Skewing skews the shape of the pixel itself, so it becomes a parallelogram
253253
9 -> 0xff3f3f3f.toInt()
@@ -258,6 +258,56 @@ class CanvasTest {
258258
)
259259
}
260260

261+
@Test
262+
fun testSaveLayerRecRect() {
263+
val surface = whiteSurface(5, 5)
264+
265+
surface.canvas.saveLayer(
266+
Canvas.SaveLayerRec(
267+
bounds = Rect(1f, 1f, 4f, 4f),
268+
saveLayerFlags = Canvas.SaveLayerFlags(Canvas.SaveLayerFlagsSet.InitWithPrevious)
269+
)
270+
)
271+
272+
val black = Paint().also { it.setARGB(255, 0, 0, 0) }
273+
surface.canvas.drawRect(Rect(1f, 1f, 4f, 4f), black)
274+
275+
surface.canvas.restore()
276+
277+
surface.assertPixelsMatch(
278+
intArrayOf(
279+
Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE,
280+
Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE,
281+
Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE,
282+
Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE,
283+
Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE,
284+
)
285+
)
286+
}
287+
288+
289+
@Test
290+
fun testSaveLayerRec() {
291+
val surface = whiteSurface(5, 5)
292+
293+
surface.canvas.saveLayer(Canvas.SaveLayerRec(saveLayerFlags = Canvas.SaveLayerFlags(Canvas.SaveLayerFlagsSet.InitWithPrevious)))
294+
295+
val black = Paint().also { it.setARGB(255, 0, 0, 0) }
296+
surface.canvas.drawRect(Rect(1f, 1f, 4f, 4f), black)
297+
298+
surface.canvas.restore()
299+
300+
surface.assertPixelsMatch(
301+
intArrayOf(
302+
Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE,
303+
Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE,
304+
Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE,
305+
Color.WHITE, Color.BLACK, Color.BLACK, Color.BLACK, Color.WHITE,
306+
Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE, Color.WHITE,
307+
)
308+
)
309+
}
310+
261311

262312
private fun whiteSurface(width: Int, height: Int): Surface {
263313
val surface = Surface.makeRasterN32Premul(width, height)
@@ -286,8 +336,8 @@ class CanvasTest {
286336

287337

288338
private fun Surface.assertSingleBlackPixelAt(x: Int, y: Int) {
289-
val pixArray = IntArray(width * height){ 0xffffffff.toInt() }
290-
pixArray[y*width + x] = 0xff000000.toInt()
339+
val pixArray = IntArray(width * height) { 0xffffffff.toInt() }
340+
pixArray[y * width + x] = 0xff000000.toInt()
291341

292342
assertPixelsMatch(pixArray)
293343
}

skiko/src/jvmMain/cpp/common/Canvas.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,27 @@ extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nSaveLayerR
335335
return canvas->saveLayer(&bounds, paint);
336336
}
337337

338+
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nSaveLayerSaveLayerRec
339+
(JNIEnv* env, jclass jclass, jlong ptr, jlong paintPtr, jlong backdropImageFilterPtr, jlong colorSpacePtr, jint saveLayerFlags) {
340+
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
341+
SkPaint* paint = reinterpret_cast<SkPaint*>(static_cast<uintptr_t>(paintPtr));
342+
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
343+
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(colorSpacePtr);
344+
345+
return canvas->saveLayer(SkCanvas::SaveLayerRec(nullptr, paint, backdrop, colorSpace, saveLayerFlags));
346+
}
347+
348+
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nSaveLayerSaveLayerRecRect
349+
(JNIEnv* env, jclass jclass, jlong ptr, jfloat left, jfloat top, jfloat right, jfloat bottom, jlong paintPtr, jlong backdropImageFilterPtr, jlong colorSpacePtr, jint saveLayerFlags) {
350+
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
351+
SkRect bounds {left, top, right, bottom};
352+
SkPaint* paint = reinterpret_cast<SkPaint*>(static_cast<uintptr_t>(paintPtr));
353+
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
354+
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(colorSpacePtr);
355+
356+
return canvas->saveLayer(SkCanvas::SaveLayerRec(&bounds, paint, backdrop, colorSpace, saveLayerFlags));
357+
}
358+
338359
extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nGetSaveCount(JNIEnv* env, jclass jclass, jlong ptr) {
339360
return reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr))->getSaveCount();
340361
}

skiko/src/nativeJsMain/cpp/Canvas.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,27 @@ SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nSaveLayerRect
330330
return canvas->saveLayer(&bounds, paint);
331331
}
332332

333+
SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRec
334+
(KNativePointer ptr, KNativePointer paintPtr, KNativePointer backdropImageFilterPtr, KNativePointer colorSpacePtr, KInt saveLayerFlags) {
335+
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((ptr));
336+
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
337+
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
338+
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(colorSpacePtr);
339+
340+
return canvas->saveLayer(SkCanvas::SaveLayerRec(nullptr, paint, backdrop, colorSpace, saveLayerFlags));
341+
}
342+
343+
SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nSaveLayerSaveLayerRecRect
344+
(KNativePointer ptr, KFloat left, KFloat top, KFloat right, KFloat bottom, KNativePointer paintPtr, KNativePointer backdropImageFilterPtr, KNativePointer colorSpacePtr, KInt saveLayerFlags) {
345+
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((ptr));
346+
SkRect bounds {left, top, right, bottom};
347+
SkPaint* paint = reinterpret_cast<SkPaint*>((paintPtr));
348+
SkImageFilter* backdrop = reinterpret_cast<SkImageFilter*>(backdropImageFilterPtr);
349+
SkColorSpace* colorSpace = reinterpret_cast<SkColorSpace*>(colorSpacePtr);
350+
351+
return canvas->saveLayer(SkCanvas::SaveLayerRec(&bounds, paint, backdrop, colorSpace, saveLayerFlags));
352+
}
353+
333354
SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nGetSaveCount(KNativePointer ptr) {
334355
return reinterpret_cast<SkCanvas*>((ptr))->getSaveCount();
335356
}

0 commit comments

Comments
 (0)