JetBrains / compose-multiplatform

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.
https://jetbrains.com/lp/compose-multiplatform
Apache License 2.0
16.15k stars 1.17k forks source link

Glow Draw #3585

Closed SudoDios closed 1 year ago

SudoDios commented 1 year ago

Hi. how to draw with shadowLayer (glow effect) on canvas ? skia paint not have this !! like this : glowCircle_0

m-sasha commented 1 year ago

Is that something that works on Android? Can you post the code for it?

SudoDios commented 1 year ago

@m-sasha

@Preview(name = "Glowing Circle")
@Composable
fun GlowCircle() {

    Canvas(
        modifier = Modifier.size(120.dp),
        onDraw = {

            val center = size.minDimension / 2f

            val paint = Paint()
            val frameworkPaint = paint.asFrameworkPaint()
            frameworkPaint.style = android.graphics.Paint.Style.STROKE
            frameworkPaint.color = Color.WHITE
            frameworkPaint.strokeWidth = 8f

            //this is not in skia paint
            frameworkPaint.setShadowLayer(28f,0f,0f, Color.WHITE)

            drawIntoCanvas {
                it.drawCircle(center = Offset(center,center), radius = center - 30f, paint = paint)
            }

        }
    )

}

Screenshot from 2023-08-30 18-00-58

m-sasha commented 1 year ago

Indeed there doesn't appear to be a setShadowLayer function in Skia, so we won't be able to support it. But maybe you can use other primitives to achieve the same effect. For example:

@Composable
fun GlowCircle() {
    Canvas(
        modifier = Modifier.size(120.dp),
        onDraw = {

            val center = size.minDimension / 2f

            val paint = Paint()
            val frameworkPaint = paint.asFrameworkPaint()
            frameworkPaint.mode = PaintMode.STROKE
            frameworkPaint.color = Color.White.toArgb()
            frameworkPaint.strokeWidth = 8f

//            frameworkPaint.setShadowLayer(28f,0f,0f, Color.WHITE)
            frameworkPaint.maskFilter = MaskFilter.makeBlur(FilterBlurMode.SOLID, 28f)

            drawIntoCanvas {
                it.drawCircle(center = Offset(center,center), radius = center - 30f, paint = paint)
            }
        }
    )
}

seems to produce a similar result:

image
SudoDios commented 1 year ago

How interesting Thank you 👌.

elijah-semyonov commented 1 year ago

Also you can roll custom shader:

import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.*
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.*
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import org.jetbrains.skia.FilterTileMode
import org.jetbrains.skia.ImageFilter
import org.jetbrains.skia.RuntimeEffect
import org.jetbrains.skia.RuntimeShaderBuilder

val shader = """
    uniform shader content;    

    uniform vec2 resolution;    

    vec4 main(vec2 coord) {
        vec2 normalized_coord = saturate(coord / resolution);
        vec2 m0_coord = normalized_coord * 2.0 - 1.0;
        float m0_length = length(m0_coord);        

        float ring_radius = 0.7;
        float ring_distance = abs(m0_length - ring_radius);
        float ring_width = 0.2;
        float normalized_ring_distance = saturate(ring_distance / ring_width);
        float result = pow(1.0 - normalized_ring_distance, 2.0);

        return vec4(result, result, result, 1.0);        
    }
"""

@Composable
fun App() {
    val effect = RuntimeEffect.makeForShader(shader)
    val builder = RuntimeShaderBuilder(effect)

    val density = LocalDensity.current.density
    val size = 100
    builder.uniform(
        "resolution",
        size * density, size * density
    )

    Box(
        Modifier
            .fillMaxSize()
            .graphicsLayer(
                renderEffect = ImageFilter.makeRuntimeShader(
                    runtimeShaderBuilder = builder,
                    shaderNames = arrayOf("content"),
                    inputs = arrayOf(
                        null
                    )
                ).asComposeRenderEffect(),
            )
    ) {
    }
}

fun main() = application {
    Window(onCloseRequest = ::exitApplication) {
        App()
    }
}

image

okushnikov commented 3 months ago

Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks.