Skip to content

Commit 3e575c5

Browse files
Expose Blender skia API (#1168)
Required for [CMP-5046](https://youtrack.jetbrains.com/issue/CMP-5046) Supersedes #1162 with a cleaned-up and fixes --------- Co-authored-by: Luc Girardin <luc.girardin@macrofocus.com>
1 parent 0c7dafe commit 3e575c5

File tree

15 files changed

+358
-22
lines changed

15 files changed

+358
-22
lines changed

DEVELOPMENT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Add `-Dskiko.test.ui.enabled=true` to enable UI tests (integration tests, which
3737
3838
For example, if we want to include UI tests when we test JVM target, call this:
3939
```
40-
./gradlew awtTest -Dskiko.test.ui.enabled=true
40+
./gradlew :skiko:awtTest -Dskiko.test.ui.enabled=true
4141
```
4242
Don't run any background tasks, click mouse, or press keys during the tests. Otherwise, they probably fail.
4343

skiko/src/commonMain/kotlin/org/jetbrains/skia/BlendMode.kt

Lines changed: 45 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,83 @@
11
package org.jetbrains.skia
22

3+
/**
4+
* Blends are operators that take in two colors (source, destination) and return a new color.
5+
* Many of these operate the same on all 4 components: red, green, blue, alpha. For these,
6+
* we just document what happens to one component, rather than naming each one separately.
7+
*
8+
* Different SkColorTypes have different representations for color components:
9+
* 8-bit: 0..255
10+
* 6-bit: 0..63
11+
* 5-bit: 0..31
12+
* 4-bit: 0..15
13+
* floats: 0...1
14+
*
15+
* The documentation is expressed as if the component values are always 0..1 (floats).
16+
*
17+
* For brevity, the documentation uses the following abbreviations
18+
* s : source
19+
* d : destination
20+
* sa : source alpha
21+
* da : destination alpha
22+
*
23+
* Results are abbreviated
24+
* r : if all 4 components are computed in the same manner
25+
* ra : result alpha component
26+
* rc : result "color": red, green, blue components
27+
*/
328
enum class BlendMode {
4-
/** Replaces destination with zero: fully transparent. */
29+
/** Replaces destination with zero: fully transparent. r = 0 */
530
CLEAR,
631

7-
/** Replaces destination. */
32+
/** Replaces destination. r = s */
833
SRC,
934

10-
/** Preserves destination. */
35+
/** Preserves destination. r = d */
1136
DST,
1237

13-
/** Source over destination. */
38+
/** Source over destination. r = s + (1-sa)*d */
1439
SRC_OVER,
1540

16-
/** Destination over source. */
41+
/** Destination over source. r = d + (1-da)*s */
1742
DST_OVER,
1843

19-
/** Source trimmed inside destination. */
44+
/** Source trimmed inside destination. r = s * da */
2045
SRC_IN,
2146

22-
/** Destination trimmed by source. */
47+
/** Destination trimmed by source. r = d * sa */
2348
DST_IN,
2449

25-
/** Source trimmed outside destination. */
50+
/** Source trimmed outside destination. r = s * (1-da) */
2651
SRC_OUT,
2752

28-
/** Destination trimmed outside source. */
53+
/** Destination trimmed outside source. r = d * (1-sa) */
2954
DST_OUT,
3055

31-
/** Source inside destination blended with destination. */
56+
/** Source inside destination blended with destination. r = s*da + d*(1-sa) */
3257
SRC_ATOP,
3358

34-
/** Destination inside source blended with source. */
59+
/** Destination inside source blended with source. r = d*sa + s*(1-da) */
3560
DST_ATOP,
3661

37-
/** Each of source and destination trimmed outside the other. */
62+
/** Each of source and destination trimmed outside the other. r = s*(1-da) + d*(1-sa) */
3863
XOR,
3964

40-
/** Sum of colors. */
65+
/** Sum of colors. r = min(s + d, 1) */
4166
PLUS,
4267

43-
/** Product of premultiplied colors; darkens destination. */
68+
/** Product of premultiplied colors; darkens destination. r = s*d */
4469
MODULATE,
4570

46-
/** Multiply inverse of pixels, inverting result; brightens destination. */
71+
/** Multiply inverse of pixels, inverting result; brightens destination. r = s + d - s*d */
4772
SCREEN,
4873

4974
/** Multiply or screen, depending on destination. */
5075
OVERLAY,
5176

52-
/** Darker of source and destination. */
77+
/** Darker of source and destination. rc = s + d - max(s*da, d*sa), ra = kSrcOver */
5378
DARKEN,
5479

55-
/** Lighter of source and destination. */
80+
/** Lighter of source and destination. rc = s + d - min(s*da, d*sa), ra = kSrcOver */
5681
LIGHTEN,
5782

5883
/** Brighten destination to reflect source. */
@@ -67,13 +92,13 @@ enum class BlendMode {
6792
/** Lighten or darken, depending on source. */
6893
SOFT_LIGHT,
6994

70-
/** Subtract darker from lighter with higher contrast. */
95+
/** Subtract darker from lighter with higher contrast. rc = s + d - 2*(min(s*da, d*sa)), ra = kSrcOver */
7196
DIFFERENCE,
7297

73-
/** Subtract darker from lighter with lower contrast. */
98+
/** Subtract darker from lighter with lower contrast. rc = s + d - two(s*d), ra = kSrcOver */
7499
EXCLUSION,
75100

76-
/** Multiply source with destination, darkening image. */
101+
/** Multiply source with destination, darkening image. r = s*(1-da) + d*(1-sa) + s*d */
77102
MULTIPLY,
78103

79104
/** Hue of source with saturation and luminosity of destination. */
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.jetbrains.skia
2+
3+
import org.jetbrains.skia.impl.NativePointer
4+
import org.jetbrains.skia.impl.RefCnt
5+
import org.jetbrains.skia.impl.Stats
6+
import org.jetbrains.skia.impl.Library.Companion.staticLoad
7+
import org.jetbrains.skia.impl.interopScope
8+
9+
/**
10+
* Blender represents a custom blend function in the Skia pipeline. A blender combines a source
11+
* color (the result of our paint) and destination color (from the canvas) into a final color.
12+
*/
13+
class Blender internal constructor(ptr: NativePointer) : RefCnt(ptr) {
14+
companion object {
15+
init {
16+
staticLoad()
17+
}
18+
19+
fun makeMode(mode: BlendMode): Blender {
20+
Stats.onNativeCall()
21+
return Blender(_nMakeMode(mode.ordinal))
22+
}
23+
24+
fun makeArithmetic(
25+
k1: Float,
26+
k2: Float,
27+
k3: Float,
28+
k4: Float,
29+
enforcePMColor: Boolean,
30+
): Blender {
31+
Stats.onNativeCall()
32+
return interopScope {
33+
Blender(
34+
_nMakeArithmetic(
35+
k1,
36+
k2,
37+
k3,
38+
k4,
39+
enforcePMColor,
40+
),
41+
)
42+
}
43+
}
44+
}
45+
}
46+
47+
@ExternalSymbolName("org_jetbrains_skia_Blender__1nMakeArithmetic")
48+
private external fun _nMakeArithmetic(
49+
k1: Float,
50+
k2: Float,
51+
k3: Float,
52+
k4: Float,
53+
enforcePMColor: Boolean
54+
): NativePointer
55+
56+
@ExternalSymbolName("org_jetbrains_skia_Blender__1nMakeMode")
57+
private external fun _nMakeMode(mode: Int): NativePointer

skiko/src/commonMain/kotlin/org/jetbrains/skia/ColorFilter.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@ package org.jetbrains.skia
33
import org.jetbrains.skia.impl.Library.Companion.staticLoad
44
import org.jetbrains.skia.impl.*
55

6+
/**
7+
* ColorFilters are optional objects in the drawing pipeline. When present in
8+
* a paint, they are called with the "src" colors, and return new colors, which
9+
* are then passed onto the next stage (either ImageFilter or Xfermode).
10+
*
11+
* All subclasses are required to be reentrant-safe : it must be legal to share
12+
* the same instance between several threads.
13+
*/
614
class ColorFilter : RefCnt {
715
companion object {
816
fun makeComposed(outer: ColorFilter?, inner: ColorFilter?): ColorFilter {

skiko/src/commonMain/kotlin/org/jetbrains/skia/ImageFilter.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ package org.jetbrains.skia
33
import org.jetbrains.skia.impl.Library.Companion.staticLoad
44
import org.jetbrains.skia.impl.*
55

6+
/**
7+
* Base class for image filters. If one is installed in the paint, then all drawing occurs as
8+
* usual, but it is as if the drawing happened into an offscreen (before the xfermode is applied).
9+
* This offscreen bitmap will then be handed to the imagefilter, who in turn creates a new bitmap
10+
* which is what will finally be drawn to the device (using the original xfermode).
11+
*
12+
* The local space of image filters matches the local space of the drawn geometry. For instance if
13+
* there is rotation on the canvas, the blur will be computed along those rotated axes and not in
14+
* the device space. In order to achieve this result, the actual drawing of the geometry may happen
15+
* in an unrotated coordinate system so that the filtered image can be computed more easily, and
16+
* then it will be post transformed to match what would have been produced if the geometry were
17+
* drawn with the total canvas matrix to begin with.
18+
*/
619
class ImageFilter internal constructor(ptr: NativePointer) : RefCnt(ptr) {
720
companion object {
821

skiko/src/commonMain/kotlin/org/jetbrains/skia/MaskFilter.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ package org.jetbrains.skia
33
import org.jetbrains.skia.impl.*
44
import org.jetbrains.skia.impl.Library.Companion.staticLoad
55

6+
/**
7+
* MaskFilter is the base class for object that perform transformations on
8+
* the mask before drawing it. An example subclass is Blur.
9+
*/
610
class MaskFilter internal constructor(ptr: NativePointer) : RefCnt(ptr) {
711
companion object {
812
init {

skiko/src/commonMain/kotlin/org/jetbrains/skia/Paint.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,35 @@ class Paint : Managed {
447447
reachabilityBarrier(this)
448448
}
449449

450+
/**
451+
* Returns the user-supplied blend function, if one has been set.
452+
*
453+
* A null blender signifies the default SrcOver behavior.
454+
*
455+
* For convenience, you can call [blendMode] if the blend effect can be expressed
456+
* as one of those values.
457+
*
458+
* @see [https://fiddle.skia.org/c/@Paint_setBlender](https://fiddle.skia.org/c/@Paint_setBlender)
459+
* @see [https://fiddle.skia.org/c/@Paint_refBlender](https://fiddle.skia.org/c/@Paint_refBlender)
460+
*
461+
* @return the [Blender] assigned to this paint, otherwise null
462+
*/
463+
var blender: Blender?
464+
get() = try {
465+
Stats.onNativeCall()
466+
val blenderPtr = _nGetBlender(_ptr)
467+
if (blenderPtr == NullPointer) null else Blender(blenderPtr)
468+
} finally {
469+
reachabilityBarrier(this)
470+
}
471+
set(value) = try {
472+
Stats.onNativeCall()
473+
_nSetBlender(_ptr, getPtr(value))
474+
} finally {
475+
reachabilityBarrier(value)
476+
reachabilityBarrier(this)
477+
}
478+
450479
/**
451480
* @return true if BlendMode is BlendMode.SRC_OVER, the default.
452481
*/
@@ -665,5 +694,11 @@ private external fun _nGetImageFilter(ptr: NativePointer): NativePointer
665694
@ExternalSymbolName("org_jetbrains_skia_Paint__1nSetImageFilter")
666695
private external fun _nSetImageFilter(ptr: NativePointer, filterPtr: NativePointer)
667696

697+
@ExternalSymbolName("org_jetbrains_skia_Paint__1nGetBlender")
698+
private external fun _nGetBlender(ptr: NativePointer): NativePointer
699+
700+
@ExternalSymbolName("org_jetbrains_skia_Paint__1nSetBlender")
701+
private external fun _nSetBlender(ptr: NativePointer, blenderPtr: NativePointer)
702+
668703
@ExternalSymbolName("org_jetbrains_skia_Paint__1nHasNothingToDraw")
669704
private external fun _nHasNothingToDraw(ptr: NativePointer): Boolean

skiko/src/commonMain/kotlin/org/jetbrains/skia/RuntimeEffect.kt

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ class RuntimeEffect internal constructor(ptr: NativePointer) : RefCnt(ptr) {
1919
}
2020
}
2121

22+
fun makeForBlender(sksl: String): RuntimeEffect {
23+
Stats.onNativeCall()
24+
return interopScope {
25+
makeFromResultPtr(_nMakeForBlender(toInterop(sksl)))
26+
}
27+
}
28+
2229
init {
2330
staticLoad()
2431
}
@@ -42,6 +49,18 @@ class RuntimeEffect internal constructor(ptr: NativePointer) : RefCnt(ptr) {
4249
reachabilityBarrier(children)
4350
}
4451
}
52+
53+
fun makeBlender(uniforms: Data?): Blender {
54+
Stats.onNativeCall()
55+
return try {
56+
interopScope {
57+
Blender(_nMakeBlender(_ptr, getPtr(uniforms)))
58+
}
59+
} finally {
60+
reachabilityBarrier(this)
61+
reachabilityBarrier(uniforms)
62+
}
63+
}
4564
}
4665

4766
internal expect fun RuntimeEffect.Companion.makeFromResultPtr(ptr: NativePointer): RuntimeEffect
@@ -52,13 +71,20 @@ private external fun _nMakeShader(
5271
childCount: Int, localMatrix: InteropPointer
5372
): NativePointer
5473

74+
@ExternalSymbolName("org_jetbrains_skia_RuntimeEffect__1nMakeBlender")
75+
private external fun _nMakeBlender(
76+
runtimeEffectPtr: NativePointer, uniformPtr: NativePointer
77+
): NativePointer
5578

5679
@ExternalSymbolName("org_jetbrains_skia_RuntimeEffect__1nMakeForShader")
5780
private external fun _nMakeForShader(sksl: InteropPointer): NativePointer
5881

5982
@ExternalSymbolName("org_jetbrains_skia_RuntimeEffect__1nMakeForColorFilter")
6083
private external fun _nMakeForColorFilter(sksl: InteropPointer): NativePointer
6184

85+
@ExternalSymbolName("org_jetbrains_skia_RuntimeEffect__1nMakeForBlender")
86+
private external fun _nMakeForBlender(sksl: InteropPointer): NativePointer
87+
6288
// The functions below can be used only in JS and native targets
6389

6490
@ExternalSymbolName("org_jetbrains_skia_RuntimeEffect__1Result_nGetPtr")
@@ -68,4 +94,4 @@ internal external fun Result_nGetPtr(ptr: NativePointer): NativePointer
6894
internal external fun Result_nGetError(ptr: NativePointer): NativePointer
6995

7096
@ExternalSymbolName("org_jetbrains_skia_RuntimeEffect__1Result_nDestroy")
71-
internal external fun Result_nDestroy(ptr: NativePointer)
97+
internal external fun Result_nDestroy(ptr: NativePointer)

0 commit comments

Comments
 (0)