Skip to content

Commit

Permalink
Reimplement the Canvas transformation functions via the corresponding…
Browse files Browse the repository at this point in the history
… Skia functions, rather than by concatenating with a transformation matrix. (#724)
  • Loading branch information
m-sasha committed Jun 20, 2023
1 parent 697d489 commit 1c90124
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 9 deletions.
44 changes: 37 additions & 7 deletions skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1080,27 +1080,47 @@ open class Canvas internal constructor(ptr: NativePointer, managed: Boolean, int
}

fun translate(dx: Float, dy: Float): Canvas {
return concat(Matrix33.makeTranslate(dx, dy))
interopScope {
Stats.onNativeCall()
_nTranslate(_ptr, dx, dy)
}
return this
}

fun scale(sx: Float, sy: Float): Canvas {
return concat(Matrix33.makeScale(sx, sy))
interopScope {
Stats.onNativeCall()
_nScale(_ptr, sx, sy)
}
return this
}

/**
* @param deg angle in degrees
* @return this
*/
fun rotate(deg: Float): Canvas {
return concat(Matrix33.makeRotate(deg))
interopScope {
Stats.onNativeCall()
_nRotate(_ptr, deg, 0f, 0f)
}
return this
}

fun rotate(deg: Float, x: Float, y: Float): Canvas {
return concat(Matrix33.makeRotate(deg, x, y))
interopScope {
Stats.onNativeCall()
_nRotate(_ptr, deg, x, y)
}
return this
}

fun skew(sx: Float, sy: Float): Canvas {
return concat(Matrix33.makeSkew(sx, sy))
interopScope {
Stats.onNativeCall()
_nSkew(_ptr, sx, sy)
}
return this
}

fun concat(matrix: Matrix33): Canvas {
Expand Down Expand Up @@ -1542,14 +1562,24 @@ private external fun _nClipPath(ptr: NativePointer, nativePath: NativePointer, m
@ExternalSymbolName("org_jetbrains_skia_Canvas__1nClipRegion")
private external fun _nClipRegion(ptr: NativePointer, nativeRegion: NativePointer, mode: Int)

@ExternalSymbolName("org_jetbrains_skia_Canvas__1nTranslate")
private external fun _nTranslate(ptr: NativePointer, dx: Float, dy: Float)

@ExternalSymbolName("org_jetbrains_skia_Canvas__1nScale")
private external fun _nScale(ptr: NativePointer, sx: Float, sy: Float)

@ExternalSymbolName("org_jetbrains_skia_Canvas__1nRotate")
private external fun _nRotate(ptr: NativePointer, deg: Float, x: Float, y: Float)

@ExternalSymbolName("org_jetbrains_skia_Canvas__1nSkew")
private external fun _nSkew(ptr: NativePointer, sx: Float, sy: Float)

@ExternalSymbolName("org_jetbrains_skia_Canvas__1nConcat")
private external fun _nConcat(ptr: NativePointer, matrix: InteropPointer)


@ExternalSymbolName("org_jetbrains_skia_Canvas__1nConcat44")
private external fun _nConcat44(ptr: NativePointer, matrix: InteropPointer)


@ExternalSymbolName("org_jetbrains_skia_Canvas__1nReadPixels")
private external fun _nReadPixels(ptr: NativePointer, bitmapPtr: NativePointer, srcX: Int, srcY: Int): Boolean

Expand Down
106 changes: 104 additions & 2 deletions skiko/src/commonTest/kotlin/org/jetbrains/skia/CanvasTest.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
@file:Suppress("RemoveRedundantCallsOfConversionMethods")

package org.jetbrains.skia

import org.jetbrains.skia.tests.makeFromResource
import org.jetbrains.skia.util.assertContentSame
import org.jetbrains.skia.util.imageFromIntArray
import org.jetbrains.skiko.tests.SkipJsTarget
import org.jetbrains.skiko.tests.SkipNativeTarget
import org.jetbrains.skiko.tests.runTest
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertTrue


class CanvasTest {

@Test
fun drawVertices() {
val surface = Surface.makeRasterN32Premul(8, 8)
Expand Down Expand Up @@ -189,4 +191,104 @@ class CanvasTest {

assertContentSame(expected = expected, got = surface.makeImageSnapshot(), sensitivity = 0.25)
}


@Test
fun testTranslate() {
val surface = whiteSurface(4, 4)
surface.canvas.translate(2f, 2f)
surface.canvas.drawBlackPixel(0, 0)

surface.assertSingleBlackPixelAt(2, 2)
}


@Test
fun testScale() {
val surface = whiteSurface(4, 4)
surface.canvas.scale(2f, 2f)
surface.canvas.drawBlackPixel(1, 1)

surface.assertPixelsMatch(
IntArray(16){ index ->
when (index) {
10, 11, 14, 15 -> 0xff000000.toInt()
else -> 0xffffffff.toInt()
}
}
)
}


@Test
fun testRotate() {
val surface = whiteSurface(4, 4)
surface.canvas.rotate(deg = 90f)
surface.canvas.drawBlackPixel(3, -1)

surface.assertSingleBlackPixelAt(0, 3)
}


@Test
fun testRotateXY() {
val surface = whiteSurface(4, 4)
surface.canvas.rotate(deg = 90f, x = 2f, y=2f)
surface.canvas.drawBlackPixel(0, 0)

surface.assertSingleBlackPixelAt(3, 0)
}


@Test
fun testSkew() {
val surface = whiteSurface(4, 4)
surface.canvas.skew(sx = 0.5f, sy = 0f)
surface.canvas.drawBlackPixel(0, 2)

surface.assertPixelsMatch(
IntArray(16){ index ->
when (index) {
// Skewing skews the shape of the pixel itself, so it becomes a parallelogram
9 -> 0xff3f3f3f.toInt()
10 -> 0xffbfbfbf.toInt()
else -> 0xffffffff.toInt()
}
}
)
}


private fun whiteSurface(width: Int, height: Int): Surface {
val surface = Surface.makeRasterN32Premul(width, height)
val white = Paint().also { it.setARGB(255, 255, 255, 255) }
surface.canvas.drawRect(Rect(0f, 0f, width.toFloat(), height.toFloat()), white)
return surface
}


private fun Canvas.drawBlackPixel(x: Int, y: Int) {
val black = Paint().also { it.setARGB(255, 0, 0, 0) }
drawRect(Rect(x.toFloat(), y.toFloat(), (x + 1).toFloat(), (y + 1).toFloat()), black)
}


private fun Surface.assertPixelsMatch(pixArray: IntArray) {
assertContentSame(
expected = imageFromIntArray(
pixArray = pixArray,
imageWidth = width
),
got = makeImageSnapshot(),
sensitivity = 0.0
)
}


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

assertPixelsMatch(pixArray)
}
}
24 changes: 24 additions & 0 deletions skiko/src/jvmMain/cpp/common/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,30 @@ extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_CanvasKt__1nConcat44
canvas->concat(*m);
}

extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_CanvasKt__1nTranslate
(JNIEnv* env, jclass jclass, jlong ptr, jfloat dx, jfloat dy) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
canvas->translate(dx, dy);
}

extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_CanvasKt__1nScale
(JNIEnv* env, jclass jclass, jlong ptr, jfloat sx, jfloat sy) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
canvas->scale(sx, sy);
}

extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_CanvasKt__1nRotate
(JNIEnv* env, jclass jclass, jlong ptr, jfloat deg, jfloat x, jfloat y) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
canvas->rotate(deg, x, y);
}

extern "C" JNIEXPORT void JNICALL Java_org_jetbrains_skia_CanvasKt__1nSkew
(JNIEnv* env, jclass jclass, jlong ptr, jfloat sx, jfloat sy) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
canvas->skew(sx, sy);
}

extern "C" JNIEXPORT jint JNICALL Java_org_jetbrains_skia_CanvasKt__1nReadPixels
(JNIEnv* env, jclass jclass, jlong ptr, jlong bitmapPtr, jint srcX, jint srcY) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>(static_cast<uintptr_t>(ptr));
Expand Down
28 changes: 28 additions & 0 deletions skiko/src/nativeJsMain/cpp/Canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,34 @@ SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nConcat44
}


SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nTranslate
(KNativePointer canvasPtr, KFloat dx, KFloat dy) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
canvas->translate(dx, dy);
}


SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nScale
(KNativePointer canvasPtr, KFloat sx, KFloat sy) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
canvas->scale(sx, sy);
}


SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nRotate
(KNativePointer canvasPtr, KFloat deg, KFloat x, KFloat y) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
canvas->rotate(deg, x, y);
}


SKIKO_EXPORT void org_jetbrains_skia_Canvas__1nSkew
(KNativePointer canvasPtr, KFloat sx, KFloat sy) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((canvasPtr));
canvas->skew(sx, sy);
}


SKIKO_EXPORT KInt org_jetbrains_skia_Canvas__1nReadPixels
(KNativePointer ptr, KNativePointer bitmapPtr, KInt srcX, KInt srcY) {
SkCanvas* canvas = reinterpret_cast<SkCanvas*>((ptr));
Expand Down

0 comments on commit 1c90124

Please sign in to comment.