Introduced skikoMain sourceset for skiko dependent platforms
Moved compose/ui/ui-graphics to skikoMain
Test: jvmTest, desktopTest
Change-Id: I8e900a36fbacf09fae1d94190e83ae5c5c8ba67b
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt b/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
index 5138c91..9fdedb5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/java/JavaCompileInputs.kt
@@ -80,7 +80,7 @@
sourceSets
.filter { it.name.contains("main", ignoreCase = true) }
// TODO(igotti): come up with better filtering for non-Android sources.
- .filterNot { it.name == "desktopMain" }
+ .filterNot { it.name == "desktopMain" || it.name == "skikoMain" }
.flatMap { it.kotlin.sourceDirectories }
.also { require(it.isNotEmpty()) }
} ?: project.provider {
diff --git a/compose/ui/ui-graphics/build.gradle b/compose/ui/ui-graphics/build.gradle
index 5fd756c..26340f2 100644
--- a/compose/ui/ui-graphics/build.gradle
+++ b/compose/ui/ui-graphics/build.gradle
@@ -84,10 +84,18 @@
api("androidx.annotation:annotation:1.2.0")
}
- desktopMain.dependencies {
- implementation(libs.kotlinStdlib)
- implementation(libs.kotlinStdlibJdk8)
- api(libs.skiko)
+ skikoMain {
+ dependencies {
+ api(libs.skikoCommon)
+ }
+ }
+
+ desktopMain {
+ dependsOn skikoMain
+ dependencies {
+ implementation(libs.kotlinStdlib)
+ implementation(libs.kotlinStdlibJdk8)
+ }
}
androidTest.dependencies {
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopColorFilter.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopColorFilter.desktop.kt
index 8612ddf..e9591c3 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopColorFilter.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopColorFilter.desktop.kt
@@ -18,8 +18,6 @@
import org.jetbrains.skia.ColorFilter as SkiaColorFilter
-actual typealias NativeColorFilter = SkiaColorFilter
-
/**
* Obtain a reference to the desktop ColorFilter type
*/
@@ -29,25 +27,5 @@
/**
* Obtain a [org.jetbrains.skia.ColorFilter] instance from this [ColorFilter]
*/
-fun ColorFilter.asSkiaColorFilter(): SkiaColorFilter = nativeColorFilter
-
@Deprecated("Use asComposeColorFilter()", replaceWith = ReplaceWith("asComposeColorFilter()"))
-fun org.jetbrains.skia.ColorFilter.toComposeColorFilter(): ColorFilter = ColorFilter(this)
-
-/**
- * Create a [ColorFilter] from the given [org.jetbrains.skia.ColorFilter] instance
- */
-fun org.jetbrains.skia.ColorFilter.asComposeColorFilter(): ColorFilter = ColorFilter(this)
-
-internal actual fun actualTintColorFilter(color: Color, blendMode: BlendMode): ColorFilter =
- ColorFilter(SkiaColorFilter.makeBlend(color.toArgb(), blendMode.toSkia()))
-
-internal actual fun actualColorMatrixColorFilter(colorMatrix: ColorMatrix): ColorFilter =
- ColorFilter(
- SkiaColorFilter.makeMatrix(
- org.jetbrains.skia.ColorMatrix(*colorMatrix.values)
- )
- )
-
-internal actual fun actualLightingColorFilter(multiply: Color, add: Color): ColorFilter =
- ColorFilter(SkiaColorFilter.makeLighting(multiply.toArgb(), add.toArgb()))
\ No newline at end of file
+fun SkiaColorFilter.toComposeColorFilter(): ColorFilter = ColorFilter(this)
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.desktop.kt
index 7099247..fd0bd46 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopImageAsset.desktop.kt
@@ -16,17 +16,10 @@
package androidx.compose.ui.graphics
-import androidx.compose.ui.graphics.colorspace.ColorSpace
-import androidx.compose.ui.graphics.colorspace.ColorSpaces
import org.jetbrains.skia.Bitmap
-import org.jetbrains.skia.ColorAlphaType
-import org.jetbrains.skia.ColorInfo
-import org.jetbrains.skia.ColorType
import org.jetbrains.skia.Image
-import org.jetbrains.skia.ImageInfo
import java.nio.ByteBuffer
import java.nio.ByteOrder
-import kotlin.math.abs
/**
* Create an [ImageBitmap] from the given [Bitmap]. Note this does
@@ -34,51 +27,13 @@
* will modify the returned [ImageBitmap]
*/
@Deprecated("Use asComposeImageBitmap", replaceWith = ReplaceWith("asComposeImageBitmap()"))
-fun Bitmap.asImageBitmap(): ImageBitmap = DesktopImageBitmap(this)
+fun Bitmap.asImageBitmap(): ImageBitmap = asComposeImageBitmap()
/**
* Create an [ImageBitmap] from the given [Image].
*/
@Deprecated("Use toComposeImageBitmap", replaceWith = ReplaceWith("toComposeImageBitmap()"))
-fun Image.asImageBitmap(): ImageBitmap = DesktopImageBitmap(toBitmap())
-
-/**
- * Create an [ImageBitmap] from the given [Bitmap]. Note this does
- * not create a copy of the original [Bitmap] and changes to it
- * will modify the returned [ImageBitmap]
- */
-fun Bitmap.asComposeImageBitmap(): ImageBitmap = DesktopImageBitmap(this)
-
-/**
- * Create an [ImageBitmap] from the given [Image].
- */
-fun Image.toComposeImageBitmap(): ImageBitmap = DesktopImageBitmap(toBitmap())
-
-private fun Image.toBitmap(): Bitmap {
- val bitmap = Bitmap()
- bitmap.allocPixels(ImageInfo.makeN32(width, height, ColorAlphaType.PREMUL))
- val canvas = org.jetbrains.skia.Canvas(bitmap)
- canvas.drawImage(this, 0f, 0f)
- bitmap.setImmutable()
- return bitmap
-}
-
-internal actual fun ActualImageBitmap(
- width: Int,
- height: Int,
- config: ImageBitmapConfig,
- hasAlpha: Boolean,
- colorSpace: ColorSpace
-): ImageBitmap {
- val colorType = config.toSkiaColorType()
- val alphaType = if (hasAlpha) ColorAlphaType.PREMUL else ColorAlphaType.OPAQUE
- val skiaColorSpace = colorSpace.toSkiaColorSpace()
- val colorInfo = ColorInfo(colorType, alphaType, skiaColorSpace)
- val imageInfo = ImageInfo(colorInfo, width, height)
- val bitmap = Bitmap()
- bitmap.allocPixels(imageInfo)
- return DesktopImageBitmap(bitmap)
-}
+fun Image.asImageBitmap(): ImageBitmap = toComposeImageBitmap()
/**
* @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
@@ -87,98 +42,9 @@
@Deprecated("Use asSkiaBitmap()", replaceWith = ReplaceWith("asSkiaBitmap()"))
fun ImageBitmap.asDesktopBitmap(): Bitmap = asSkiaBitmap()
-/**
- * Obtain a reference to the [org.jetbrains.skia.Bitmap]
- *
- * @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
- * org.jetbrains.skia.Image
- */
-fun ImageBitmap.asSkiaBitmap(): Bitmap =
- when (this) {
- is DesktopImageBitmap -> bitmap
- else -> throw UnsupportedOperationException("Unable to obtain org.jetbrains.skia.Image")
- }
-
-private class DesktopImageBitmap(val bitmap: Bitmap) : ImageBitmap {
- override val colorSpace = bitmap.colorSpace.toComposeColorSpace()
- override val config = bitmap.colorType.toComposeConfig()
- override val hasAlpha = !bitmap.isOpaque
- override val height get() = bitmap.height
- override val width get() = bitmap.width
- override fun prepareToDraw() = Unit
-
- override fun readPixels(
- buffer: IntArray,
- startX: Int,
- startY: Int,
- width: Int,
- height: Int,
- bufferOffset: Int,
- stride: Int
- ) {
- // similar to https://cs.android.com/android/platform/superproject/+/42c50042d1f05d92ecc57baebe3326a57aeecf77:frameworks/base/graphics/java/android/graphics/Bitmap.java;l=2007
- val lastScanline: Int = bufferOffset + (height - 1) * stride
- require(startX >= 0 && startY >= 0)
- require(width > 0 && startX + width <= this.width)
- require(height > 0 && startY + height <= this.height)
- require(abs(stride) >= width)
- require(bufferOffset >= 0 && bufferOffset + width <= buffer.size)
- require(lastScanline >= 0 && lastScanline + width <= buffer.size)
-
- // similar to https://cs.android.com/android/platform/superproject/+/9054ca2b342b2ea902839f629e820546d8a2458b:frameworks/base/libs/hwui/jni/Bitmap.cpp;l=898;bpv=1
- val colorInfo = ColorInfo(
- ColorType.BGRA_8888,
- ColorAlphaType.UNPREMUL,
- org.jetbrains.skia.ColorSpace.sRGB
- )
- val imageInfo = ImageInfo(colorInfo, width, height)
- val bytesPerPixel = 4
- val bytes = bitmap.readPixels(imageInfo, stride * bytesPerPixel.toLong(), startX, startY)!!
-
- ByteBuffer.wrap(bytes)
- .order(ByteOrder.LITTLE_ENDIAN) // to return ARGB
- .asIntBuffer()
- .get(buffer, bufferOffset, bytes.size / bytesPerPixel)
- }
+internal actual fun ByteArray.putBytesInto(array: IntArray, offset: Int, length: Int) {
+ ByteBuffer.wrap(this)
+ .order(ByteOrder.LITTLE_ENDIAN) // to return ARGB
+ .asIntBuffer()
+ .get(array, offset, length)
}
-
-// TODO(demin): [API] maybe we should use:
-// `else -> throw UnsupportedOperationException()`
-// in toSkiaColorType/toComposeConfig/toComposeColorSpace/toSkiaColorSpace
-// see [https://android-review.googlesource.com/c/platform/frameworks/support/+/1429835/comment/c219501b_63c3d1fe/]
-
-private fun ImageBitmapConfig.toSkiaColorType() = when (this) {
- ImageBitmapConfig.Argb8888 -> ColorType.N32
- ImageBitmapConfig.Alpha8 -> ColorType.ALPHA_8
- ImageBitmapConfig.Rgb565 -> ColorType.RGB_565
- ImageBitmapConfig.F16 -> ColorType.RGBA_F16
- else -> ColorType.N32
-}
-
-private fun ColorType.toComposeConfig() = when (this) {
- ColorType.N32 -> ImageBitmapConfig.Argb8888
- ColorType.ALPHA_8 -> ImageBitmapConfig.Alpha8
- ColorType.RGB_565 -> ImageBitmapConfig.Rgb565
- ColorType.RGBA_F16 -> ImageBitmapConfig.F16
- else -> ImageBitmapConfig.Argb8888
-}
-
-private fun org.jetbrains.skia.ColorSpace?.toComposeColorSpace(): ColorSpace {
- return when (this) {
- org.jetbrains.skia.ColorSpace.sRGB -> ColorSpaces.Srgb
- org.jetbrains.skia.ColorSpace.sRGBLinear -> ColorSpaces.LinearSrgb
- org.jetbrains.skia.ColorSpace.displayP3 -> ColorSpaces.DisplayP3
- else -> ColorSpaces.Srgb
- }
-}
-
-// TODO(demin): support all color spaces.
-// to do this we need to implement SkColorSpace::MakeRGB in skia
-private fun ColorSpace.toSkiaColorSpace(): org.jetbrains.skia.ColorSpace {
- return when (this) {
- ColorSpaces.Srgb -> org.jetbrains.skia.ColorSpace.sRGB
- ColorSpaces.LinearSrgb -> org.jetbrains.skia.ColorSpace.sRGBLinear
- ColorSpaces.DisplayP3 -> org.jetbrains.skia.ColorSpace.displayP3
- else -> org.jetbrains.skia.ColorSpace.sRGB
- }
-}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.desktop.kt
index d0c6172..14e0070 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPath.desktop.kt
@@ -16,197 +16,6 @@
package androidx.compose.ui.graphics
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.geometry.RoundRect
-import org.jetbrains.skia.Matrix33
-import org.jetbrains.skia.PathDirection
-import org.jetbrains.skia.PathFillMode
-import org.jetbrains.skia.PathOp
-
-actual fun Path(): Path = DesktopPath()
-
-/**
- * Convert the [org.jetbrains.skia.Path] instance into a Compose-compatible Path
- */
-fun org.jetbrains.skia.Path.asComposePath(): Path = DesktopPath(this)
-
@Suppress("NOTHING_TO_INLINE")
@Deprecated("Use asSkiaPath()", replaceWith = ReplaceWith("asSkiaPath()"))
inline fun Path.asDesktopPath(): org.jetbrains.skia.Path = asSkiaPath()
-
-/**
- * Obtain a reference to the [org.jetbrains.skia.Path]
- *
- * @Throws UnsupportedOperationException if this Path is not backed by an org.jetbrains.skia.Path
- */
-fun Path.asSkiaPath(): org.jetbrains.skia.Path =
- if (this is DesktopPath) {
- internalPath
- } else {
- throw UnsupportedOperationException("Unable to obtain org.jetbrains.skia.Path")
- }
-
-internal class DesktopPath(
- internalPath: org.jetbrains.skia.Path = org.jetbrains.skia.Path()
-) : Path {
- var internalPath = internalPath
- private set
-
- override var fillType: PathFillType
- get() {
- if (internalPath.fillMode == PathFillMode.EVEN_ODD) {
- return PathFillType.EvenOdd
- } else {
- return PathFillType.NonZero
- }
- }
-
- set(value) {
- internalPath.fillMode =
- if (value == PathFillType.EvenOdd) {
- PathFillMode.EVEN_ODD
- } else {
- PathFillMode.WINDING
- }
- }
-
- override fun moveTo(x: Float, y: Float) {
- internalPath.moveTo(x, y)
- }
-
- override fun relativeMoveTo(dx: Float, dy: Float) {
- internalPath.rMoveTo(dx, dy)
- }
-
- override fun lineTo(x: Float, y: Float) {
- internalPath.lineTo(x, y)
- }
-
- override fun relativeLineTo(dx: Float, dy: Float) {
- internalPath.rLineTo(dx, dy)
- }
-
- override fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) {
- internalPath.quadTo(x1, y1, x2, y2)
- }
-
- override fun relativeQuadraticBezierTo(dx1: Float, dy1: Float, dx2: Float, dy2: Float) {
- internalPath.rQuadTo(dx1, dy1, dx2, dy2)
- }
-
- override fun cubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) {
- internalPath.cubicTo(
- x1, y1,
- x2, y2,
- x3, y3
- )
- }
-
- override fun relativeCubicTo(
- dx1: Float,
- dy1: Float,
- dx2: Float,
- dy2: Float,
- dx3: Float,
- dy3: Float
- ) {
- internalPath.rCubicTo(
- dx1, dy1,
- dx2, dy2,
- dx3, dy3
- )
- }
-
- override fun arcTo(
- rect: Rect,
- startAngleDegrees: Float,
- sweepAngleDegrees: Float,
- forceMoveTo: Boolean
- ) {
- internalPath.arcTo(
- rect.toSkiaRect(),
- startAngleDegrees,
- sweepAngleDegrees,
- forceMoveTo
- )
- }
-
- override fun addRect(rect: Rect) {
- internalPath.addRect(rect.toSkiaRect(), PathDirection.COUNTER_CLOCKWISE)
- }
-
- override fun addOval(oval: Rect) {
- internalPath.addOval(oval.toSkiaRect(), PathDirection.COUNTER_CLOCKWISE)
- }
-
- override fun addArcRad(oval: Rect, startAngleRadians: Float, sweepAngleRadians: Float) {
- addArc(oval, degrees(startAngleRadians), degrees(sweepAngleRadians))
- }
-
- override fun addArc(oval: Rect, startAngleDegrees: Float, sweepAngleDegrees: Float) {
- internalPath.addArc(oval.toSkiaRect(), startAngleDegrees, sweepAngleDegrees)
- }
-
- override fun addRoundRect(roundRect: RoundRect) {
- internalPath.addRRect(roundRect.toSkiaRRect(), PathDirection.COUNTER_CLOCKWISE)
- }
-
- override fun addPath(path: Path, offset: Offset) {
- internalPath.addPath(path.asSkiaPath(), offset.x, offset.y)
- }
-
- override fun close() {
- internalPath.closePath()
- }
-
- override fun reset() {
- // preserve fillType to match the Android behavior
- // see https://cs.android.com/android/_/android/platform/frameworks/base/+/d0f379c1976c600313f1f4c39f2587a649e3a4fc
- val fillType = this.fillType
- internalPath.reset()
- this.fillType = fillType
- }
-
- override fun translate(offset: Offset) {
- internalPath.transform(Matrix33.makeTranslate(offset.x, offset.y))
- }
-
- override fun getBounds(): Rect {
- val bounds = internalPath.bounds
- return Rect(
- bounds.left,
- bounds.top,
- bounds.right,
- bounds.bottom
- )
- }
-
- override fun op(
- path1: Path,
- path2: Path,
- operation: PathOperation
- ): Boolean {
- val path = org.jetbrains.skia.Path.makeCombining(
- path1.asSkiaPath(),
- path2.asSkiaPath(),
- operation.toSkiaOperation()
- )
-
- internalPath = path ?: internalPath
- return path != null
- }
-
- private fun PathOperation.toSkiaOperation() = when (this) {
- PathOperation.Difference -> PathOp.DIFFERENCE
- PathOperation.Intersect -> PathOp.INTERSECT
- PathOperation.Union -> PathOp.UNION
- PathOperation.Xor -> PathOp.XOR
- PathOperation.ReverseDifference -> PathOp.REVERSE_DIFFERENCE
- else -> PathOp.XOR
- }
-
- override val isConvex: Boolean get() = internalPath.isConvex
-
- override val isEmpty: Boolean get() = internalPath.isEmpty
-}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.desktop.kt
index 22d5e8f..b2c0fac 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathEffect.desktop.kt
@@ -16,57 +16,10 @@
package androidx.compose.ui.graphics
-import org.jetbrains.skia.PathEffect as SkiaPathEffect
-
-internal class DesktopPathEffect(val nativePathEffect: SkiaPathEffect) : PathEffect
-
-/**
- * Convert the [org.jetbrains.skia.PathEffect] instance into a Compose-compatible PathEffect
- */
-fun SkiaPathEffect.asComposePathEffect(): PathEffect = DesktopPathEffect(this)
+import org.jetbrains.skia.PathEffect as SkPathEffect
/**
* Obtain a reference to the desktop PathEffect type
*/
@Deprecated("Use asSkiaPathEffect()", replaceWith = ReplaceWith("asSkiaPathEffect()"))
-fun PathEffect.asDesktopPathEffect(): SkiaPathEffect = asSkiaPathEffect()
-
-/**
- * Obtain a reference to the desktop PathEffect type
- */
-fun PathEffect.asSkiaPathEffect(): SkiaPathEffect =
- (this as DesktopPathEffect).nativePathEffect
-
-internal actual fun actualCornerPathEffect(radius: Float): PathEffect =
- DesktopPathEffect(SkiaPathEffect.makeCorner(radius))
-
-internal actual fun actualDashPathEffect(
- intervals: FloatArray,
- phase: Float
-): PathEffect = DesktopPathEffect(SkiaPathEffect.makeDash(intervals, phase))
-
-internal actual fun actualChainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
- DesktopPathEffect(outer.asSkiaPathEffect().makeCompose(inner.asSkiaPathEffect()))
-
-internal actual fun actualStampedPathEffect(
- shape: Path,
- advance: Float,
- phase: Float,
- style: StampedPathEffectStyle
-): PathEffect =
- DesktopPathEffect(
- SkiaPathEffect.makePath1D(
- shape.asSkiaPath(),
- advance,
- phase,
- style.toSkiaStampedPathEffectStyle()
- )
- )
-
-internal fun StampedPathEffectStyle.toSkiaStampedPathEffectStyle(): SkiaPathEffect.Style =
- when (this) {
- StampedPathEffectStyle.Morph -> SkiaPathEffect.Style.MORPH
- StampedPathEffectStyle.Rotate -> SkiaPathEffect.Style.ROTATE
- StampedPathEffectStyle.Translate -> SkiaPathEffect.Style.TRANSLATE
- else -> SkiaPathEffect.Style.TRANSLATE
- }
\ No newline at end of file
+fun PathEffect.asDesktopPathEffect(): SkPathEffect = asSkiaPathEffect()
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/RenderEffect.desktop.kt b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/RenderEffect.desktop.kt
index fea46c1..e278ef0 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/RenderEffect.desktop.kt
+++ b/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/RenderEffect.desktop.kt
@@ -16,141 +16,7 @@
package androidx.compose.ui.graphics
-import androidx.compose.runtime.Immutable
-import androidx.compose.ui.geometry.Offset
import org.jetbrains.skia.ImageFilter
-/**
- * Convert the [ImageFilter] instance into a Compose-compatible [RenderEffect]
- */
-fun ImageFilter.asComposeRenderEffect(): RenderEffect =
- DesktopRenderEffect(this)
-
-/**
- * Intermediate rendering step used to render drawing commands with a corresponding
- * visual effect. A [RenderEffect] can be configured on a [GraphicsLayerScope]
- * and will be applied when drawn.
- */
-@Immutable
-actual sealed class RenderEffect actual constructor() {
-
- private var internalImageFilter: ImageFilter? = null
-
- @Deprecated("Use asSkiaImageFilter()", replaceWith = ReplaceWith("asSkiaImageFilter()"))
- fun asDesktopImageFilter(): ImageFilter = asSkiaImageFilter()
-
- fun asSkiaImageFilter(): ImageFilter =
- internalImageFilter ?: createImageFilter().also { internalImageFilter = it }
-
- protected abstract fun createImageFilter(): ImageFilter
-
- /**
- * Capability query to determine if the particular platform supports the [RenderEffect]. Not
- * all platforms support all render effects
- */
- actual open fun isSupported(): Boolean = true
-}
-
-@Immutable
-internal class DesktopRenderEffect(
- val imageFilter: ImageFilter
-) : RenderEffect() {
- override fun createImageFilter(): ImageFilter = imageFilter
-}
-
-@Immutable
-actual class BlurEffect actual constructor(
- private val renderEffect: RenderEffect?,
- private val radiusX: Float,
- private val radiusY: Float,
- private val edgeTreatment: TileMode
-) : RenderEffect() {
-
- override fun createImageFilter(): ImageFilter =
- if (renderEffect == null) {
- ImageFilter.makeBlur(
- convertRadiusToSigma(radiusX),
- convertRadiusToSigma(radiusY),
- edgeTreatment.toSkiaTileMode()
- )
- } else {
- ImageFilter.makeBlur(
- convertRadiusToSigma(radiusX),
- convertRadiusToSigma(radiusY),
- edgeTreatment.toSkiaTileMode(),
- renderEffect.asSkiaImageFilter(),
- null
- )
- }
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is BlurEffect) return false
-
- if (radiusX != other.radiusX) return false
- if (radiusY != other.radiusY) return false
- if (edgeTreatment != other.edgeTreatment) return false
- if (renderEffect != other.renderEffect) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = renderEffect?.hashCode() ?: 0
- result = 31 * result + radiusX.hashCode()
- result = 31 * result + radiusY.hashCode()
- result = 31 * result + edgeTreatment.hashCode()
- return result
- }
-
- override fun toString(): String {
- return "BlurEffect(renderEffect=$renderEffect, radiusX=$radiusX, radiusY=$radiusY, " +
- "edgeTreatment=$edgeTreatment)"
- }
-
- companion object {
-
- // Constant used to convert blur radius into a corresponding sigma value
- // for the gaussian blur algorithm used within SkImageFilter.
- // This constant approximates the scaling done in the software path's
- // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
- val BlurSigmaScale = 0.57735f
-
- fun convertRadiusToSigma(radius: Float) =
- if (radius > 0) {
- BlurSigmaScale * radius + 0.5f
- } else {
- 0.0f
- }
- }
-}
-
-@Immutable
-actual class OffsetEffect actual constructor(
- private val renderEffect: RenderEffect?,
- private val offset: Offset
-) : RenderEffect() {
-
- override fun createImageFilter(): ImageFilter =
- ImageFilter.makeOffset(offset.x, offset.y, renderEffect?.asSkiaImageFilter(), null)
-
- override fun equals(other: Any?): Boolean {
- if (this === other) return true
- if (other !is OffsetEffect) return false
-
- if (renderEffect != other.renderEffect) return false
- if (offset != other.offset) return false
-
- return true
- }
-
- override fun hashCode(): Int {
- var result = renderEffect?.hashCode() ?: 0
- result = 31 * result + offset.hashCode()
- return result
- }
-
- override fun toString(): String {
- return "OffsetEffect(renderEffect=$renderEffect, offset=$offset)"
- }
-}
\ No newline at end of file
+@Deprecated("Use asSkiaImageFilter()", replaceWith = ReplaceWith("asSkiaImageFilter()"))
+fun RenderEffect.asDesktopImageFilter(): ImageFilter = asSkiaImageFilter()
diff --git a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt
index e894d93..5c9a60a 100644
--- a/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt
+++ b/compose/ui/ui-graphics/src/desktopTest/kotlin/androidx/compose/ui/graphics/DesktopGraphicsTest.kt
@@ -41,7 +41,7 @@
protected fun initCanvas(widthPx: Int, heightPx: Int): Canvas {
require(_surface == null)
_surface = Surface.makeRasterN32Premul(widthPx, heightPx)
- return DesktopCanvas(_surface!!.canvas)
+ return SkiaBackedCanvas(_surface!!.canvas)
}
@After
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/BlendMode.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/BlendMode.skiko.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/BlendMode.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/BlendMode.skiko.kt
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Matrices.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/Matrices.skiko.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Matrices.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/Matrices.skiko.kt
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Rects.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/Rects.skiko.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/Rects.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/Rects.skiko.kt
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedCanvas.skiko.kt
similarity index 91%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedCanvas.skiko.kt
index c5fb5ce..b2b81f3 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopCanvas.desktop.kt
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedCanvas.skiko.kt
@@ -29,9 +29,9 @@
import org.jetbrains.skia.Matrix44
import org.jetbrains.skia.MipmapMode
import org.jetbrains.skia.SamplingMode
-import org.jetbrains.skia.ClipMode as SkiaClipMode
-import org.jetbrains.skia.RRect as SkiaRRect
-import org.jetbrains.skia.Rect as SkiaRect
+import org.jetbrains.skia.ClipMode as SkClipMode
+import org.jetbrains.skia.RRect as SkRRect
+import org.jetbrains.skia.Rect as SkRect
actual typealias NativeCanvas = org.jetbrains.skia.Canvas
@@ -40,18 +40,18 @@
require(!skiaBitmap.isImmutable) {
"Cannot draw on immutable ImageBitmap"
}
- return DesktopCanvas(org.jetbrains.skia.Canvas(skiaBitmap))
+ return SkiaBackedCanvas(org.jetbrains.skia.Canvas(skiaBitmap))
}
/**
* Convert the [org.jetbrains.skia.Canvas] instance into a Compose-compatible Canvas
*/
-fun org.jetbrains.skia.Canvas.asComposeCanvas(): Canvas = DesktopCanvas(this)
+fun org.jetbrains.skia.Canvas.asComposeCanvas(): Canvas = SkiaBackedCanvas(this)
-actual val Canvas.nativeCanvas: NativeCanvas get() = (this as DesktopCanvas).skia
+actual val Canvas.nativeCanvas: NativeCanvas get() = (this as SkiaBackedCanvas).skia
-internal class DesktopCanvas(val skia: org.jetbrains.skia.Canvas) : Canvas {
- private val Paint.skia get() = (this as DesktopPaint).skia
+internal class SkiaBackedCanvas(val skia: org.jetbrains.skia.Canvas) : Canvas {
+ private val Paint.skia get() = (this as SkiaBackedPaint).skia
override fun save() {
skia.save()
@@ -95,7 +95,7 @@
override fun clipRect(left: Float, top: Float, right: Float, bottom: Float, clipOp: ClipOp) {
val antiAlias = true
- skia.clipRect(SkiaRect.makeLTRB(left, top, right, bottom), clipOp.toSkia(), antiAlias)
+ skia.clipRect(SkRect.makeLTRB(left, top, right, bottom), clipOp.toSkia(), antiAlias)
}
override fun clipPath(path: Path, clipOp: ClipOp) {
@@ -108,7 +108,7 @@
}
override fun drawRect(left: Float, top: Float, right: Float, bottom: Float, paint: Paint) {
- skia.drawRect(SkiaRect.makeLTRB(left, top, right, bottom), paint.skia)
+ skia.drawRect(SkRect.makeLTRB(left, top, right, bottom), paint.skia)
}
override fun drawRoundRect(
@@ -121,7 +121,7 @@
paint: Paint
) {
skia.drawRRect(
- SkiaRRect.makeLTRB(
+ SkRRect.makeLTRB(
left,
top,
right,
@@ -134,7 +134,7 @@
}
override fun drawOval(left: Float, top: Float, right: Float, bottom: Float, paint: Paint) {
- skia.drawOval(SkiaRect.makeLTRB(left, top, right, bottom), paint.skia)
+ skia.drawOval(SkRect.makeLTRB(left, top, right, bottom), paint.skia)
}
override fun drawCircle(center: Offset, radius: Float, paint: Paint) {
@@ -203,13 +203,13 @@
Image.makeFromBitmap(bitmap).use { skiaImage ->
skia.drawImageRect(
skiaImage,
- SkiaRect.makeXYWH(
+ SkRect.makeXYWH(
srcOffset.x,
srcOffset.y,
srcSize.width,
srcSize.height
),
- SkiaRect.makeXYWH(
+ SkRect.makeXYWH(
dstOffset.x,
dstOffset.y,
dstSize.width,
@@ -348,9 +348,9 @@
}
private fun ClipOp.toSkia() = when (this) {
- ClipOp.Difference -> SkiaClipMode.DIFFERENCE
- ClipOp.Intersect -> SkiaClipMode.INTERSECT
- else -> SkiaClipMode.INTERSECT
+ ClipOp.Difference -> SkClipMode.DIFFERENCE
+ ClipOp.Intersect -> SkClipMode.INTERSECT
+ else -> SkClipMode.INTERSECT
}
private fun Matrix.toSkia() = Matrix44(
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPaint.skiko.kt
similarity index 75%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPaint.skiko.kt
index 0d61dc0..e9df393 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPaint.desktop.kt
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPaint.skiko.kt
@@ -16,20 +16,20 @@
package androidx.compose.ui.graphics
-import org.jetbrains.skia.PaintMode as SkiaPaintMode
-import org.jetbrains.skia.PaintStrokeCap as SkiaPaintStrokeCap
-import org.jetbrains.skia.PaintStrokeJoin as SkiaPaintStrokeJoin
+import org.jetbrains.skia.PaintMode as SkPaintMode
+import org.jetbrains.skia.PaintStrokeCap as SkPaintStrokeCap
+import org.jetbrains.skia.PaintStrokeJoin as SkPaintStrokeJoin
actual typealias NativePaint = org.jetbrains.skia.Paint
-actual fun Paint(): Paint = DesktopPaint()
+actual fun Paint(): Paint = SkiaBackedPaint()
/**
* Convert the [org.jetbrains.skia.Paint] instance into a Compose-compatible Paint
*/
-fun org.jetbrains.skia.Paint.asComposePaint(): Paint = DesktopPaint(this)
+fun org.jetbrains.skia.Paint.asComposePaint(): Paint = SkiaBackedPaint(this)
-internal class DesktopPaint(
+internal class SkiaBackedPaint(
val skia: org.jetbrains.skia.Paint = org.jetbrains.skia.Paint()
) : Paint {
override fun asFrameworkPaint(): NativePaint = skia
@@ -104,28 +104,28 @@
override var pathEffect: PathEffect? = null
set(value) {
- skia.pathEffect = (value as DesktopPathEffect?)?.asSkiaPathEffect()
+ skia.pathEffect = (value as SkiaBackedPathEffect?)?.asSkiaPathEffect()
field = value
}
private fun PaintingStyle.toSkia() = when (this) {
- PaintingStyle.Fill -> SkiaPaintMode.FILL
- PaintingStyle.Stroke -> SkiaPaintMode.STROKE
- else -> SkiaPaintMode.FILL
+ PaintingStyle.Fill -> SkPaintMode.FILL
+ PaintingStyle.Stroke -> SkPaintMode.STROKE
+ else -> SkPaintMode.FILL
}
private fun StrokeCap.toSkia() = when (this) {
- StrokeCap.Butt -> SkiaPaintStrokeCap.BUTT
- StrokeCap.Round -> SkiaPaintStrokeCap.ROUND
- StrokeCap.Square -> SkiaPaintStrokeCap.SQUARE
- else -> SkiaPaintStrokeCap.BUTT
+ StrokeCap.Butt -> SkPaintStrokeCap.BUTT
+ StrokeCap.Round -> SkPaintStrokeCap.ROUND
+ StrokeCap.Square -> SkPaintStrokeCap.SQUARE
+ else -> SkPaintStrokeCap.BUTT
}
private fun StrokeJoin.toSkia() = when (this) {
- StrokeJoin.Miter -> SkiaPaintStrokeJoin.MITER
- StrokeJoin.Round -> SkiaPaintStrokeJoin.ROUND
- StrokeJoin.Bevel -> SkiaPaintStrokeJoin.BEVEL
- else -> SkiaPaintStrokeJoin.MITER
+ StrokeJoin.Miter -> SkPaintStrokeJoin.MITER
+ StrokeJoin.Round -> SkPaintStrokeJoin.ROUND
+ StrokeJoin.Bevel -> SkPaintStrokeJoin.BEVEL
+ else -> SkPaintStrokeJoin.MITER
}
}
diff --git a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPath.skiko.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPath.skiko.kt
new file mode 100644
index 0000000..5552b80
--- /dev/null
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPath.skiko.kt
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.RoundRect
+import org.jetbrains.skia.Matrix33
+import org.jetbrains.skia.PathDirection
+import org.jetbrains.skia.PathFillMode
+import org.jetbrains.skia.PathOp
+
+actual fun Path(): Path = SkiaBackedPath()
+
+/**
+ * Convert the [org.jetbrains.skia.Path] instance into a Compose-compatible Path
+ */
+fun org.jetbrains.skia.Path.asComposePath(): Path = SkiaBackedPath(this)
+
+/**
+ * Obtain a reference to the [org.jetbrains.skia.Path]
+ *
+ * @Throws UnsupportedOperationException if this Path is not backed by an org.jetbrains.skia.Path
+ */
+fun Path.asSkiaPath(): org.jetbrains.skia.Path =
+ if (this is SkiaBackedPath) {
+ internalPath
+ } else {
+ throw UnsupportedOperationException("Unable to obtain org.jetbrains.skia.Path")
+ }
+
+internal class SkiaBackedPath(
+ internalPath: org.jetbrains.skia.Path = org.jetbrains.skia.Path()
+) : Path {
+ var internalPath = internalPath
+ private set
+
+ override var fillType: PathFillType
+ get() {
+ if (internalPath.fillMode == PathFillMode.EVEN_ODD) {
+ return PathFillType.EvenOdd
+ } else {
+ return PathFillType.NonZero
+ }
+ }
+
+ set(value) {
+ internalPath.fillMode =
+ if (value == PathFillType.EvenOdd) {
+ PathFillMode.EVEN_ODD
+ } else {
+ PathFillMode.WINDING
+ }
+ }
+
+ override fun moveTo(x: Float, y: Float) {
+ internalPath.moveTo(x, y)
+ }
+
+ override fun relativeMoveTo(dx: Float, dy: Float) {
+ internalPath.rMoveTo(dx, dy)
+ }
+
+ override fun lineTo(x: Float, y: Float) {
+ internalPath.lineTo(x, y)
+ }
+
+ override fun relativeLineTo(dx: Float, dy: Float) {
+ internalPath.rLineTo(dx, dy)
+ }
+
+ override fun quadraticBezierTo(x1: Float, y1: Float, x2: Float, y2: Float) {
+ internalPath.quadTo(x1, y1, x2, y2)
+ }
+
+ override fun relativeQuadraticBezierTo(dx1: Float, dy1: Float, dx2: Float, dy2: Float) {
+ internalPath.rQuadTo(dx1, dy1, dx2, dy2)
+ }
+
+ override fun cubicTo(x1: Float, y1: Float, x2: Float, y2: Float, x3: Float, y3: Float) {
+ internalPath.cubicTo(
+ x1, y1,
+ x2, y2,
+ x3, y3
+ )
+ }
+
+ override fun relativeCubicTo(
+ dx1: Float,
+ dy1: Float,
+ dx2: Float,
+ dy2: Float,
+ dx3: Float,
+ dy3: Float
+ ) {
+ internalPath.rCubicTo(
+ dx1, dy1,
+ dx2, dy2,
+ dx3, dy3
+ )
+ }
+
+ override fun arcTo(
+ rect: Rect,
+ startAngleDegrees: Float,
+ sweepAngleDegrees: Float,
+ forceMoveTo: Boolean
+ ) {
+ internalPath.arcTo(
+ rect.toSkiaRect(),
+ startAngleDegrees,
+ sweepAngleDegrees,
+ forceMoveTo
+ )
+ }
+
+ override fun addRect(rect: Rect) {
+ internalPath.addRect(rect.toSkiaRect(), PathDirection.COUNTER_CLOCKWISE)
+ }
+
+ override fun addOval(oval: Rect) {
+ internalPath.addOval(oval.toSkiaRect(), PathDirection.COUNTER_CLOCKWISE)
+ }
+
+ override fun addArcRad(oval: Rect, startAngleRadians: Float, sweepAngleRadians: Float) {
+ addArc(oval, degrees(startAngleRadians), degrees(sweepAngleRadians))
+ }
+
+ override fun addArc(oval: Rect, startAngleDegrees: Float, sweepAngleDegrees: Float) {
+ internalPath.addArc(oval.toSkiaRect(), startAngleDegrees, sweepAngleDegrees)
+ }
+
+ override fun addRoundRect(roundRect: RoundRect) {
+ internalPath.addRRect(roundRect.toSkiaRRect(), PathDirection.COUNTER_CLOCKWISE)
+ }
+
+ override fun addPath(path: Path, offset: Offset) {
+ internalPath.addPath(path.asSkiaPath(), offset.x, offset.y)
+ }
+
+ override fun close() {
+ internalPath.closePath()
+ }
+
+ override fun reset() {
+ // preserve fillType to match the Android behavior
+ // see https://cs.android.com/android/_/android/platform/frameworks/base/+/d0f379c1976c600313f1f4c39f2587a649e3a4fc
+ val fillType = this.fillType
+ internalPath.reset()
+ this.fillType = fillType
+ }
+
+ override fun translate(offset: Offset) {
+ internalPath.transform(Matrix33.makeTranslate(offset.x, offset.y))
+ }
+
+ override fun getBounds(): Rect {
+ val bounds = internalPath.bounds
+ return Rect(
+ bounds.left,
+ bounds.top,
+ bounds.right,
+ bounds.bottom
+ )
+ }
+
+ override fun op(
+ path1: Path,
+ path2: Path,
+ operation: PathOperation
+ ): Boolean {
+ val path = org.jetbrains.skia.Path.makeCombining(
+ path1.asSkiaPath(),
+ path2.asSkiaPath(),
+ operation.toSkiaOperation()
+ )
+
+ internalPath = path ?: internalPath
+ return path != null
+ }
+
+ private fun PathOperation.toSkiaOperation() = when (this) {
+ PathOperation.Difference -> PathOp.DIFFERENCE
+ PathOperation.Intersect -> PathOp.INTERSECT
+ PathOperation.Union -> PathOp.UNION
+ PathOperation.Xor -> PathOp.XOR
+ PathOperation.ReverseDifference -> PathOp.REVERSE_DIFFERENCE
+ else -> PathOp.XOR
+ }
+
+ override val isConvex: Boolean get() = internalPath.isConvex
+
+ override val isEmpty: Boolean get() = internalPath.isEmpty
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathEffect.skiko.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathEffect.skiko.kt
new file mode 100644
index 0000000..cd96db9
--- /dev/null
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathEffect.skiko.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import org.jetbrains.skia.PathEffect as SkPathEffect
+
+internal class SkiaBackedPathEffect(val nativePathEffect: SkPathEffect) : PathEffect
+
+/**
+ * Convert the [org.jetbrains.skia.PathEffect] instance into a Compose-compatible PathEffect
+ */
+fun SkPathEffect.asComposePathEffect(): PathEffect = SkiaBackedPathEffect(this)
+
+/**
+ * Obtain a reference to skia PathEffect type
+ */
+fun PathEffect.asSkiaPathEffect(): SkPathEffect =
+ (this as SkiaBackedPathEffect).nativePathEffect
+
+internal actual fun actualCornerPathEffect(radius: Float): PathEffect =
+ SkiaBackedPathEffect(SkPathEffect.makeCorner(radius))
+
+internal actual fun actualDashPathEffect(
+ intervals: FloatArray,
+ phase: Float
+): PathEffect = SkiaBackedPathEffect(SkPathEffect.makeDash(intervals, phase))
+
+internal actual fun actualChainPathEffect(outer: PathEffect, inner: PathEffect): PathEffect =
+ SkiaBackedPathEffect(outer.asSkiaPathEffect().makeCompose(inner.asSkiaPathEffect()))
+
+internal actual fun actualStampedPathEffect(
+ shape: Path,
+ advance: Float,
+ phase: Float,
+ style: StampedPathEffectStyle
+): PathEffect =
+ SkiaBackedPathEffect(
+ SkPathEffect.makePath1D(
+ shape.asSkiaPath(),
+ advance,
+ phase,
+ style.toSkiaStampedPathEffectStyle()
+ )
+ )
+
+internal fun StampedPathEffectStyle.toSkiaStampedPathEffectStyle(): SkPathEffect.Style =
+ when (this) {
+ StampedPathEffectStyle.Morph -> SkPathEffect.Style.MORPH
+ StampedPathEffectStyle.Rotate -> SkPathEffect.Style.ROTATE
+ StampedPathEffectStyle.Translate -> SkPathEffect.Style.TRANSLATE
+ else -> SkPathEffect.Style.TRANSLATE
+ }
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathMeasure.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt
similarity index 88%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathMeasure.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt
index f348407..7658c68 100644
--- a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopPathMeasure.desktop.kt
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedPathMeasure.skiko.kt
@@ -19,15 +19,15 @@
/**
* Convert the [org.jetbrains.skia.PathMeasure] instance into a Compose-compatible PathMeasure
*/
-fun org.jetbrains.skia.PathMeasure.asComposePathEffect(): PathMeasure = DesktopPathMeasure(this)
+fun org.jetbrains.skia.PathMeasure.asComposePathEffect(): PathMeasure = SkiaBackedPathMeasure(this)
/**
- * Obtain a reference to the desktop PathMeasure type
+ * Obtain a reference to skia PathMeasure type
*/
fun PathMeasure.asSkiaPathMeasure(): org.jetbrains.skia.PathMeasure =
- (this as DesktopPathMeasure).skia
+ (this as SkiaBackedPathMeasure).skia
-internal class DesktopPathMeasure(
+internal class SkiaBackedPathMeasure(
internal val skia: org.jetbrains.skia.PathMeasure = org.jetbrains.skia.PathMeasure()
) : PathMeasure {
@@ -52,4 +52,4 @@
}
actual fun PathMeasure(): PathMeasure =
- DesktopPathMeasure()
\ No newline at end of file
+ SkiaBackedPathMeasure()
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedRenderEffect.skiko.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedRenderEffect.skiko.kt
new file mode 100644
index 0000000..8d6e519
--- /dev/null
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaBackedRenderEffect.skiko.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.runtime.Immutable
+import androidx.compose.ui.geometry.Offset
+import org.jetbrains.skia.ImageFilter
+
+/**
+ * Convert the [ImageFilter] instance into a Compose-compatible [RenderEffect]
+ */
+fun ImageFilter.asComposeRenderEffect(): RenderEffect =
+ SkiaBackedRenderEffect(this)
+
+/**
+ * Intermediate rendering step used to render drawing commands with a corresponding
+ * visual effect. A [RenderEffect] can be configured on a [GraphicsLayerScope]
+ * and will be applied when drawn.
+ */
+@Immutable
+actual sealed class RenderEffect actual constructor() {
+
+ private var internalImageFilter: ImageFilter? = null
+
+ fun asSkiaImageFilter(): ImageFilter =
+ internalImageFilter ?: createImageFilter().also { internalImageFilter = it }
+
+ protected abstract fun createImageFilter(): ImageFilter
+
+ /**
+ * Capability query to determine if the particular platform supports the [RenderEffect]. Not
+ * all platforms support all render effects
+ */
+ actual open fun isSupported(): Boolean = true
+}
+
+@Immutable
+internal class SkiaBackedRenderEffect(
+ val imageFilter: ImageFilter
+) : RenderEffect() {
+ override fun createImageFilter(): ImageFilter = imageFilter
+}
+
+@Immutable
+actual class BlurEffect actual constructor(
+ private val renderEffect: RenderEffect?,
+ private val radiusX: Float,
+ private val radiusY: Float,
+ private val edgeTreatment: TileMode
+) : RenderEffect() {
+
+ override fun createImageFilter(): ImageFilter =
+ if (renderEffect == null) {
+ ImageFilter.makeBlur(
+ convertRadiusToSigma(radiusX),
+ convertRadiusToSigma(radiusY),
+ edgeTreatment.toSkiaTileMode()
+ )
+ } else {
+ ImageFilter.makeBlur(
+ convertRadiusToSigma(radiusX),
+ convertRadiusToSigma(radiusY),
+ edgeTreatment.toSkiaTileMode(),
+ renderEffect.asSkiaImageFilter(),
+ null
+ )
+ }
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is BlurEffect) return false
+
+ if (radiusX != other.radiusX) return false
+ if (radiusY != other.radiusY) return false
+ if (edgeTreatment != other.edgeTreatment) return false
+ if (renderEffect != other.renderEffect) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = renderEffect?.hashCode() ?: 0
+ result = 31 * result + radiusX.hashCode()
+ result = 31 * result + radiusY.hashCode()
+ result = 31 * result + edgeTreatment.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "BlurEffect(renderEffect=$renderEffect, radiusX=$radiusX, radiusY=$radiusY, " +
+ "edgeTreatment=$edgeTreatment)"
+ }
+
+ companion object {
+
+ // Constant used to convert blur radius into a corresponding sigma value
+ // for the gaussian blur algorithm used within SkImageFilter.
+ // This constant approximates the scaling done in the software path's
+ // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)).
+ val BlurSigmaScale = 0.57735f
+
+ fun convertRadiusToSigma(radius: Float) =
+ if (radius > 0) {
+ BlurSigmaScale * radius + 0.5f
+ } else {
+ 0.0f
+ }
+ }
+}
+
+@Immutable
+actual class OffsetEffect actual constructor(
+ private val renderEffect: RenderEffect?,
+ private val offset: Offset
+) : RenderEffect() {
+
+ override fun createImageFilter(): ImageFilter =
+ ImageFilter.makeOffset(offset.x, offset.y, renderEffect?.asSkiaImageFilter(), null)
+
+ override fun equals(other: Any?): Boolean {
+ if (this === other) return true
+ if (other !is OffsetEffect) return false
+
+ if (renderEffect != other.renderEffect) return false
+ if (offset != other.offset) return false
+
+ return true
+ }
+
+ override fun hashCode(): Int {
+ var result = renderEffect?.hashCode() ?: 0
+ result = 31 * result + offset.hashCode()
+ return result
+ }
+
+ override fun toString(): String {
+ return "OffsetEffect(renderEffect=$renderEffect, offset=$offset)"
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaColorFilter.skiko.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaColorFilter.skiko.kt
new file mode 100644
index 0000000..2e598a0
--- /dev/null
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaColorFilter.skiko.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import org.jetbrains.skia.ColorFilter as SkiaColorFilter
+
+actual typealias NativeColorFilter = SkiaColorFilter
+
+/**
+ * Obtain a [org.jetbrains.skia.ColorFilter] instance from this [ColorFilter]
+ */
+fun ColorFilter.asSkiaColorFilter(): SkiaColorFilter = nativeColorFilter
+
+/**
+ * Create a [ColorFilter] from the given [org.jetbrains.skia.ColorFilter] instance
+ */
+fun org.jetbrains.skia.ColorFilter.asComposeColorFilter(): ColorFilter = ColorFilter(this)
+
+internal actual fun actualTintColorFilter(color: Color, blendMode: BlendMode): ColorFilter =
+ ColorFilter(SkiaColorFilter.makeBlend(color.toArgb(), blendMode.toSkia()))
+
+internal actual fun actualColorMatrixColorFilter(colorMatrix: ColorMatrix): ColorFilter =
+ ColorFilter(
+ SkiaColorFilter.makeMatrix(
+ org.jetbrains.skia.ColorMatrix(*colorMatrix.values)
+ )
+ )
+
+internal actual fun actualLightingColorFilter(multiply: Color, add: Color): ColorFilter =
+ ColorFilter(SkiaColorFilter.makeLighting(multiply.toArgb(), add.toArgb()))
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaImageAsset.skiko.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaImageAsset.skiko.kt
new file mode 100644
index 0000000..bc92d05
--- /dev/null
+++ b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaImageAsset.skiko.kt
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.graphics
+
+import androidx.compose.ui.graphics.colorspace.ColorSpace
+import androidx.compose.ui.graphics.colorspace.ColorSpaces
+import org.jetbrains.skia.Bitmap
+import org.jetbrains.skia.ColorAlphaType
+import org.jetbrains.skia.ColorInfo
+import org.jetbrains.skia.ColorType
+import org.jetbrains.skia.Image
+import org.jetbrains.skia.ImageInfo
+import kotlin.math.abs
+
+/**
+ * Create an [ImageBitmap] from the given [Bitmap]. Note this does
+ * not create a copy of the original [Bitmap] and changes to it
+ * will modify the returned [ImageBitmap]
+ */
+fun Bitmap.asComposeImageBitmap(): ImageBitmap = SkiaBackedImageBitmap(this)
+
+/**
+ * Create an [ImageBitmap] from the given [Image].
+ */
+fun Image.toComposeImageBitmap(): ImageBitmap = SkiaBackedImageBitmap(toBitmap())
+
+private fun Image.toBitmap(): Bitmap {
+ val bitmap = Bitmap()
+ bitmap.allocPixels(ImageInfo.makeN32(width, height, ColorAlphaType.PREMUL))
+ val canvas = org.jetbrains.skia.Canvas(bitmap)
+ canvas.drawImage(this, 0f, 0f)
+ bitmap.setImmutable()
+ return bitmap
+}
+
+internal actual fun ActualImageBitmap(
+ width: Int,
+ height: Int,
+ config: ImageBitmapConfig,
+ hasAlpha: Boolean,
+ colorSpace: ColorSpace
+): ImageBitmap {
+ val colorType = config.toSkiaColorType()
+ val alphaType = if (hasAlpha) ColorAlphaType.PREMUL else ColorAlphaType.OPAQUE
+ val skiaColorSpace = colorSpace.toSkiaColorSpace()
+ val colorInfo = ColorInfo(colorType, alphaType, skiaColorSpace)
+ val imageInfo = ImageInfo(colorInfo, width, height)
+ val bitmap = Bitmap()
+ bitmap.allocPixels(imageInfo)
+ return SkiaBackedImageBitmap(bitmap)
+}
+
+/**
+ * Obtain a reference to the [org.jetbrains.skia.Bitmap]
+ *
+ * @Throws UnsupportedOperationException if this [ImageBitmap] is not backed by an
+ * org.jetbrains.skia.Image
+ */
+fun ImageBitmap.asSkiaBitmap(): Bitmap =
+ when (this) {
+ is SkiaBackedImageBitmap -> bitmap
+ else -> throw UnsupportedOperationException("Unable to obtain org.jetbrains.skia.Image")
+ }
+
+private class SkiaBackedImageBitmap(val bitmap: Bitmap) : ImageBitmap {
+ override val colorSpace = bitmap.colorSpace.toComposeColorSpace()
+ override val config = bitmap.colorType.toComposeConfig()
+ override val hasAlpha = !bitmap.isOpaque
+ override val height get() = bitmap.height
+ override val width get() = bitmap.width
+ override fun prepareToDraw() = Unit
+
+ override fun readPixels(
+ buffer: IntArray,
+ startX: Int,
+ startY: Int,
+ width: Int,
+ height: Int,
+ bufferOffset: Int,
+ stride: Int
+ ) {
+ // similar to https://cs.android.com/android/platform/superproject/+/42c50042d1f05d92ecc57baebe3326a57aeecf77:frameworks/base/graphics/java/android/graphics/Bitmap.java;l=2007
+ val lastScanline: Int = bufferOffset + (height - 1) * stride
+ require(startX >= 0 && startY >= 0)
+ require(width > 0 && startX + width <= this.width)
+ require(height > 0 && startY + height <= this.height)
+ require(abs(stride) >= width)
+ require(bufferOffset >= 0 && bufferOffset + width <= buffer.size)
+ require(lastScanline >= 0 && lastScanline + width <= buffer.size)
+
+ // similar to https://cs.android.com/android/platform/superproject/+/9054ca2b342b2ea902839f629e820546d8a2458b:frameworks/base/libs/hwui/jni/Bitmap.cpp;l=898;bpv=1
+ val colorInfo = ColorInfo(
+ ColorType.BGRA_8888,
+ ColorAlphaType.UNPREMUL,
+ org.jetbrains.skia.ColorSpace.sRGB
+ )
+ val imageInfo = ImageInfo(colorInfo, width, height)
+ val bytesPerPixel = 4
+ val bytes = bitmap.readPixels(imageInfo, stride * bytesPerPixel.toLong(), startX, startY)!!
+ bytes.putBytesInto(buffer, bufferOffset, bytes.size / bytesPerPixel)
+ }
+}
+
+internal expect fun ByteArray.putBytesInto(array: IntArray, offset: Int, length: Int)
+
+// TODO(demin): [API] maybe we should use:
+// `else -> throw UnsupportedOperationException()`
+// in toSkiaColorType/toComposeConfig/toComposeColorSpace/toSkiaColorSpace
+// see [https://android-review.googlesource.com/c/platform/frameworks/support/+/1429835/comment/c219501b_63c3d1fe/]
+
+private fun ImageBitmapConfig.toSkiaColorType() = when (this) {
+ ImageBitmapConfig.Argb8888 -> ColorType.N32
+ ImageBitmapConfig.Alpha8 -> ColorType.ALPHA_8
+ ImageBitmapConfig.Rgb565 -> ColorType.RGB_565
+ ImageBitmapConfig.F16 -> ColorType.RGBA_F16
+ else -> ColorType.N32
+}
+
+private fun ColorType.toComposeConfig() = when (this) {
+ ColorType.N32 -> ImageBitmapConfig.Argb8888
+ ColorType.ALPHA_8 -> ImageBitmapConfig.Alpha8
+ ColorType.RGB_565 -> ImageBitmapConfig.Rgb565
+ ColorType.RGBA_F16 -> ImageBitmapConfig.F16
+ else -> ImageBitmapConfig.Argb8888
+}
+
+private fun org.jetbrains.skia.ColorSpace?.toComposeColorSpace(): ColorSpace {
+ return when (this) {
+ org.jetbrains.skia.ColorSpace.sRGB -> ColorSpaces.Srgb
+ org.jetbrains.skia.ColorSpace.sRGBLinear -> ColorSpaces.LinearSrgb
+ org.jetbrains.skia.ColorSpace.displayP3 -> ColorSpaces.DisplayP3
+ else -> ColorSpaces.Srgb
+ }
+}
+
+// TODO(demin): support all color spaces.
+// to do this we need to implement SkColorSpace::MakeRGB in skia
+private fun ColorSpace.toSkiaColorSpace(): org.jetbrains.skia.ColorSpace {
+ return when (this) {
+ ColorSpaces.Srgb -> org.jetbrains.skia.ColorSpace.sRGB
+ ColorSpaces.LinearSrgb -> org.jetbrains.skia.ColorSpace.sRGBLinear
+ ColorSpaces.DisplayP3 -> org.jetbrains.skia.ColorSpace.displayP3
+ else -> org.jetbrains.skia.ColorSpace.sRGB
+ }
+}
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopShader.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaShader.skiko.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopShader.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaShader.skiko.kt
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopTileMode.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaTileMode.skiko.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopTileMode.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaTileMode.skiko.kt
diff --git a/compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopVertexMode.desktop.kt b/compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaVertexMode.skiko.kt
similarity index 100%
rename from compose/ui/ui-graphics/src/desktopMain/kotlin/androidx/compose/ui/graphics/DesktopVertexMode.desktop.kt
rename to compose/ui/ui-graphics/src/skikoMain/kotlin/androidx/compose/ui/graphics/SkiaVertexMode.skiko.kt
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 9ec2be1..8887613 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -150,6 +150,7 @@
rxjava3 = { module = "io.reactivex.rxjava3:rxjava", version = "3.0.0" }
shadow = { module = "com.github.jengelman.gradle.plugins:shadow", version = "6.1.0" }
skiko = { module = "org.jetbrains.skiko:skiko-jvm", version.ref = "skiko" }
+skikoCommon = { module = "org.jetbrains.skiko:skiko", version.ref = "skiko" }
skikoMacOsArm64 = { module = "org.jetbrains.skiko:skiko-jvm-runtime-macos-arm64", version.ref = "skiko" }
skikoMacOsX64 = { module = "org.jetbrains.skiko:skiko-jvm-runtime-macos-x64", version.ref = "skiko" }
skikoWindowsX64 = { module = "org.jetbrains.skiko:skiko-jvm-runtime-windows-x64", version.ref = "skiko" }