JetBrains/skiko

Kotlin Multiplatform bindings to Skia

Kotlin

2,193

1,296 commits

updated Oct 19, 2026

See the code

README

official project version

Kotlin Multiplatform library for Skia and window management

Skiko (short for Skia for Kotlin) is the graphical library exposing significant part of Skia library APIs to Kotlin, along with the gluing code for rendering context.

Supported platforms:

  • Kotlin/JVM on Linux(x86_64 and arm64)
  • Kotlin/JVM on Windows(x86_64)
  • Kotlin/JVM on macOS(x86_64 and arm64)
  • Kotlin/JVM on Android(x86_64 and arm64), starting with API version 24
  • Kotlin/JS + WebAssembly in browsers
  • Kotlin/Native on iOS(arm64 and x64)
  • Kotlin/Native on macOS (arm64 and x64)

API documentation

See autogenerated API docs at https://jetbrains.github.io/skiko/

Using as dependency

To use in build scripts one has to compute appropriate target platform and version, i.e. something like this

    repositories {
        mavenCentral()
        maven("https://redirector.kotlinlang.org/maven/compose-dev")
    }

    val osName = System.getProperty("os.name")
    val targetOs = when {
        osName == "Mac OS X" -> "macos"
        osName.startsWith("Win") -> "windows"
        osName.startsWith("Linux") -> "linux"
        else -> error("Unsupported OS: $osName")
    }

    val osArch = System.getProperty("os.arch")
    val targetArch = when (osArch) {
        "x86_64", "amd64" -> "x64"
        "aarch64" -> "arm64"
        else -> error("Unsupported arch: $osArch")
    }

    val version = "0.8.9" // or any more recent version
    val target = "${targetOs}-${targetArch}"
    dependencies {
        implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$version")
    }

Simple example for Kotlin/JVM

fun main() {
    val skiaLayer = SkiaLayer()
    skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
        val paint = Paint().apply {
            color = Color.RED
        }
        override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
            canvas.clear(Color.CYAN)
            val ts = nanoTime / 5_000_000
            canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
        }
    })
    SwingUtilities.invokeLater {
        val window = JFrame("Skiko example").apply {
            defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
            preferredSize = Dimension(800, 600)
        }
        skiaLayer.attachTo(window.contentPane)
        skiaLayer.needRedraw()
        window.pack()
        window.isVisible = true
    }
}

Simple example for iOS

fun main() {
    val args = emptyArray<String>()
    memScoped {
        val argc = args.size + 1
        val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()
        autoreleasepool {
            UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))
        }
    }
}

class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
    companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta

    @ObjCObjectBase.OverrideInit
    constructor() : super()

    private var _window: UIWindow? = null
    override fun window() = _window
    override fun setWindow(window: UIWindow?) {
        _window = window
    }

    override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
        window = UIWindow(frame = UIScreen.mainScreen.bounds)
        window!!.rootViewController = SkikoViewController(
            SkikoUIView(
                SkiaLayer().apply {
                    renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
                      val paint = Paint().apply { color = Color.RED }
                      override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
                        canvas.clear(Color.CYAN)
                        val ts = nanoTime / 5_000_000
                        canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
                      }
                    })
                }
            )
        )
        window!!.makeKeyAndVisible()
        return true
    }
}

See this sample for complete example.

JetBrains/skiko

Kotlin Multiplatform bindings to Skia

Kotlin

2,193

1,296 commits

updated Oct 19, 2026

See the code

README

official project version

Kotlin Multiplatform library for Skia and window management

Skiko (short for Skia for Kotlin) is the graphical library exposing significant part of Skia library APIs to Kotlin, along with the gluing code for rendering context.

Supported platforms:

  • Kotlin/JVM on Linux(x86_64 and arm64)
  • Kotlin/JVM on Windows(x86_64)
  • Kotlin/JVM on macOS(x86_64 and arm64)
  • Kotlin/JVM on Android(x86_64 and arm64), starting with API version 24
  • Kotlin/JS + WebAssembly in browsers
  • Kotlin/Native on iOS(arm64 and x64)
  • Kotlin/Native on macOS (arm64 and x64)

API documentation

See autogenerated API docs at https://jetbrains.github.io/skiko/

Using as dependency

To use in build scripts one has to compute appropriate target platform and version, i.e. something like this

    repositories {
        mavenCentral()
        maven("https://redirector.kotlinlang.org/maven/compose-dev")
    }

    val osName = System.getProperty("os.name")
    val targetOs = when {
        osName == "Mac OS X" -> "macos"
        osName.startsWith("Win") -> "windows"
        osName.startsWith("Linux") -> "linux"
        else -> error("Unsupported OS: $osName")
    }

    val osArch = System.getProperty("os.arch")
    val targetArch = when (osArch) {
        "x86_64", "amd64" -> "x64"
        "aarch64" -> "arm64"
        else -> error("Unsupported arch: $osArch")
    }

    val version = "0.8.9" // or any more recent version
    val target = "${targetOs}-${targetArch}"
    dependencies {
        implementation("org.jetbrains.skiko:skiko-awt-runtime-$target:$version")
    }

Simple example for Kotlin/JVM

fun main() {
    val skiaLayer = SkiaLayer()
    skiaLayer.renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
        val paint = Paint().apply {
            color = Color.RED
        }
        override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
            canvas.clear(Color.CYAN)
            val ts = nanoTime / 5_000_000
            canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
        }
    })
    SwingUtilities.invokeLater {
        val window = JFrame("Skiko example").apply {
            defaultCloseOperation = WindowConstants.EXIT_ON_CLOSE
            preferredSize = Dimension(800, 600)
        }
        skiaLayer.attachTo(window.contentPane)
        skiaLayer.needRedraw()
        window.pack()
        window.isVisible = true
    }
}

Simple example for iOS

fun main() {
    val args = emptyArray<String>()
    memScoped {
        val argc = args.size + 1
        val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues()
        autoreleasepool {
            UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate))
        }
    }
}

class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol {
    companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta

    @ObjCObjectBase.OverrideInit
    constructor() : super()

    private var _window: UIWindow? = null
    override fun window() = _window
    override fun setWindow(window: UIWindow?) {
        _window = window
    }

    override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map<Any?, *>?): Boolean {
        window = UIWindow(frame = UIScreen.mainScreen.bounds)
        window!!.rootViewController = SkikoViewController(
            SkikoUIView(
                SkiaLayer().apply {
                    renderDelegate = SkiaLayerRenderDelegate(skiaLayer, object : SkikoRenderDelegate {
                      val paint = Paint().apply { color = Color.RED }
                      override fun onRender(canvas: Canvas, width: Int, height: Int, nanoTime: Long) {
                        canvas.clear(Color.CYAN)
                        val ts = nanoTime / 5_000_000
                        canvas.drawCircle( (ts % width).toFloat(), (ts % height).toFloat(), 20f, paint )
                      }
                    })
                }
            )
        )
        window!!.makeKeyAndVisible()
        return true
    }
}

See this sample for complete example.

Project health

Overall

4.8/10

Maintained

10/10

Code review

9/10
OpenSSF Scorecard

Latest release

0.153.0 · Oct 6, 2026

Releases this year

15

Significant stargazers

(top 24 of 32)

Jake Wharton

68,766 followers · starred Jan 2023

Jaewoong Eum

13,002 followers · starred Nov 2020

An Long

640 followers · starred Sep 2026

Luke Warlow

165 followers · starred Aug 2024