Loading packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +9 −1 Original line number Diff line number Diff line Loading @@ -181,6 +181,11 @@ private constructor( turbulenceNoiseShader.setColor(newColor) } /** Updates the noise color that's screen blended on top. */ fun updateScreenColor(newColor: Int) { turbulenceNoiseShader.setScreenColor(newColor) } /** * Retrieves the noise offset x, y, z values. This is useful for replaying the animation * smoothly from the last animation, by passing in the last values to the next animation. Loading Loading @@ -322,7 +327,10 @@ private constructor( private fun draw() { paintCallback?.onDraw(paint!!) renderEffectCallback?.onDraw( RenderEffect.createRuntimeShaderEffect(turbulenceNoiseShader, "in_src") RenderEffect.createRuntimeShaderEffect( turbulenceNoiseShader, TurbulenceNoiseShader.BACKGROUND_UNIFORM ) ) } Loading packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +3 −3 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ data class TurbulenceNoiseAnimationConfig( /** Color of the effect. */ val color: Int = DEFAULT_COLOR, /** Background color of the effect. */ val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR, val screenColor: Int = DEFAULT_SCREEN_COLOR, val width: Float = 0f, val height: Float = 0f, val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS, Loading @@ -72,7 +72,7 @@ data class TurbulenceNoiseAnimationConfig( */ val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS, /** Whether to flip the luma mask. */ val shouldInverseNoiseLuminosity: Boolean = false val shouldInverseNoiseLuminosity: Boolean = false, ) { companion object { const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec Loading @@ -83,7 +83,7 @@ data class TurbulenceNoiseAnimationConfig( const val DEFAULT_COLOR = Color.WHITE const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f const val DEFAULT_BACKGROUND_COLOR = Color.BLACK const val DEFAULT_SCREEN_COLOR = Color.BLACK private val random = Random() } } packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +110 −25 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.surfaceeffects.turbulencenoise import android.graphics.RuntimeShader import com.android.systemui.surfaceeffects.shaders.SolidColorShader import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary import java.lang.Float.max Loading @@ -28,9 +29,11 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : RuntimeShader(getShader(baseType)) { // language=AGSL companion object { /** Uniform name for the background buffer (e.g. image, solid color, etc.). */ const val BACKGROUND_UNIFORM = "in_src" private const val UNIFORMS = """ uniform shader in_src; // Needed to support RenderEffect. uniform shader ${BACKGROUND_UNIFORM}; uniform float in_gridNum; uniform vec3 in_noiseMove; uniform vec2 in_size; Loading @@ -41,7 +44,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : uniform half in_lumaMatteBlendFactor; uniform half in_lumaMatteOverallBrightness; layout(color) uniform vec4 in_color; layout(color) uniform vec4 in_backgroundColor; layout(color) uniform vec4 in_screenColor; """ private const val SIMPLEX_SHADER = Loading @@ -50,22 +53,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : vec2 uv = p / in_size.xy; uv.x *= in_aspectRatio; // Compute turbulence effect with the uv distorted with simplex noise. vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; // Bring it to [0, 1] range. float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5; luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); // Blend the result with the background color. color = in_src.eval(p).rgb + color * 0.6; // Add dither with triangle distribution to avoid color banding. Dither in the // shader here as we are in gamma space. float dither = triangleNoise(p * in_pixelDensity) / 255.; color += dither.rrr; // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to // multiply rgb with a to get the correct result. color = (color + dither.rrr) * in_opacity; return vec4(color, in_opacity); // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A]. return vec4(color * in_opacity, in_opacity); } """ Loading @@ -76,32 +77,105 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; // Bring it to [0, 1] range. float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5; luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma); // Blend the result with the background color. color = in_src.eval(p).rgb + color * 0.6; // Skip dithering. return vec4(color * in_opacity, in_opacity); } """ /** * This effect has two layers: color turbulence effect with sparkles on top. * 1. Gets the luma matte using Simplex noise. * 2. Generate a colored turbulence layer with the luma matte. * 3. Generate a colored sparkle layer with the same luma matter. * 4. Apply a screen color to the background image. * 5. Composite the previous result with the color turbulence. * 6. Composite the latest result with the sparkles. */ private const val SIMPLEX_SPARKLE_SHADER = """ vec4 main(vec2 p) { vec2 uv = p / in_size.xy; uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; // Luma is used for both color and sparkle masks. float luma = simplex3d(noiseP) * in_inverseLuma; // Get color layer (color mask with in_color applied) vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); float dither = triangleNoise(p * in_pixelDensity) / 255.; colorLayer += dither.rrr; // Get sparkle layer (sparkle mask with particles & in_color applied) vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p); // Composite with the background. half4 bgColor = in_src.eval(p); half sparkleOpacity = smoothstep(0, 0.75, in_opacity); half3 effect = screen(bgColor.rgb, in_screenColor.rgb); effect = screen(effect, colorLayer * 0.22); effect += sparkleLayer * sparkleOpacity; return mix(bgColor, vec4(effect, 1.), in_opacity); } """ private const val COMMON_FUNCTIONS = /** * Below two functions generate turbulence layers (color or sparkles applied) with the * given luma matte. They both return a mask with in_color applied. */ """ vec3 getColorTurbulenceMask(float luma) { // Bring it to [0, 1] range. luma = luma * 0.5 + 0.5; half colorLuma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) * in_opacity; vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma); return colorLayer; } vec3 getSparkleTurbulenceMask(float luma, vec2 p) { half lumaIntensity = 1.75; half lumaBrightness = -1.3; half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.); float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z); vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma); return sparkleLayer; } """ private const val SIMPLEX_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER private const val FRACTAL_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER // TODO (b/282007590): Add NOISE_WITH_SPARKLE ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER private const val SPARKLE_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER enum class Type { /** Effect with a simple color noise turbulence. */ SIMPLEX_NOISE, /** Effect with a simple color noise turbulence, with fractal. */ SIMPLEX_NOISE_FRACTAL, /** Effect with color & sparkle turbulence with screen color layer. */ SIMPLEX_NOISE_SPARKLE } fun getShader(type: Type): String { return when (type) { Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER } } } Loading @@ -111,7 +185,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : setGridCount(config.gridCount) setPixelDensity(config.pixelDensity) setColor(config.color) setBackgroundColor(config.backgroundColor) setScreenColor(config.screenColor) setSize(config.width, config.height) setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness) setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity) Loading @@ -137,9 +211,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : setColorUniform("in_color", color) } /** Sets the background color of the effect. Alpha is ignored. */ /** * Sets the color that is used for blending on top of the background color/image. Only relevant * to [Type.SIMPLEX_NOISE_SPARKLE]. */ fun setScreenColor(color: Int) { setColorUniform("in_screenColor", color) } /** * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect], * no need to call this function since the background image of the View will be used. */ fun setBackgroundColor(color: Int) { setColorUniform("in_backgroundColor", color) setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color)) } /** Loading @@ -163,7 +248,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : * * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting * this a lower number removes variations. I.e. the turbulence noise will look more blended. * Expected input range is [0, 1]. more dimmed. * Expected input range is [0, 1]. * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise. * Expected input range is [0, 1]. * Loading packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +1 −1 Original line number Diff line number Diff line Loading @@ -1306,7 +1306,7 @@ public class MediaControlPanel { TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z, // Color will be correctly updated in ColorSchemeTransition. /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(), /* backgroundColor= */ Color.BLACK, /* screenColor= */ Color.BLACK, width, height, TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS, Loading packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +40 −50 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.systemui.surfaceeffects.loadingeffect import android.graphics.Paint import android.graphics.RenderEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.model.SysUiStateTest import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN Loading @@ -31,18 +33,17 @@ import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class LoadingEffectTest : SysUiStateTest() { private val fakeSystemClock = FakeSystemClock() private val fakeExecutor = FakeExecutor(fakeSystemClock) @get:Rule val animatorTestRule = AnimatorTestRule(this) @Test fun play_paintCallback_triggersDrawCallback() { Loading @@ -61,15 +62,13 @@ class LoadingEffectTest : SysUiStateTest() { animationStateChangedCallback = null ) fakeExecutor.execute { assertThat(paintFromCallback).isNull() loadingEffect.play() fakeSystemClock.advanceTime(500L) animatorTestRule.advanceTimeBy(500L) assertThat(paintFromCallback).isNotNull() } } @Test fun play_renderEffectCallback_triggersDrawCallback() { Loading @@ -88,25 +87,22 @@ class LoadingEffectTest : SysUiStateTest() { animationStateChangedCallback = null ) fakeExecutor.execute { assertThat(renderEffectFromCallback).isNull() loadingEffect.play() fakeSystemClock.advanceTime(500L) animatorTestRule.advanceTimeBy(500L) assertThat(renderEffectFromCallback).isNotNull() } } @Test fun play_animationStateChangesInOrder() { val config = TurbulenceNoiseAnimationConfig() val expectedStates = arrayOf(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) val actualStates = mutableListOf(NOT_PLAYING) val states = mutableListOf(NOT_PLAYING) val stateChangedCallback = object : AnimationStateChangedCallback { override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { actualStates.add(newState) states.add(newState) } } val drawCallback = Loading @@ -121,16 +117,15 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) val timeToAdvance = config.easeInDuration + config.maxDuration + config.easeOutDuration + 100 fakeExecutor.execute { loadingEffect.play() fakeSystemClock.advanceTime(timeToAdvance.toLong()) // Execute all the animators by advancing each duration with some buffer. animatorTestRule.advanceTimeBy(config.easeInDuration.toLong()) animatorTestRule.advanceTimeBy(config.maxDuration.toLong()) animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong()) animatorTestRule.advanceTimeBy(500) assertThat(actualStates).isEqualTo(expectedStates) } assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) } @Test Loading @@ -157,7 +152,6 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) fakeExecutor.execute { assertThat(numPlay).isEqualTo(0) loadingEffect.play() Loading @@ -168,7 +162,6 @@ class LoadingEffectTest : SysUiStateTest() { assertThat(numPlay).isEqualTo(1) } } @Test fun finish_finishesLoadingEffect() { Loading @@ -181,7 +174,7 @@ class LoadingEffectTest : SysUiStateTest() { val stateChangedCallback = object : AnimationStateChangedCallback { override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { if (oldState == MAIN && newState == NOT_PLAYING) { if (oldState == EASE_OUT && newState == NOT_PLAYING) { isFinished = true } } Loading @@ -194,19 +187,18 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) fakeExecutor.execute { assertThat(isFinished).isFalse() loadingEffect.play() fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L) animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L) assertThat(isFinished).isFalse() loadingEffect.finish() animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L) assertThat(isFinished).isTrue() } } @Test fun finish_notMainState_hasNoEffect() { Loading @@ -232,14 +224,12 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) fakeExecutor.execute { assertThat(isFinished).isFalse() loadingEffect.finish() assertThat(isFinished).isFalse() } } @Test fun getNoiseOffset_returnsNoiseOffset() { Loading Loading
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffect.kt +9 −1 Original line number Diff line number Diff line Loading @@ -181,6 +181,11 @@ private constructor( turbulenceNoiseShader.setColor(newColor) } /** Updates the noise color that's screen blended on top. */ fun updateScreenColor(newColor: Int) { turbulenceNoiseShader.setScreenColor(newColor) } /** * Retrieves the noise offset x, y, z values. This is useful for replaying the animation * smoothly from the last animation, by passing in the last values to the next animation. Loading Loading @@ -322,7 +327,10 @@ private constructor( private fun draw() { paintCallback?.onDraw(paint!!) renderEffectCallback?.onDraw( RenderEffect.createRuntimeShaderEffect(turbulenceNoiseShader, "in_src") RenderEffect.createRuntimeShaderEffect( turbulenceNoiseShader, TurbulenceNoiseShader.BACKGROUND_UNIFORM ) ) } Loading
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt +3 −3 Original line number Diff line number Diff line Loading @@ -52,7 +52,7 @@ data class TurbulenceNoiseAnimationConfig( /** Color of the effect. */ val color: Int = DEFAULT_COLOR, /** Background color of the effect. */ val backgroundColor: Int = DEFAULT_BACKGROUND_COLOR, val screenColor: Int = DEFAULT_SCREEN_COLOR, val width: Float = 0f, val height: Float = 0f, val maxDuration: Float = DEFAULT_MAX_DURATION_IN_MILLIS, Loading @@ -72,7 +72,7 @@ data class TurbulenceNoiseAnimationConfig( */ val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS, /** Whether to flip the luma mask. */ val shouldInverseNoiseLuminosity: Boolean = false val shouldInverseNoiseLuminosity: Boolean = false, ) { companion object { const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec Loading @@ -83,7 +83,7 @@ data class TurbulenceNoiseAnimationConfig( const val DEFAULT_COLOR = Color.WHITE const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f const val DEFAULT_BACKGROUND_COLOR = Color.BLACK const val DEFAULT_SCREEN_COLOR = Color.BLACK private val random = Random() } }
packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +110 −25 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.systemui.surfaceeffects.turbulencenoise import android.graphics.RuntimeShader import com.android.systemui.surfaceeffects.shaders.SolidColorShader import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary import java.lang.Float.max Loading @@ -28,9 +29,11 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : RuntimeShader(getShader(baseType)) { // language=AGSL companion object { /** Uniform name for the background buffer (e.g. image, solid color, etc.). */ const val BACKGROUND_UNIFORM = "in_src" private const val UNIFORMS = """ uniform shader in_src; // Needed to support RenderEffect. uniform shader ${BACKGROUND_UNIFORM}; uniform float in_gridNum; uniform vec3 in_noiseMove; uniform vec2 in_size; Loading @@ -41,7 +44,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : uniform half in_lumaMatteBlendFactor; uniform half in_lumaMatteOverallBrightness; layout(color) uniform vec4 in_color; layout(color) uniform vec4 in_backgroundColor; layout(color) uniform vec4 in_screenColor; """ private const val SIMPLEX_SHADER = Loading @@ -50,22 +53,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : vec2 uv = p / in_size.xy; uv.x *= in_aspectRatio; // Compute turbulence effect with the uv distorted with simplex noise. vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; // Bring it to [0, 1] range. float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5; luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; vec3 color = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); // Blend the result with the background color. color = in_src.eval(p).rgb + color * 0.6; // Add dither with triangle distribution to avoid color banding. Dither in the // shader here as we are in gamma space. float dither = triangleNoise(p * in_pixelDensity) / 255.; color += dither.rrr; // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to // multiply rgb with a to get the correct result. color = (color + dither.rrr) * in_opacity; return vec4(color, in_opacity); // Return the pre-multiplied alpha result, i.e. [R*A, G*A, B*A, A]. return vec4(color * in_opacity, in_opacity); } """ Loading @@ -76,32 +77,105 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; // Bring it to [0, 1] range. float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5; luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; vec3 color = getColorTurbulenceMask(simplex3d_fractal(noiseP) * in_inverseLuma); // Blend the result with the background color. color = in_src.eval(p).rgb + color * 0.6; // Skip dithering. return vec4(color * in_opacity, in_opacity); } """ /** * This effect has two layers: color turbulence effect with sparkles on top. * 1. Gets the luma matte using Simplex noise. * 2. Generate a colored turbulence layer with the luma matte. * 3. Generate a colored sparkle layer with the same luma matter. * 4. Apply a screen color to the background image. * 5. Composite the previous result with the color turbulence. * 6. Composite the latest result with the sparkles. */ private const val SIMPLEX_SPARKLE_SHADER = """ vec4 main(vec2 p) { vec2 uv = p / in_size.xy; uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; // Luma is used for both color and sparkle masks. float luma = simplex3d(noiseP) * in_inverseLuma; // Get color layer (color mask with in_color applied) vec3 colorLayer = getColorTurbulenceMask(simplex3d(noiseP) * in_inverseLuma); float dither = triangleNoise(p * in_pixelDensity) / 255.; colorLayer += dither.rrr; // Get sparkle layer (sparkle mask with particles & in_color applied) vec3 sparkleLayer = getSparkleTurbulenceMask(luma, p); // Composite with the background. half4 bgColor = in_src.eval(p); half sparkleOpacity = smoothstep(0, 0.75, in_opacity); half3 effect = screen(bgColor.rgb, in_screenColor.rgb); effect = screen(effect, colorLayer * 0.22); effect += sparkleLayer * sparkleOpacity; return mix(bgColor, vec4(effect, 1.), in_opacity); } """ private const val COMMON_FUNCTIONS = /** * Below two functions generate turbulence layers (color or sparkles applied) with the * given luma matte. They both return a mask with in_color applied. */ """ vec3 getColorTurbulenceMask(float luma) { // Bring it to [0, 1] range. luma = luma * 0.5 + 0.5; half colorLuma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness) * in_opacity; vec3 colorLayer = maskLuminosity(in_color.rgb, colorLuma); return colorLayer; } vec3 getSparkleTurbulenceMask(float luma, vec2 p) { half lumaIntensity = 1.75; half lumaBrightness = -1.3; half sparkleLuma = max(luma * lumaIntensity + lumaBrightness, 0.); float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_noiseMove.z); vec3 sparkleLayer = maskLuminosity(in_color.rgb * sparkle, sparkleLuma); return sparkleLayer; } """ private const val SIMPLEX_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SIMPLEX_SHADER ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SHADER private const val FRACTAL_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + FRACTAL_SHADER // TODO (b/282007590): Add NOISE_WITH_SPARKLE ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + FRACTAL_SHADER private const val SPARKLE_NOISE_SHADER = ShaderUtilLibrary.SHADER_LIB + UNIFORMS + COMMON_FUNCTIONS + SIMPLEX_SPARKLE_SHADER enum class Type { /** Effect with a simple color noise turbulence. */ SIMPLEX_NOISE, /** Effect with a simple color noise turbulence, with fractal. */ SIMPLEX_NOISE_FRACTAL, /** Effect with color & sparkle turbulence with screen color layer. */ SIMPLEX_NOISE_SPARKLE } fun getShader(type: Type): String { return when (type) { Type.SIMPLEX_NOISE -> SIMPLEX_NOISE_SHADER Type.SIMPLEX_NOISE_FRACTAL -> FRACTAL_NOISE_SHADER Type.SIMPLEX_NOISE_SPARKLE -> SPARKLE_NOISE_SHADER } } } Loading @@ -111,7 +185,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : setGridCount(config.gridCount) setPixelDensity(config.pixelDensity) setColor(config.color) setBackgroundColor(config.backgroundColor) setScreenColor(config.screenColor) setSize(config.width, config.height) setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness) setInverseNoiseLuminosity(config.shouldInverseNoiseLuminosity) Loading @@ -137,9 +211,20 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : setColorUniform("in_color", color) } /** Sets the background color of the effect. Alpha is ignored. */ /** * Sets the color that is used for blending on top of the background color/image. Only relevant * to [Type.SIMPLEX_NOISE_SPARKLE]. */ fun setScreenColor(color: Int) { setColorUniform("in_screenColor", color) } /** * Sets the background color of the effect. Alpha is ignored. If you are using [RenderEffect], * no need to call this function since the background image of the View will be used. */ fun setBackgroundColor(color: Int) { setColorUniform("in_backgroundColor", color) setInputShader(BACKGROUND_UNIFORM, SolidColorShader(color)) } /** Loading @@ -163,7 +248,7 @@ class TurbulenceNoiseShader(val baseType: Type = Type.SIMPLEX_NOISE) : * * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting * this a lower number removes variations. I.e. the turbulence noise will look more blended. * Expected input range is [0, 1]. more dimmed. * Expected input range is [0, 1]. * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise. * Expected input range is [0, 1]. * Loading
packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaControlPanel.java +1 −1 Original line number Diff line number Diff line Loading @@ -1306,7 +1306,7 @@ public class MediaControlPanel { TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z, // Color will be correctly updated in ColorSchemeTransition. /* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(), /* backgroundColor= */ Color.BLACK, /* screenColor= */ Color.BLACK, width, height, TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS, Loading
packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +40 −50 Original line number Diff line number Diff line Loading @@ -19,7 +19,9 @@ package com.android.systemui.surfaceeffects.loadingeffect import android.graphics.Paint import android.graphics.RenderEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.animation.AnimatorTestRule import com.android.systemui.model.SysUiStateTest import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.AnimationState.EASE_IN Loading @@ -31,18 +33,17 @@ import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion import com.android.systemui.surfaceeffects.loadingeffect.LoadingEffect.Companion.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseShader import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class LoadingEffectTest : SysUiStateTest() { private val fakeSystemClock = FakeSystemClock() private val fakeExecutor = FakeExecutor(fakeSystemClock) @get:Rule val animatorTestRule = AnimatorTestRule(this) @Test fun play_paintCallback_triggersDrawCallback() { Loading @@ -61,15 +62,13 @@ class LoadingEffectTest : SysUiStateTest() { animationStateChangedCallback = null ) fakeExecutor.execute { assertThat(paintFromCallback).isNull() loadingEffect.play() fakeSystemClock.advanceTime(500L) animatorTestRule.advanceTimeBy(500L) assertThat(paintFromCallback).isNotNull() } } @Test fun play_renderEffectCallback_triggersDrawCallback() { Loading @@ -88,25 +87,22 @@ class LoadingEffectTest : SysUiStateTest() { animationStateChangedCallback = null ) fakeExecutor.execute { assertThat(renderEffectFromCallback).isNull() loadingEffect.play() fakeSystemClock.advanceTime(500L) animatorTestRule.advanceTimeBy(500L) assertThat(renderEffectFromCallback).isNotNull() } } @Test fun play_animationStateChangesInOrder() { val config = TurbulenceNoiseAnimationConfig() val expectedStates = arrayOf(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) val actualStates = mutableListOf(NOT_PLAYING) val states = mutableListOf(NOT_PLAYING) val stateChangedCallback = object : AnimationStateChangedCallback { override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { actualStates.add(newState) states.add(newState) } } val drawCallback = Loading @@ -121,16 +117,15 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) val timeToAdvance = config.easeInDuration + config.maxDuration + config.easeOutDuration + 100 fakeExecutor.execute { loadingEffect.play() fakeSystemClock.advanceTime(timeToAdvance.toLong()) // Execute all the animators by advancing each duration with some buffer. animatorTestRule.advanceTimeBy(config.easeInDuration.toLong()) animatorTestRule.advanceTimeBy(config.maxDuration.toLong()) animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong()) animatorTestRule.advanceTimeBy(500) assertThat(actualStates).isEqualTo(expectedStates) } assertThat(states).containsExactly(NOT_PLAYING, EASE_IN, MAIN, EASE_OUT, NOT_PLAYING) } @Test Loading @@ -157,7 +152,6 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) fakeExecutor.execute { assertThat(numPlay).isEqualTo(0) loadingEffect.play() Loading @@ -168,7 +162,6 @@ class LoadingEffectTest : SysUiStateTest() { assertThat(numPlay).isEqualTo(1) } } @Test fun finish_finishesLoadingEffect() { Loading @@ -181,7 +174,7 @@ class LoadingEffectTest : SysUiStateTest() { val stateChangedCallback = object : AnimationStateChangedCallback { override fun onStateChanged(oldState: AnimationState, newState: AnimationState) { if (oldState == MAIN && newState == NOT_PLAYING) { if (oldState == EASE_OUT && newState == NOT_PLAYING) { isFinished = true } } Loading @@ -194,19 +187,18 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) fakeExecutor.execute { assertThat(isFinished).isFalse() loadingEffect.play() fakeSystemClock.advanceTime(config.easeInDuration.toLong() + 500L) animatorTestRule.advanceTimeBy(config.easeInDuration.toLong() + 500L) assertThat(isFinished).isFalse() loadingEffect.finish() animatorTestRule.advanceTimeBy(config.easeOutDuration.toLong() + 500L) assertThat(isFinished).isTrue() } } @Test fun finish_notMainState_hasNoEffect() { Loading @@ -232,14 +224,12 @@ class LoadingEffectTest : SysUiStateTest() { stateChangedCallback ) fakeExecutor.execute { assertThat(isFinished).isFalse() loadingEffect.finish() assertThat(isFinished).isFalse() } } @Test fun getNoiseOffset_returnsNoiseOffset() { Loading