Skiko (Skia for Kotlin)

repository·master·Indexed 24 days ago

https://github.com/jetbrains/skiko

A Kotlin Multiplatform library that exposes Skia's 2D graphics APIs to Kotlin. It provides rendering contexts for JVM, Android, iOS, macOS, and WebAssembly. The library includes support for the Graphite GPU backend, Skottie animations, and integration with Swing via SkiaLayer.

Tokens
4.9K
Snippets
9
Records
27
Agent score
77%

What's inside Skiko

  1. Run Skia multiplatform samples on iOS with debugging in AppCode

    master

    To debug Skiko sources directly in AppCode without publishing to Maven Local, follow these steps:

    1. Set the Gradle property skiko.composite.build=1 in your gradle.properties file.
    2. Install the KMM plugin for AppCode.
    3. Open samples/SkiaMultiplatformSample in AppCode (File -> Open) and select "Open as Project".
    4. Select your target device and Run. This allows you to use breakpoints in both common and native Kotlin code.
    skiko.composite.build=1
  2. Add Skiko as a dependency

    master

    To use Skiko in your Kotlin project, you must include the appropriate runtime dependency based on your target operating system and architecture. You need to add mavenCentral() and the Compose Dev repository to your repositories block, then compute the target string in the format ${targetOs}-${targetArch} (e.g., macos-arm64, windows-x64, linux-x64).

    Common target mappings:

    • OS: macos, windows, linux
    • Arch: x64 (for x86_64 or amd64), arm64 (for aarch64)

    Use the dependency format: org.jetbrains.skiko:skiko-awt-runtime-$target:$version.

        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")
        }
  3. Record and submit GPU work with GraphiteContext

    master

    The GraphiteContext manages the lifecycle of GPU work. The typical workflow involves:

    1. Creating a Recorder via makeRecorder().
    2. Recording drawing commands into a Recording.
    3. Inserting that Recording into the context using insertRecording(recording).
    4. Submitting the pending work to the GPU using submit(syncCpu).

    If syncCpu is set to true in submit(), the function will wait for the submitted GPU work to complete before returning.

  4. How to build a Jump List using JumpList.build

    master

    To create or update a Jump List, use the JumpList.build function. This function provides a JumpListBuilder instance within a block. The builder follows a specific lifecycle:

    1. Set AppID: If your application uses an explicit AppUserModelID, call setAppID(appID: String) first.
    2. Begin List: Call beginList() to start the transaction. This retrieves the current list and identifies items the user has previously removed (via getRemovedItems()).
    3. Add Content: Use addCategory(category: String, items: List<JumpListItem>) to add grouped items, or addUserTask(task: JumpListItem) to add tasks to the canonical "Tasks" section at the bottom.
    4. Commit: Call commit() to finalize the changes. This returns a Result<Unit> which may indicate a recoverable error (e.g., if a category could not be appended due to access restrictions).

    Note: When adding categories, ensure you do not re-add items that are present in getRemovedItems(), as they may not be re-added during the transaction.

  5. Render Skia content on iOS (Kotlin/Native)

    master

    For iOS applications using Kotlin/Native, you integrate Skiko by setting up a SkikoAppDelegate that manages a UIWindow. The root view controller should be a SkikoViewController containing a SkikoUIView which wraps a SkiaLayer. The SkiaLayer uses a SkikoRenderDelegate to handle drawing via the onRender callback.

    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
        }
    }
  6. Render Skia content on Kotlin/JVM (Swing)

    master

    On the JVM, you can use SkiaLayer to integrate Skia rendering into a Swing application. You must provide a SkiaLayerRenderDelegate that implements the SkikoRenderDelegate interface. The onRender method provides a Canvas for drawing, the current dimensions, and a nanoTime timestamp.

    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
        }
    }
  7. Handle JumpListException

    master
    The JumpList.commit() method returns a Result<Unit>. If a JumpListException occurs during the commit process, it might be recoverable. Specifically, if the error code is -2147024891 (E_ACCESSDENIED) during an AppendCategory operation, the library treats this as a recoverable failure, meaning the list was still committed but might be missing some categories. Other error codes will result in the exception being thrown.
  8. Create a Graphite backend texture from Metal

    master

    Use BackendTexture.makeMetal to wrap an existing Metal texture for use in Graphite rendering. This requires Metal support on the platform. You must provide the texture's dimensions in pixels and its native pointer.

    Parameters:

    • width: The width of the texture in pixels (must be > 0).
    • height: The height of the texture in pixels (must be > 0).
    • texturePtr: The native pointer to the Metal texture to wrap (must not be NullPointer).

    Note: This API is marked with @ExperimentalSkikoApi.