From a0be0274f1cf8d440a706da3bd2e158c245a2071 Mon Sep 17 00:00:00 2001 From: "Andres Gomez, Thomas (ITDV RL)" Date: Wed, 29 May 2024 11:28:45 +0200 Subject: [PATCH] Update the shader layer. --- .../rplexicon/ui/agsl/ChromaticAberration.kt | 148 ++++++++++++++++++ .../rplexicon/ui/agsl/DancingColor.kt | 86 ++++++++++ .../rplexicon/ui/agsl/FrostedGlass.kt | 78 +++++++++ .../rplexicon/ui/agsl/RandomNoise.kt | 55 +++++++ .../rplexicon/ui/agsl/rememberTimeState.kt | 19 +++ .../ui/screens/landing/LandingScreen.kt | 1 + .../ui/screens/rolls/composable/RollDice.kt | 22 +-- .../modifier/ModifierEx+CriticalShader.kt | 73 --------- 8 files changed, 399 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/agsl/ChromaticAberration.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/agsl/DancingColor.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/agsl/FrostedGlass.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/agsl/RandomNoise.kt create mode 100644 app/src/main/java/com/pixelized/rplexicon/ui/agsl/rememberTimeState.kt delete mode 100644 app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx+CriticalShader.kt diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/agsl/ChromaticAberration.kt b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/ChromaticAberration.kt new file mode 100644 index 0000000..74b0078 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/ChromaticAberration.kt @@ -0,0 +1,148 @@ +package com.pixelized.rplexicon.ui.agsl + +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Slider +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asComposeRenderEffect +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.pixelized.rplexicon.R +import org.intellij.lang.annotations.Language + + +@Language("AGSL") +val CHROMATIC_ABERRATION: String = """ + uniform shader composable; + uniform float displacement; + uniform float2 size; + + half4 main(float2 coord) { + half4 texture = composable.eval(coord); + + float2 distance = (coord - size * 0.5) * 2.0 / size; + distance = distance * abs(distance); + + half4 red = composable.eval(float2( + coord.x - displacement * distance.x, + coord.y - displacement * distance.y + )); + half4 blue = composable.eval(float2( + coord.x + displacement * distance.x, + coord.y + displacement * distance.y + )); + + texture.r = red.r; + texture.b = blue.b; + texture.a = max(texture.a, (red.a + blue.a) / 3.0); + return texture; + } +""".trimIndent() + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +@Preview +@Composable +private fun ChromaticAberrationD20() { + val shader = remember { RuntimeShader(CHROMATIC_ABERRATION) } + val displacement = remember { mutableFloatStateOf(25f) } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .graphicsLayer { + shader.setFloatUniform("displacement", displacement.floatValue) + shader.setFloatUniform("size", size.width, size.height) + + renderEffect = RenderEffect + .createRuntimeShaderEffect(shader, "composable") + .asComposeRenderEffect() + }, + ) { + Image( + modifier = Modifier + .fillMaxWidth() + .padding(all = 16.dp), + painter = painterResource(id = R.drawable.ic_d20_24), + contentScale = ContentScale.FillWidth, + contentDescription = null, + ) + } + + Slider( + modifier = Modifier.padding(horizontal = 16.dp), + value = displacement.floatValue, + valueRange = remember { 0f..100f }, + onValueChange = { displacement.floatValue = it }, + ) + } +} + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +@Preview +@Composable +private fun ChromaticAberrationImage() { + val shader = remember { RuntimeShader(CHROMATIC_ABERRATION) } + val displacement = remember { mutableFloatStateOf(25f) } + + Column( + modifier = Modifier.padding(all = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Box( + modifier = Modifier.graphicsLayer { + shader.setFloatUniform("displacement", displacement.floatValue) + shader.setFloatUniform("size", size.width, size.height) + clip = true + renderEffect = RenderEffect + .createRuntimeShaderEffect(shader, "composable") + .asComposeRenderEffect() + }, + ) { + Image( + modifier = Modifier.fillMaxWidth(), + painter = painterResource(id = R.drawable.im_naderius_background), + contentScale = ContentScale.FillWidth, + contentDescription = null, + ) + Image( + modifier = Modifier.fillMaxWidth(), + painter = painterResource(id = R.drawable.im_naderius_foreground), + contentScale = ContentScale.FillWidth, + contentDescription = null, + ) + Image( + modifier = Modifier + .fillMaxWidth() + .offset(y = (-16).dp), + painter = painterResource(id = R.drawable.im_naderius_dice), + contentScale = ContentScale.FillWidth, + contentDescription = null, + ) + } + + Slider( + value = displacement.floatValue, + valueRange = remember { 0f..100f }, + onValueChange = { displacement.floatValue = it }, + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/agsl/DancingColor.kt b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/DancingColor.kt new file mode 100644 index 0000000..e67d763 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/DancingColor.kt @@ -0,0 +1,86 @@ +package com.pixelized.rplexicon.ui.agsl + +import android.graphics.RuntimeShader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.CompositingStrategy +import androidx.compose.ui.graphics.ShaderBrush +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.pixelized.rplexicon.R +import org.intellij.lang.annotations.Language + +@Language("AGSL") +val DANCING_COLOR = """ + uniform float2 size; + uniform float time; + uniform float transition; + layout(color) uniform half4 tint; + + half4 main(float2 coord) { + // Normalized pixel coordinates (from 0 to 1) + half2 uv = coord.xy / size; + // Time varying pixel color + half2 wave = 0.5 + 0.5 * cos(time + uv.xy * 2.0 + half2(0, 3.14)); + half4 color = half4(wave, 1.0, 1.0); + // Output to screen + return mix(tint, color, transition); + } +""".trimIndent() + +fun Modifier.dancingColor( + transition: State, + time: State, + default: Color, +): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + this + .graphicsLayer { + compositingStrategy = CompositingStrategy.Offscreen + } + .drawWithCache { + val shader = RuntimeShader(DANCING_COLOR) + val brush = ShaderBrush(shader) + val color = android.graphics.Color.valueOf(default.red, default.green, default.blue) + shader.setColorUniform("tint", color) + shader.setFloatUniform("size", size.width, size.height) + shader.setFloatUniform("transition", transition.value) + + onDrawWithContent { + drawContent() + shader.setFloatUniform("time", time.value) + drawRect(brush = brush, blendMode = BlendMode.SrcAtop) + } + } +} else { + this +} + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +@Composable +@Preview +private fun DancingColor() { + Image( + modifier = Modifier + .fillMaxWidth() + .dancingColor( + transition = remember { mutableFloatStateOf(1.0f) }, + time = rememberTimeState(), + default = Color.White, + ), + painter = painterResource(id = R.drawable.ic_d20_24), + contentScale = ContentScale.FillWidth, + contentDescription = null, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/agsl/FrostedGlass.kt b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/FrostedGlass.kt new file mode 100644 index 0000000..8ba5cf2 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/FrostedGlass.kt @@ -0,0 +1,78 @@ +package com.pixelized.rplexicon.ui.agsl + +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.graphics.Shader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.Image +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asComposeRenderEffect +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.pixelized.rplexicon.R +import org.intellij.lang.annotations.Language + +// https://medium.com/androiddevelopers/agsl-made-in-the-shade-r-7d06d14fe02a + +@Language("AGSL") +val FROSTED_GLASS_SHADER = """ + uniform shader inputShader; + uniform float height; + uniform float width; + + vec4 main(vec2 coords) { + vec4 currValue = inputShader.eval(coords); + float top = height - 100; + if (coords.y < top) { + return currValue; + } else { + // Avoid blurring edges + if (coords.x > 1 && coords.y > 1 && + coords.x < (width - 1) && + coords.y < (height - 1)) { + // simple box blur - average 5x5 grid around pixel + vec4 boxSum = + inputShader.eval(coords + vec2(-2, -2)) + + // ... + currValue + + // ... + inputShader.eval(coords + vec2(2, 2)); + currValue = boxSum / 25; + } + + const vec4 white = vec4(1); + // top-left corner of label area + vec2 lefttop = vec2(0.0, top); + float lightenFactor = min(1.0, .6 * + length(coords - lefttop) / + (0.85 * length(vec2(width, 100)))); + // White in upper-left, blended increasingly + // toward lower-right + return mix(currValue, white, 1 - lightenFactor); + } + } +""" + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +@Composable +@Preview +private fun FrostedGlass() { + val shader = remember { RuntimeShader(FROSTED_GLASS_SHADER) } + + Image( + modifier = Modifier.graphicsLayer { + shader.setFloatUniform("width", size.width) + shader.setFloatUniform("height", size.height) + clip = true + val glass = RenderEffect.createRuntimeShaderEffect(shader, "inputShader") + val blur = RenderEffect.createBlurEffect(30f, 30f, Shader.TileMode.CLAMP) + renderEffect = RenderEffect.createChainEffect(blur, glass).asComposeRenderEffect() + }, + painter = painterResource(id = R.drawable.im_brulkhai), + contentDescription = null, + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/agsl/RandomNoise.kt b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/RandomNoise.kt new file mode 100644 index 0000000..378bf4d --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/RandomNoise.kt @@ -0,0 +1,55 @@ +package com.pixelized.rplexicon.ui.agsl + +import android.graphics.RenderEffect +import android.graphics.RuntimeShader +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asComposeRenderEffect +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.pixelized.rplexicon.R +import org.intellij.lang.annotations.Language + +@Language("AGSL") +val RANDOM_NOISE = """ + uniform shader composable; + uniform float2 size; + + float random(float2 uv) { + return fract(sin(dot(uv.xy, float2(12.9898, 78.233))) * 43758.5453123); + } + + half4 main(float2 coord) { + float2 uv = coord / size; + float noise = random(uv); + return half4(uv.x * noise, 0.0, uv.y * noise, 1.0); + } +""".trimIndent() + +@RequiresApi(Build.VERSION_CODES.TIRAMISU) +@Composable +@Preview +private fun RandomNoise() { + val shader = remember { RuntimeShader(RANDOM_NOISE) } + + Box( + modifier = Modifier + .fillMaxSize() + .graphicsLayer { + shader.setFloatUniform("size", size.width, size.height) + clip = true + val noise = RenderEffect + .createRuntimeShaderEffect(shader, "composable") + renderEffect = noise.asComposeRenderEffect() + }, + ) { + Image(painter = painterResource(id = R.drawable.im_brulkhai), contentDescription = null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/agsl/rememberTimeState.kt b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/rememberTimeState.kt new file mode 100644 index 0000000..b142593 --- /dev/null +++ b/app/src/main/java/com/pixelized/rplexicon/ui/agsl/rememberTimeState.kt @@ -0,0 +1,19 @@ +package com.pixelized.rplexicon.ui.agsl + +import androidx.compose.animation.core.withInfiniteAnimationFrameMillis +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.runtime.produceState + +@Composable +@Stable +fun rememberTimeState(speed: Float = 1f): State { + return produceState(0f) { + while (true) { + withInfiniteAnimationFrameMillis { + value = it / 1000f * speed + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt index b5aa102..4bc77ac 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/landing/LandingScreen.kt @@ -119,6 +119,7 @@ private fun LandingContent( .aspectRatio(1f) .clip(RectangleShape), ) { + Image( modifier = Modifier .matchParentSize() diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt index e57638b..e251e2c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/rolls/composable/RollDice.kt @@ -12,6 +12,7 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource @@ -21,7 +22,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -44,10 +45,10 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.agsl.dancingColor +import com.pixelized.rplexicon.ui.agsl.rememberTimeState import com.pixelized.rplexicon.ui.theme.LexiconTheme import com.pixelized.rplexicon.utilitary.extentions.lexicon -import com.pixelized.rplexicon.utilitary.extentions.modifier.criticalShader -import com.pixelized.rplexicon.utilitary.extentions.modifier.rememberITimeState @Stable @@ -120,25 +121,26 @@ private fun Dice( dice: State, ) { dice.value?.let { + val time = rememberTimeState() val animatedRotation = animateFloatAsState( targetValue = it.rotation * 360, animationSpec = it.animationSpec, label = "AnimatedRotation" ) - val isCritical = animateFloatAsState( + val transition = animateFloatAsState( targetValue = if (it.isCriticalSuccess) 1.0f else 0f, label = "animatedCritical", animationSpec = tween(1500), ) - Icon( + Image( modifier = modifier .rotate(degrees = animatedRotation.value % 360) - .criticalShader( - iDefault = MaterialTheme.colorScheme.primary, - isCritical = isCritical.value, - iTime = rememberITimeState().value, + .dancingColor( + time = time, + transition = transition, + default = MaterialTheme.colorScheme.primary, ), - tint = MaterialTheme.colorScheme.primary, + colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.primary), painter = painterResource(id = it.icon), contentDescription = null, ) diff --git a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx+CriticalShader.kt b/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx+CriticalShader.kt deleted file mode 100644 index 7c04054..0000000 --- a/app/src/main/java/com/pixelized/rplexicon/utilitary/extentions/modifier/ModifierEx+CriticalShader.kt +++ /dev/null @@ -1,73 +0,0 @@ -package com.pixelized.rplexicon.utilitary.extentions.modifier - -import android.graphics.Color.valueOf -import android.graphics.RuntimeShader -import android.os.Build -import androidx.compose.animation.core.withInfiniteAnimationFrameMillis -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.produceState -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithCache -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.CompositingStrategy -import androidx.compose.ui.graphics.ShaderBrush -import androidx.compose.ui.graphics.graphicsLayer -import org.intellij.lang.annotations.Language - -fun Modifier.criticalShader( - isCritical: Float, - iTime: Float, - iDefault: Color, -): Modifier = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - this - .graphicsLayer { - compositingStrategy = CompositingStrategy.Offscreen - } - .drawWithCache { - val shader = RuntimeShader(CRITICAL_SHADER) - val shaderBrush = ShaderBrush(shader) - shader.setFloatUniform("iResolution", size.width, size.height) - shader.setFloatUniform("isCritical", isCritical) - shader.setColorUniform("iDefault", valueOf(iDefault.red, iDefault.green, iDefault.blue)) - - onDrawWithContent { - drawContent() - shader.setFloatUniform("iTime", iTime) - drawRect(brush = shaderBrush, blendMode = BlendMode.SrcAtop) - } - } -} else { - this -} - -@Composable -@Stable -fun rememberITimeState(speed: Float = 1f): State { - return produceState(0f) { - while (true) { - withInfiniteAnimationFrameMillis { - value = it / 1000f * speed - } - } - } -} - -@Language("AGSL") -val CRITICAL_SHADER = """ - uniform float2 iResolution; - uniform float iTime; - uniform float isCritical; - layout(color) uniform half4 iDefault; - - half4 main(float2 fragCoord) { - // Normalized pixel coordinates (from 0 to 1) - float2 uv = fragCoord.xy / iResolution; - // Time varying pixel color - float2 color = 0.5 + 0.5 * cos(iTime + uv.xy * 2.0 + float2(0, 3.14)); - // Output to screen - return mix(iDefault, float4(color, 1.0, 1.0), isCritical); - } -""".trimIndent() \ No newline at end of file