Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
S
skiko
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
liuqiang
skiko
Commits
9c3169a5
Unverified
Commit
9c3169a5
authored
Jan 07, 2026
by
Alexander Maryanovsky
Committed by
GitHub
Jan 07, 2026
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fix SkiaLayer background drawing (#1141)
parent
e3e6430d
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
182 additions
and
40 deletions
+182
-40
SkiaLayer.android.kt
...droidMain/kotlin/org/jetbrains/skiko/SkiaLayer.android.kt
+10
-0
SkiaLayer.awt.kt
...o/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt
+55
-17
SoftwareContextHandler.kt
...lin/org/jetbrains/skiko/context/SoftwareContextHandler.kt
+3
-3
SkiaLayerTest.kt
...o/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt
+64
-11
SkiaLayer.kt
skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt
+5
-0
SkikoRenderDelegate.kt
...monMain/kotlin/org/jetbrains/skiko/SkikoRenderDelegate.kt
+1
-1
ContextHandler.kt
...Main/kotlin/org/jetbrains/skiko/context/ContextHandler.kt
+12
-8
SkiaLayer.linux.kt
...c/linuxMain/kotlin/org/jetbrains/skiko/SkiaLayer.linux.kt
+3
-0
SkiaLayer.macos.kt
...c/macosMain/kotlin/org/jetbrains/skiko/SkiaLayer.macos.kt
+9
-0
SkiaLayer.uikit.kt
...c/uikitMain/kotlin/org/jetbrains/skiko/SkiaLayer.uikit.kt
+10
-0
SkiaLayer.js.kt
skiko/src/webMain/kotlin/org/jetbrains/skiko/SkiaLayer.js.kt
+10
-0
No files found.
skiko/src/androidMain/kotlin/org/jetbrains/skiko/SkiaLayer.android.kt
View file @
9c3169a5
...
...
@@ -4,6 +4,7 @@ import android.content.Context
import
android.view.*
import
org.jetbrains.skia.Canvas
import
org.jetbrains.skia.PixelGeometry
import
org.jetbrains.skia.Color
actual
open
class
SkiaLayer
{
private
var
glView
:
SkikoSurfaceView
?
=
null
...
...
@@ -25,6 +26,15 @@ actual open class SkiaLayer {
if
(
value
)
throw
IllegalArgumentException
(
"transparency unsupported"
)
}
/**
* The background color of the layer.
*/
actual
var
backgroundColor
:
Int
=
Color
.
WHITE
set
(
value
)
{
field
=
value
needRender
()
}
actual
var
renderDelegate
:
SkikoRenderDelegate
?
=
null
actual
fun
attachTo
(
container
:
Any
)
{
...
...
skiko/src/awtMain/kotlin/org/jetbrains/skiko/SkiaLayer.awt.kt
View file @
9c3169a5
...
...
@@ -17,11 +17,11 @@ import java.awt.im.InputMethodRequests
import
java.beans.PropertyChangeListener
import
java.util.concurrent.CancellationException
import
javax.accessibility.Accessible
import
javax.accessibility.AccessibleContext
import
javax.accessibility.AccessibleRole
import
javax.swing.JComponent
import
javax.swing.JPanel
import
javax.swing.SwingUtilities
import
javax.swing.SwingUtilities.isEventDispatchThread
import
javax.swing.UIManager
import
javax.swing.event.AncestorEvent
import
javax.swing.event.AncestorListener
import
kotlin.math.floor
...
...
@@ -32,7 +32,7 @@ actual open class SkiaLayer internal constructor(
private
val
renderFactory
:
RenderFactory
=
RenderFactory
.
Default
,
private
val
analytics
:
SkiaLayerAnalytics
=
SkiaLayerAnalytics
.
Empty
,
actual
val
pixelGeometry
:
PixelGeometry
=
PixelGeometry
.
UNKNOWN
,
)
:
J
Panel
()
{
)
:
J
Component
(),
Accessible
{
internal
companion
object
{
init
{
...
...
@@ -45,18 +45,6 @@ actual open class SkiaLayer internal constructor(
ContentScale
,
}
private
var
_transparency
:
Boolean
=
false
actual
var
transparency
:
Boolean
get
()
=
_transparency
set
(
value
)
{
_transparency
=
value
if
(!
value
)
{
background
=
UIManager
.
getColor
(
"Panel.background"
)
}
else
{
background
=
Color
(
0
,
0
,
0
,
0
)
}
}
internal
val
backedLayer
:
HardwareLayer
constructor
(
...
...
@@ -100,7 +88,6 @@ actual open class SkiaLayer internal constructor(
private
var
latestReceivedGraphicsContextScaleTransform
:
AffineTransform
?
=
null
init
{
isOpaque
=
false
layout
=
null
backedLayer
=
object
:
HardwareLayer
(
externalAccessibleFactory
)
{
override
fun
paint
(
g
:
Graphics
)
{
...
...
@@ -206,6 +193,43 @@ actual open class SkiaLayer internal constructor(
}
}
private
var
_transparency
:
Boolean
=
false
actual
var
transparency
:
Boolean
get
()
=
_transparency
set
(
value
)
{
configureBackground
(
value
,
_background
)
}
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
override
fun
setBackground
(
bg
:
Color
?)
{
configureBackground
(
_transparency
,
bg
)
}
private
fun
configureBackground
(
transparency
:
Boolean
,
bg
:
Color
?)
{
_transparency
=
transparency
_background
=
bg
// Note that SkiaLayer itself doesn't draw its background; only backedLayer does, as it's heavyweight.
// We set the property just so it can be read back correctly, and also for the case when bg==null, as that
// indicates the parent's background should be used (getBackground() calls parent.getBackground() if own
// background is null).
super
.
setBackground
(
bg
)
// To enable transparency, the backedLayer's background must be transparent (also the window background).
backedLayer
.
background
=
if
(
transparency
)
Color
(
0
,
0
,
0
,
0
)
else
bg
needRender
()
}
// Override to make final, because it's called it in the init block
final
override
fun
addAncestorListener
(
listener
:
AncestorListener
?)
{
super
.
addAncestorListener
(
listener
)
...
...
@@ -662,10 +686,24 @@ actual open class SkiaLayer internal constructor(
fun
requestNativeFocusOnAccessible
(
accessible
:
Accessible
?)
{
backedLayer
.
requestNativeFocusOnAccessible
(
accessible
)
}
override
fun
getAccessibleContext
():
AccessibleContext
{
if
(
accessibleContext
==
null
)
{
accessibleContext
=
AccessibleSkiaLayer
()
}
return
accessibleContext
}
@Suppress
(
"RedundantInnerClassModifier"
)
protected
inner
class
AccessibleSkiaLayer
:
AccessibleJComponent
()
{
override
fun
getAccessibleRole
():
AccessibleRole
{
return
AccessibleRole
.
PANEL
}
}
}
/**
* Disable showing window title bar.
* Disable showing
the
window title bar.
*/
fun
SkiaLayer
.
disableTitleBar
(
customHeaderHeight
:
Float
)
{
backedLayer
.
disableTitleBar
(
customHeaderHeight
)
...
...
skiko/src/awtMain/kotlin/org/jetbrains/skiko/context/SoftwareContextHandler.kt
View file @
9c3169a5
...
...
@@ -10,9 +10,9 @@ import java.awt.color.ColorSpace
import
java.awt.image.*
internal
class
SoftwareContextHandler
(
layer
:
SkiaLayer
)
:
ContextFreeContextHandler
(
layer
)
{
override
fun
isTransparentBackground
():
Boolean
{
// TODO: why Software rendering has another transparency logic from the beg
g
inning
return
hostOs
==
OS
.
MacOS
&&
layer
.
transparency
override
fun
isTransparentBackground
Supported
():
Boolean
{
// TODO: why Software rendering has another transparency logic from the beginning
return
hostOs
==
OS
.
MacOS
}
val
colorModel
=
ComponentColorModel
(
...
...
skiko/src/awtTest/kotlin/org/jetbrains/skiko/SkiaLayerTest.kt
View file @
9c3169a5
...
...
@@ -1057,15 +1057,6 @@ class SkiaLayerTest {
// a smaller black window on top of it while screenshotting the pixel at the center,
// and making sure that pixel is always either black or green.
// We can't compare colors exactly because java.awt.Robot can return a slightly different color due to
// system color profile
fun
Color
.
closeTo
(
other
:
Color
):
Boolean
{
val
diffLimit
=
10
return
(
red
-
other
.
red
).
absoluteValue
<
diffLimit
&&
(
green
-
other
.
green
).
absoluteValue
<
diffLimit
&&
(
blue
-
other
.
blue
).
absoluteValue
<
diffLimit
}
val
bgColor
=
Color
.
GREEN
val
fgColor
=
Color
.
BLACK
val
backgroundWindow
=
JFrame
().
also
{
...
...
@@ -1329,6 +1320,60 @@ class SkiaLayerTest {
assertTrue
(
drawCalls
>
initDrawCalls
)
}
private
suspend
fun
UiTestScope
.
testLayerBackground
(
initLayer
:
UiTestWindow
.()
->
Unit
=
{}
)
{
val
window
=
UiTestWindow
()
try
{
window
.
setLocation
(
200
,
200
)
window
.
setSize
(
300
,
300
)
val
layer
=
window
.
layer
layer
.
renderDelegate
=
SkikoRenderDelegate
{
_
,
_
,
_
,
_
->
}
initLayer
(
window
)
layer
.
background
=
Color
.
RED
window
.
isVisible
=
true
delay
(
1000
)
val
robot
=
Robot
()
val
windowBounds
=
window
.
bounds
fun
assertLayerIs
(
color
:
Color
)
{
val
pixel
=
robot
.
getPixelColor
(
windowBounds
.
centerX
.
toInt
(),
windowBounds
.
centerY
.
toInt
())
assertTrue
(
pixel
.
closeTo
(
color
),
"Actual pixel $pixel not close to expected $color"
)
}
assertLayerIs
(
Color
.
RED
)
layer
.
background
=
Color
.
BLUE
delay
(
100
)
assertLayerIs
(
Color
.
BLUE
)
layer
.
background
=
Color
.
GREEN
delay
(
100
)
assertLayerIs
(
Color
.
GREEN
)
}
finally
{
window
.
dispose
()
}
}
@Test
fun
`
layer
background
is
drawn
correctly
`
()
=
uiTest
{
testLayerBackground
()
}
@Test
fun
`
layer
background
is
drawn
correctly
with
transparency
`
()
=
uiTest
{
if
(
renderApi
==
GraphicsApi
.
ANGLE
)
return
@uiTest
// See https://youtrack.jetbrains.com/issue/SKIKO-1089
testLayerBackground
{
layer
.
transparency
=
true
layer
.
background
=
Color
(
0
,
0
,
0
,
0
)
isUndecorated
=
true
val
transparentWindowHack
=
(
hostOs
==
OS
.
Windows
)
&&
(
renderApi
!=
GraphicsApi
.
DIRECT3D
)
background
=
if
(
transparentWindowHack
)
null
else
Color
(
0
,
0
,
0
,
0
)
}
}
private
class
RectRenderer
(
private
val
getContentScale
:
()
->
Float
,
var
rectWidth
:
Int
,
...
...
@@ -1365,8 +1410,6 @@ class SkiaLayerTest {
}
}
private
class
AnimatedBoxRenderer
(
private
val
layer
:
SkiaLayer
,
private
val
pixelsPerSecond
:
Double
,
...
...
@@ -1424,6 +1467,16 @@ class SkiaLayerTest {
}
}
/**
* Compares two colors, within a certain tolerance.
* We can't compare colors exactly because java.awt.Robot can return a slightly different color due to
* system color profile
*/
fun
Color
.
closeTo
(
other
:
Color
,
diffLimit
:
Int
=
10
):
Boolean
{
return
(
red
-
other
.
red
).
absoluteValue
<
diffLimit
&&
(
green
-
other
.
green
).
absoluteValue
<
diffLimit
&&
(
blue
-
other
.
blue
).
absoluteValue
<
diffLimit
}
}
private
fun
JFrame
.
close
()
=
dispatchEvent
(
WindowEvent
(
this
,
WindowEvent
.
WINDOW_CLOSING
))
skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkiaLayer.kt
View file @
9c3169a5
...
...
@@ -33,6 +33,11 @@ expect open class SkiaLayer {
*/
var
transparency
:
Boolean
/**
* The color, in ARGB format, with which the layer is cleared before rendering.
*/
var
backgroundColor
:
Int
/**
* Underlying platform component.
*/
...
...
skiko/src/commonMain/kotlin/org/jetbrains/skiko/SkikoRenderDelegate.kt
View file @
9c3169a5
...
...
@@ -2,7 +2,7 @@ package org.jetbrains.skiko
import
org.jetbrains.skia.Canvas
interface
SkikoRenderDelegate
{
fun
interface
SkikoRenderDelegate
{
fun
onRender
(
canvas
:
Canvas
,
width
:
Int
,
height
:
Int
,
nanoTime
:
Long
)
}
...
...
skiko/src/commonMain/kotlin/org/jetbrains/skiko/context/ContextHandler.kt
View file @
9c3169a5
...
...
@@ -41,22 +41,26 @@ internal abstract class ContextHandler(
}
initCanvas
()
canvas
?.
apply
{
clear
(
if
(
isTransparentBackground
())
Color
.
TRANSPARENT
else
Color
.
WHITE
)
val
layerBg
=
layer
.
backgroundColor
clear
(
if
(
layer
.
transparency
&&
isTransparentBackgroundSupported
())
{
layerBg
}
else
{
layerBg
or
0
xFF000000
.
toInt
()
}
)
drawContent
()
}
flush
()
}
protected
open
fun
isTransparentBackground
():
Boolean
{
protected
open
fun
isTransparentBackground
Supported
():
Boolean
{
if
(
hostOs
==
OS
.
MacOS
)
{
// MacOS transparency is always supported
return
true
}
if
(
layer
.
fullscreen
)
{
// for non-MacOS in fullscreen transparency is not supported
return
false
}
// for non-MacOS in non-fullscreen transparency provided by [layer]
return
layer
.
transparency
// for non-MacOS in fullscreen transparency is not supported
return
!
layer
.
fullscreen
}
}
skiko/src/linuxMain/kotlin/org/jetbrains/skiko/SkiaLayer.linux.kt
View file @
9c3169a5
...
...
@@ -15,6 +15,9 @@ actual open class SkiaLayer {
actual
var
transparency
:
Boolean
get
()
=
TODO
(
"Not yet implemented"
)
set
(
value
)
{}
actual
var
backgroundColor
:
Int
get
()
=
TODO
(
"Not yet implemented"
)
set
(
value
)
{}
actual
val
component
:
Any
?
get
()
=
TODO
(
"Not yet implemented"
)
actual
fun
needRender
(
throttledToVsync
:
Boolean
)
{
...
...
skiko/src/macosMain/kotlin/org/jetbrains/skiko/SkiaLayer.macos.kt
View file @
9c3169a5
...
...
@@ -58,6 +58,15 @@ actual open class SkiaLayer {
if
(
value
)
throw
IllegalArgumentException
(
"transparency unsupported"
)
}
/**
* The background color of the layer.
*/
actual
var
backgroundColor
:
Int
=
Color
.
WHITE
set
(
value
)
{
field
=
value
needRender
()
}
/**
* Underlying [NSView]
*/
...
...
skiko/src/uikitMain/kotlin/org/jetbrains/skiko/SkiaLayer.uikit.kt
View file @
9c3169a5
...
...
@@ -2,6 +2,7 @@ package org.jetbrains.skiko
import
kotlinx.cinterop.useContents
import
org.jetbrains.skia.Canvas
import
org.jetbrains.skia.Color
import
org.jetbrains.skia.PixelGeometry
import
org.jetbrains.skia.Surface
...
...
@@ -23,6 +24,15 @@ actual open class SkiaLayer {
get
()
=
false
set
(
_
)
{
throw
UnsupportedOperationException
()
}
/**
* The background color of the layer, as transparency is not supported.
*/
actual
var
backgroundColor
:
Int
=
Color
.
WHITE
set
(
value
)
{
field
=
value
needRender
()
}
actual
fun
needRender
(
throttledToVsync
:
Boolean
)
{
needRedrawCallback
.
invoke
()
}
...
...
skiko/src/webMain/kotlin/org/jetbrains/skiko/SkiaLayer.js.kt
View file @
9c3169a5
package
org.jetbrains.skiko
import
org.jetbrains.skia.Canvas
import
org.jetbrains.skia.Color
import
org.jetbrains.skia.PixelGeometry
import
org.jetbrains.skiko.w3c.HTMLCanvasElement
import
org.jetbrains.skiko.w3c.window
...
...
@@ -45,6 +46,15 @@ actual open class SkiaLayer {
if
(
value
)
throw
Exception
(
"Transparency is not supported!"
)
}
/**
* The background color of the layer, as transparency is not supported.
*/
actual
var
backgroundColor
:
Int
=
Color
.
WHITE
set
(
value
)
{
field
=
value
needRender
()
}
/**
* Schedules a drawFrame to the appropriate moment.
*/
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment