Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.*
import android.content.res.Configuration
import android.view.View
import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.defaultIsTransparentBackgroundSupported

actual fun setSystemLookAndFeel(): Unit = TODO()

Expand All @@ -15,6 +16,7 @@ internal class AndroidOpenGLRedrawer(
override fun needRender(canUpdateImmediately: Boolean) = TODO()
override fun renderImmediately() = TODO()
override fun update(nanoTime: Long) = TODO()
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(layer)

override val renderInfo: String get() = "Android renderer"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,6 @@ actual open class SkiaLayer {
if (value) throw IllegalArgumentException("changing fullscreen is unsupported")
}

actual var transparency: Boolean
get() = false
set(value) {
if (value) throw IllegalArgumentException("transparency unsupported")
}

/**
* The background color of the layer.
*/
actual internal var backgroundColor: Int = Color.WHITE
set(value) {
field = value
needRender()
}

actual var renderDelegate: SkikoRenderDelegate? = null

actual fun attachTo(container: Any) {
Expand Down Expand Up @@ -81,8 +66,5 @@ actual open class SkiaLayer {
actual val component: Any?
get() = this.container

actual internal val cutoutRectangles: List<ClipRectangle>
get() = emptyList()

internal actual fun draw(canvas: Canvas): Unit = TODO()
}
34 changes: 23 additions & 11 deletions skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -194,18 +194,16 @@ actual open class SkiaLayer internal constructor(
}

private var _transparency: Boolean = false
actual var transparency: Boolean

/**
* Whether transparency is enabled.
*/
var transparency: Boolean
get() = _transparency
set(value) {
configureBackground(value, _background)
}

internal actual var backgroundColor: Int
get() = background.rgb // Will return an ancestor's non-null background after setBackground(null).
set(value) {
configureBackground(_transparency, Color(value, true))
}

// This is needed because after setBackground(null), getBackground() will not return null, but an ancestor's
// non-null background. But we need to preserve the null value when modifying `transparency`.
private var _background: Color? = null
Expand Down Expand Up @@ -333,11 +331,11 @@ actual open class SkiaLayer internal constructor(
jComponent.add(this)
}

/**
* A list of rectangles to cut out from the rendered content; No content will be drawn inside them.
*/
val clipComponents = mutableListOf<ClipRectangle>()

internal actual val cutoutRectangles: List<ClipRectangle>
get() = clipComponents

@Volatile
private var isDisposed = false

Expand Down Expand Up @@ -603,13 +601,27 @@ actual open class SkiaLayer internal constructor(
// If this approach will be changed, create an issue in https://youtrack.jetbrains.com/issues/CMP for changing it in
// https://github.com/JetBrains/compose-multiplatform/blob/e4e2d329709cded91a09cc612d4defbce37aad96/benchmarks/multiplatform/benchmarks/src/commonMain/kotlin/MeasureComposable.kt#L151 as well

val contentScale = this.contentScale
val pictureWidth = (backedLayer.width * contentScale).coerceAtLeast(0f)
val pictureHeight = (backedLayer.height * contentScale).coerceAtLeast(0f)
val intWidth = pictureWidth.toInt()
val intHeight = pictureHeight.toInt()

val pictureRecorder = pictureRecorder!!
val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth, pictureHeight)
val canvas = pictureRecorder.beginRecording(0f, 0f, pictureWidth, pictureHeight).apply {
for (component in clipComponents) {
cutoutFromClip(component, contentScale)
}

val layerBg = background.rgb // Will return an ancestor's non-null background after setBackground(null).
clear(
if (transparency && (redrawer?.isTransparentBackgroundSupported() == true)) {
layerBg
} else {
layerBg or 0xFF000000.toInt()
}
)
}

try {
isRendering = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ import java.awt.color.ColorSpace
import java.awt.image.*

internal class SoftwareContextHandler(layer: SkiaLayer) : ContextFreeContextHandler(layer) {
override fun isTransparentBackgroundSupported(): Boolean {
// TODO: why Software rendering has another transparency logic from the beginning
return hostOs == OS.MacOS
}

val colorModel = ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,6 @@ internal abstract class AWTRedrawer(
protected fun checkDisposed() {
check(!isDisposed) { "${this.javaClass.simpleName} is disposed" }
}

override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(layer)
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,9 @@ internal class SoftwareRedrawer(
}
}
}

override fun isTransparentBackgroundSupported(): Boolean {
// TODO: why Software rendering has another transparency logic from the beginning
return hostOs == OS.MacOS
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.jetbrains.skiko.swing

import org.jetbrains.skiko.*
import org.jetbrains.skiko.context.cutoutFromClip
import org.jetbrains.skiko.redrawer.RedrawerManager
import java.awt.Component
import java.awt.Graphics
Expand Down Expand Up @@ -46,8 +45,8 @@ open class SkiaSwingLayer(
val scale = graphicsConfiguration.defaultTransform.scaleX.toFloat()
// clipping
for (index in clipComponents.indices) {
val item = clipComponents[index]
canvas.cutoutFromClip(item, scale)
val component = clipComponents[index]
canvas.cutoutFromClip(component, scale)
}
renderDelegate.onRender(canvas, width, height, nanoTime)
}
Expand Down
2 changes: 2 additions & 0 deletions skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import org.jetbrains.skiko.context.JvmContextHandler
import org.jetbrains.skiko.redrawer.MetalRedrawer
import org.jetbrains.skiko.redrawer.MetalVSyncer
import org.jetbrains.skiko.redrawer.Redrawer
import org.jetbrains.skiko.redrawer.defaultIsTransparentBackgroundSupported
import org.jetbrains.skiko.swing.SkiaSwingLayer
import org.jetbrains.skiko.util.ScreenshotTestRule
import org.jetbrains.skiko.util.UiTestScope
Expand Down Expand Up @@ -565,6 +566,7 @@ class SkiaLayerTest {
override fun needRender(throttledToVsync: Boolean) = frameDispatcher.scheduleFrame()
override fun renderImmediately() = Unit
override fun update(nanoTime: Long) = layer.update(nanoTime)
override fun isTransparentBackgroundSupported() = defaultIsTransparentBackgroundSupported(layer)

override val renderInfo: String
get() = ""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal fun uiTest(
block: suspend UiTestScope.() -> Unit
) {
assumeFalse(GraphicsEnvironment.isHeadless())
assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true")
// assumeTrue(System.getProperty("skiko.test.ui.enabled", "false") == "true")

val renderApiProperty = System.getProperty("skiko.test.ui.renderApi", "all")

Expand Down
9 changes: 9 additions & 0 deletions skiko/src/commonMain/kotlin/org/jetbrains/skia/Canvas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1849,3 +1849,12 @@ private external fun _nRestore(ptr: NativePointer)

@ExternalSymbolName("org_jetbrains_skia_Canvas__1nRestoreToCount")
private external fun _nRestoreToCount(ptr: NativePointer, saveCount: Int)

internal inline fun Canvas.runRestoringState(block: Canvas.() -> Unit) {
val restoreCount = save()
try {
block()
} finally {
restoreToCount(restoreCount)
}
}
19 changes: 18 additions & 1 deletion skiko/src/commonMain/kotlin/org/jetbrains/skiko/ClipRectangle.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jetbrains.skiko

import org.jetbrains.skia.Canvas
import org.jetbrains.skia.ClipMode

/**
* Rectangle used for clipping.
*/
Expand All @@ -18,4 +21,18 @@ internal fun ClipRectangle(x: Float, y: Float, width: Float, height: Float) = ob
override val y: Float = y
override val width: Float = width
override val height: Float = height
}
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun Canvas.cutoutFromClip(rect: ClipRectangle, scale: Float) {
val x = rect.x
val y = rect.y
clipRect(
left = x * scale,
top = y * scale,
right = (x + rect.width) * scale,
bottom = (y + rect.height) * scale,
mode = ClipMode.DIFFERENCE,
antiAlias = true
)
}
15 changes: 0 additions & 15 deletions skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,26 +28,11 @@ expect open class SkiaLayer {
*/
var fullscreen: Boolean

/**
* If transparency is enabled.
*/
var transparency: Boolean

/**
* The color, in ARGB format, with which the layer is cleared before rendering.
*/
internal var backgroundColor: Int

/**
* Underlying platform component.
*/
val component: Any?

/**
* A list of rectangles to cut out from the rendered content; No content will be drawn inside them.
*/
internal val cutoutRectangles: List<ClipRectangle>

/**
* Current view used for rendering.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,54 +42,8 @@ internal abstract class ContextHandler(
initCanvas()
canvas?.runRestoringState {
clear(Color.TRANSPARENT)

val scale = layer.contentScale
for (clip in layer.cutoutRectangles) {
cutoutFromClip(clip, scale)
}

val layerBg = layer.backgroundColor
clear(
if (layer.transparency && isTransparentBackgroundSupported()) {
layerBg
} else {
layerBg or 0xFF000000.toInt()
}
)

drawContent()
}
flush()
}

protected open fun isTransparentBackgroundSupported(): Boolean {
if (hostOs == OS.MacOS) {
// macOS transparency is always supported
return true
}

// for non-macOS in fullscreen transparency is not supported
return !layer.fullscreen
}
}

@Suppress("NOTHING_TO_INLINE")
internal inline fun Canvas.cutoutFromClip(rectangle: ClipRectangle, scale: Float) {
clipRect(
left = rectangle.x * scale,
top = rectangle.y * scale,
right = (rectangle.x + rectangle.width) * scale,
bottom = (rectangle.y + rectangle.height) * scale,
mode = ClipMode.DIFFERENCE,
antiAlias = true
)
}

private inline fun Canvas.runRestoringState(block: Canvas.() -> Unit) {
val restoreCount = save()
try {
block()
} finally {
restoreToCount(restoreCount)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.jetbrains.skiko.redrawer

import org.jetbrains.skiko.OS
import org.jetbrains.skiko.SkiaLayer
import org.jetbrains.skiko.hostOs
import kotlin.time.TimeSource

private val initialTime = TimeSource.Monotonic.markNow()
Expand All @@ -12,4 +15,15 @@ internal interface Redrawer {
fun update(nanoTime: Long = initialTime.elapsedNow().inWholeNanoseconds)
fun setVisible(isVisible: Boolean) = Unit
val renderInfo: String
fun isTransparentBackgroundSupported(): Boolean
}

internal fun defaultIsTransparentBackgroundSupported(layer: SkiaLayer): Boolean {
if (hostOs == OS.MacOS) {
// macOS transparency is always supported
return true
}

// for non-macOS in fullscreen transparency is not supported
return !layer.fullscreen
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,8 @@ actual open class SkiaLayer {
actual var fullscreen: Boolean
get() = TODO("Not yet implemented")
set(value) {}
actual var transparency: Boolean
get() = TODO("Not yet implemented")
set(value) {}
internal actual var backgroundColor: Int
get() = TODO("Not yet implemented")
set(value) {}
actual val component: Any?
get() = TODO("Not yet implemented")
internal actual val cutoutRectangles: List<ClipRectangle>
get() = emptyList()
actual fun needRender(throttledToVsync: Boolean) {
TODO("unimplemented")
}
Expand Down
Loading