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

Support vsync, refactor rendering, rendering tests #44

Merged
merged 13 commits into from
Jan 20, 2021
25 changes: 25 additions & 0 deletions .run/test.run.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="test" type="GradleRunConfiguration" factoryName="Gradle">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$/samples/SkijaInjectSample" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="taskDescriptions">
<list />
</option>
<option name="taskNames">
<list>
<option value="test" />
</list>
</option>
<option name="vmOptions" value="" />
</ExternalSystemSettings>
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<method v="2">
<option name="Gradle.BeforeRunTask" enabled="false" tasks="publishToMavenLocal" externalProjectPath="$PROJECT_DIR$/skiko" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>
18 changes: 17 additions & 1 deletion samples/SkijaInjectSample/build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.72'
id 'org.jetbrains.kotlin.jvm' version '1.4.21'
igordmn marked this conversation as resolved.
Show resolved Hide resolved
id 'application'
}

Expand Down Expand Up @@ -44,6 +44,7 @@ if (project.hasProperty('skiko.version')) {
dependencies {
implementation platform('org.jetbrains.kotlin:kotlin-bom')
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.4.1'
implementation "org.jetbrains.skiko:skiko-jvm-runtime-$target:$version"
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
Expand All @@ -52,3 +53,18 @@ dependencies {
application {
mainClassName = 'SkijaInjectSample.AppKt'
}

run {
systemProperty("skiko.fps.enabled", "true")
}

test {
systemProperty("skiko.test.screenshots.dir", new File(project.projectDir, "src/test/screenshots").absolutePath)

// Tests should be determined, so disable scaling.
igordmn marked this conversation as resolved.
Show resolved Hide resolved
// On MacOs we need the actual scale, otherwise we will have aliased screenshots because of scaling.
if (System.getProperty("os.name") != "Mac OS X") {
systemProperty("sun.java2d.dpiaware", "false")
systemProperty("sun.java2d.uiScale", "1")
}
}
66 changes: 27 additions & 39 deletions samples/SkijaInjectSample/src/main/kotlin/SkijaInjectSample/App.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,26 @@
package SkijaInjectSample

import org.jetbrains.skiko.SkiaWindow

import java.awt.event.MouseEvent
import javax.swing.WindowConstants
import javax.swing.event.MouseInputAdapter
import org.jetbrains.skija.*
import org.jetbrains.skiko.SkiaRenderer
import java.awt.event.MouseMotionAdapter
import kotlin.math.cos
import kotlin.math.sin
import org.jetbrains.skija.paragraph.FontCollection
import org.jetbrains.skija.paragraph.ParagraphBuilder
import org.jetbrains.skija.paragraph.ParagraphStyle
import org.jetbrains.skija.paragraph.TextStyle
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.KeyEvent
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.SkiaRenderer
import org.jetbrains.skiko.SkiaWindow
import java.awt.Toolkit
import javax.swing.JFrame
import javax.swing.JMenu
import javax.swing.JMenuBar
import javax.swing.JMenuItem
import javax.swing.JOptionPane
import javax.swing.KeyStroke
import java.awt.event.*
import javax.swing.*
import kotlin.math.cos
import kotlin.math.sin

fun main(args: Array<String>) {
createWindow("First window")
repeat(1) {
createWindow("window $it")
}
}

fun createWindow(title: String) {
fun createWindow(title: String) = SwingUtilities.invokeLater {
igordmn marked this conversation as resolved.
Show resolved Hide resolved
var mouseX = 0
var mouseY = 0

Expand Down Expand Up @@ -68,15 +59,14 @@ fun createWindow(title: String) {
val state = State()
state.text = title

window.layer.renderer = Renderer {
renderer, w, h -> displayScene(renderer, w, h, mouseX, mouseY, state)
window.layer.renderer = Renderer(window.layer) {
renderer, w, h, nanoTime -> displayScene(renderer, w, h, nanoTime, mouseX, mouseY, state)
}

window.layer.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(event: MouseEvent) {
mouseX = event.x
mouseY = event.y
window.display()
}
})

Expand All @@ -85,7 +75,10 @@ fun createWindow(title: String) {
window.setVisible(true)
}

class Renderer(val displayScene: (Renderer, Int, Int) -> Unit): SkiaRenderer {
class Renderer(
val layer: SkiaLayer,
val displayScene: (Renderer, Int, Int, Long) -> Unit
): SkiaRenderer {
val typeface = Typeface.makeFromFile("fonts/JetBrainsMono-Regular.ttf")
val font = Font(typeface, 40f)
val paint = Paint().apply {
Expand All @@ -96,18 +89,12 @@ class Renderer(val displayScene: (Renderer, Int, Int) -> Unit): SkiaRenderer {

var canvas: Canvas? = null

override fun onInit() {
}

override fun onDispose() {
}

override fun onReshape(width: Int, height: Int) {
}

override fun onRender(canvas: Canvas, width: Int, height: Int) {
override suspend fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
this.canvas = canvas
displayScene(this, width, height)
val contentScale = layer.contentScale
canvas.scale(contentScale, contentScale)
displayScene(this, (width / contentScale).toInt(), (height / contentScale).toInt(), nanoTime)
layer.needRedraw()
}
}

Expand All @@ -116,7 +103,10 @@ class State {
var text: String = "Hello Skija"
}

fun displayScene(renderer: Renderer, width: Int, height: Int, xpos: Int, ypos: Int, state: State) {
private val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.getDefault())

fun displayScene(renderer: Renderer, width: Int, height: Int, nanoTime: Long, xpos: Int, ypos: Int, state: State) {
val canvas = renderer.canvas!!
val watchFill = Paint().setColor(0xFFFFFFFF.toInt())
val watchStroke = Paint().setColor(0xFF000000.toInt()).setMode(PaintMode.STROKE).setStrokeWidth(1f)
Expand All @@ -140,7 +130,7 @@ fun displayScene(renderer: Renderer, width: Int, height: Int, xpos: Int, ypos: I
)
angle += (2.0 * Math.PI / 12.0).toFloat()
}
val time = System.currentTimeMillis() % 60000 +
val time = (nanoTime / 1E6) % 60000 +
(x.toFloat() / width * 5000).toLong() +
(y.toFloat() / width * 5000).toLong()

Expand All @@ -160,8 +150,6 @@ fun displayScene(renderer: Renderer, width: Int, height: Int, xpos: Int, ypos: I
val text = "${state.text} ${state.frame++}!"
canvas.drawString(text, xpos.toFloat(), ypos.toFloat(), renderer.font, renderer.paint)

val fontCollection = FontCollection()
.setDefaultFontManager(FontMgr.getDefault())
val style = ParagraphStyle()
val paragraph = ParagraphBuilder(style, fontCollection)
.pushStyle(TextStyle().setColor(0xFF000000.toInt()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package org.jetbrains.skiko
package SkijaInjectSample
igordmn marked this conversation as resolved.
Show resolved Hide resolved

import org.jetbrains.skiko.ClipComponent
import org.jetbrains.skiko.SkiaLayer
import java.awt.Color
import java.awt.Component
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import javax.swing.JLayeredPane
import org.jetbrains.skija.Rect

open class SkiaPanel: JLayeredPane {
val layer = SkiaLayer()
Expand All @@ -16,7 +17,7 @@ open class SkiaPanel: JLayeredPane {
}

override fun add(component: Component): Component {
layer.clipComponets.add(ClipComponent(component))
layer.clipComponents.add(ClipComponent(component))
return super.add(component, Integer.valueOf(0))
}

Expand All @@ -26,7 +27,6 @@ open class SkiaPanel: JLayeredPane {

addComponentListener(object : ComponentAdapter() {
override fun componentResized(e: ComponentEvent) {
layer.reinit()
layer.setSize(width, height)
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ import java.awt.Color
import java.awt.Dimension
import java.awt.event.ComponentAdapter
import java.awt.event.ComponentEvent
import java.awt.event.KeyEvent
import java.awt.event.MouseEvent
import java.awt.event.MouseMotionAdapter
import javax.swing.JFrame
import javax.swing.JButton
import javax.swing.JLayeredPane
import javax.swing.JFrame
import javax.swing.JPanel
import javax.swing.event.MouseInputAdapter
import javax.swing.WindowConstants
import org.jetbrains.skiko.SkiaPanel


fun Button(text: String): JButton {
Expand Down Expand Up @@ -63,13 +59,12 @@ fun SwingSkia() {
}
})

panel.layer.renderer = Renderer { renderer, w, h -> displayScene(renderer, w, h, mouseX, mouseY, state) }
panel.layer.renderer = Renderer(panel.layer) { renderer, w, h, nanoTime -> displayScene(renderer, w, h, nanoTime, mouseX, mouseY, state) }

panel.layer.addMouseMotionListener(object : MouseMotionAdapter() {
override fun mouseMoved(event: MouseEvent) {
mouseX = event.x
mouseY = event.y
panel.layer.display()
}
})

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.jetbrains.skiko

import java.awt.image.BufferedImage

fun isContentSame(img1: BufferedImage, img2: BufferedImage): Boolean {
if (img1.width == img2.width && img1.height == img2.height) {
for (x in 0 until img1.width) {
for (y in 0 until img1.height) {
if (img1.getRGB(x, y) != img2.getRGB(x, y)) {
return false
}
}
}
} else {
return false
}
return true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.jetbrains.skiko

import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
import java.awt.Rectangle
import java.awt.Robot
import java.io.File
import javax.imageio.ImageIO

// TODO macOs has wrong colors. Only white, black, red and green are correct
igordmn marked this conversation as resolved.
Show resolved Hide resolved
class ScreenshotTestRule(private val robot: Robot) : TestRule {
private lateinit var testIdentifier: String
private val screenshotsDir = File(System.getProperty("skiko.test.screenshots.dir")!!)

override fun apply(base: Statement, description: Description): Statement {
return object : Statement() {
override fun evaluate() {
testIdentifier = "${description.className}_${description.methodName}"
.replace(".", "_")
.replace(",", "_")
.replace(" ", "_")
.replace("(", "_")
.replace(")", "_")
.replace("__", "_")
.replace("__", "_")
.removePrefix("_")
.removeSuffix("_")
base.evaluate()
}
}
}

fun assert(rectangle: Rectangle, id: String = "") {
val actual = robot.createScreenCapture(rectangle)
val name = if (id.isNotEmpty()) "${testIdentifier}_$id" else testIdentifier
val actualFile = File(screenshotsDir, "${name}_actual.png")
val expectedFile = File(screenshotsDir, "$name.png")
if (actualFile.exists()) {
actualFile.delete()
}
if (expectedFile.exists()) {
val expected = ImageIO.read(expectedFile)
if (!isContentSame(expected, actual)) {
ImageIO.write(actual, "png", actualFile)
throw AssertionError(
"Image mismatch! Expected image ${expectedFile.absolutePath}, actual: ${actualFile.absolutePath}"
)
}
} else {
ImageIO.write(actual, "png", actualFile)
throw AssertionError(
"Missing screenshot image " +
"${actualFile.absolutePath}. " +
"Did you mean to check in a new image?"
)
}
}
}