Update the shader layer.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-05-29 11:28:45 +02:00
parent 223ae9f02b
commit a0be0274f1
8 changed files with 399 additions and 83 deletions

View file

@ -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 },
)
}
}

View file

@ -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<Float>,
time: State<Float>,
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,
)
}

View file

@ -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,
)
}

View file

@ -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)
}
}

View file

@ -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<Float> {
return produceState(0f) {
while (true) {
withInfiniteAnimationFrameMillis {
value = it / 1000f * speed
}
}
}
}

View file

@ -119,6 +119,7 @@ private fun LandingContent(
.aspectRatio(1f)
.clip(RectangleShape),
) {
Image(
modifier = Modifier
.matchParentSize()

View file

@ -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<RollDiceUio?>,
) {
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,
)

View file

@ -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<Float> {
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()