Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplement the Canvas transformation functions #724

Merged
merged 1 commit into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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