• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.systemui.surfaceeffects.ripple
17 
18 import android.graphics.RuntimeShader
19 import android.util.Log
20 import android.util.MathUtils
21 import androidx.annotation.VisibleForTesting
22 import com.android.systemui.animation.Interpolators
23 import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary
24 import com.android.systemui.surfaceeffects.shaderutil.ShaderUtilLibrary
25 
26 /**
27  * Shader class that renders an expanding ripple effect. The ripple contains three elements:
28  * 1. an expanding filled [RippleShape] that appears in the beginning and quickly fades away
29  * 2. an expanding ring that appears throughout the effect
30  * 3. an expanding ring-shaped area that reveals noise over #2.
31  *
32  * The ripple shader will be default to the circle shape if not specified.
33  *
34  * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
35  */
36 class RippleShader(rippleShape: RippleShape = RippleShape.CIRCLE) :
37     RuntimeShader(buildShader(rippleShape)) {
38 
39     /** Shapes that the [RippleShader] supports. */
40     enum class RippleShape {
41         CIRCLE,
42         ROUNDED_BOX,
43         ELLIPSE
44     }
45     // language=AGSL
46     companion object {
47         private val TAG = RippleShader::class.simpleName
48 
49         // Default fade in/ out values. The value range is [0,1].
50         const val DEFAULT_FADE_IN_START = 0f
51         const val DEFAULT_FADE_OUT_END = 1f
52 
53         const val DEFAULT_BASE_RING_FADE_IN_END = 0.1f
54         const val DEFAULT_BASE_RING_FADE_OUT_START = 0.3f
55 
56         const val DEFAULT_SPARKLE_RING_FADE_IN_END = 0.1f
57         const val DEFAULT_SPARKLE_RING_FADE_OUT_START = 0.4f
58 
59         const val DEFAULT_CENTER_FILL_FADE_IN_END = 0f
60         const val DEFAULT_CENTER_FILL_FADE_OUT_START = 0f
61         const val DEFAULT_CENTER_FILL_FADE_OUT_END = 0.6f
62 
63         private const val SHADER_UNIFORMS =
64             """
65             uniform vec2 in_center;
66             uniform vec2 in_size;
67             uniform float in_cornerRadius;
68             uniform float in_thickness;
69             uniform float in_time;
70             uniform float in_distort_radial;
71             uniform float in_distort_xy;
72             uniform float in_fadeSparkle;
73             uniform float in_fadeFill;
74             uniform float in_fadeRing;
75             uniform float in_blur;
76             uniform float in_pixelDensity;
77             layout(color) uniform vec4 in_color;
78             uniform float in_sparkle_strength;
79         """
80 
81         private const val SHADER_CIRCLE_MAIN =
82             """
83             vec4 main(vec2 p) {
84                 vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
85                 float radius = in_size.x * 0.5;
86                 float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
87                 float inside = soften(sdCircle(p_distorted-in_center, radius * 1.25), in_blur);
88                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
89                     * (1.-sparkleRing) * in_fadeSparkle;
90 
91                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
92                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
93                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
94                 vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
95                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
96             }
97         """
98 
99         private const val SHADER_ROUNDED_BOX_MAIN =
100             """
101             vec4 main(vec2 p) {
102                 float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
103                     in_thickness), in_blur);
104                 float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius),
105                     in_blur);
106                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
107                     * (1.-sparkleRing) * in_fadeSparkle;
108 
109                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
110                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
111                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
112                 vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
113                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
114             }
115         """
116 
117         private const val SHADER_ELLIPSE_MAIN =
118             """
119             vec4 main(vec2 p) {
120                 vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
121 
122                 float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
123                 float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
124                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
125                     * (1.-sparkleRing) * in_fadeSparkle;
126 
127                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
128                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
129                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
130                 vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
131                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
132             }
133         """
134 
135         private const val CIRCLE_SHADER =
136             SHADER_UNIFORMS +
137                 ShaderUtilLibrary.SHADER_LIB +
138                 SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
139                 SdfShaderLibrary.CIRCLE_SDF +
140                 SHADER_CIRCLE_MAIN
141         private const val ROUNDED_BOX_SHADER =
142             SHADER_UNIFORMS +
143                 ShaderUtilLibrary.SHADER_LIB +
144                 SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
145                 SdfShaderLibrary.ROUNDED_BOX_SDF +
146                 SHADER_ROUNDED_BOX_MAIN
147         private const val ELLIPSE_SHADER =
148             SHADER_UNIFORMS +
149                 ShaderUtilLibrary.SHADER_LIB +
150                 SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
151                 SdfShaderLibrary.ELLIPSE_SDF +
152                 SHADER_ELLIPSE_MAIN
153 
buildShadernull154         private fun buildShader(rippleShape: RippleShape): String =
155             when (rippleShape) {
156                 RippleShape.CIRCLE -> CIRCLE_SHADER
157                 RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
158                 RippleShape.ELLIPSE -> ELLIPSE_SHADER
159             }
160 
subProgressnull161         private fun subProgress(start: Float, end: Float, progress: Float): Float {
162             // Avoid division by 0.
163             if (start == end) {
164                 // If start and end are the same and progress has exceeded the start/ end point,
165                 // treat it as 1, otherwise 0.
166                 return if (progress > start) 1f else 0f
167             }
168 
169             val min = Math.min(start, end)
170             val max = Math.max(start, end)
171             val sub = Math.min(Math.max(progress, min), max)
172             return (sub - start) / (end - start)
173         }
174 
getFadenull175         private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float {
176             val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress)
177             val fadeOut =
178                 1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress)
179 
180             return Math.min(fadeIn, fadeOut)
181         }
182     }
183 
184     /** Sets the center position of the ripple. */
setCenternull185     fun setCenter(x: Float, y: Float) {
186         setFloatUniform("in_center", x, y)
187     }
188 
189     /**
190      * Blur multipliers for the ripple.
191      *
192      * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to
193      * add more blur.
194      */
195     var blurStart: Float = 1.25f
196     var blurEnd: Float = 0.5f
197 
198     /** Size of the ripple. */
199     val rippleSize = RippleSize()
200 
201     /**
202      * Linear progress of the ripple. Float value between [0, 1].
203      *
204      * <p>Note that the progress here is expected to be linear without any curve applied.
205      */
206     var rawProgress: Float = 0.0f
207         set(value) {
208             field = value
209             progress = Interpolators.STANDARD.getInterpolation(value)
210 
211             setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value))
212             setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value))
213             setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value))
214         }
215 
216     /** Progress with Standard easing curve applied. */
217     private var progress: Float = 0.0f
218         set(value) {
219             field = value
220 
221             rippleSize.update(value)
222 
223             setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight)
224             setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f)
225             // Corner radius is always max of the min between the current width and height.
226             setFloatUniform(
227                 "in_cornerRadius",
228                 Math.min(rippleSize.currentWidth, rippleSize.currentHeight)
229             )
230 
231             setFloatUniform("in_blur", MathUtils.lerp(blurStart, blurEnd, value))
232         }
233 
234     /** Play time since the start of the effect. */
235     var time: Float = 0.0f
236         set(value) {
237             field = value
238             setFloatUniform("in_time", value)
239         }
240 
241     /** A hex value representing the ripple color, in the format of ARGB */
242     var color: Int = 0xffffff
243         set(value) {
244             field = value
245             setColorUniform("in_color", value)
246         }
247 
248     /**
249      * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
250      * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
251      * opaque white and looks the most grainy.
252      */
253     var sparkleStrength: Float = 0.0f
254         set(value) {
255             field = value
256             setFloatUniform("in_sparkle_strength", value)
257         }
258 
259     /** Distortion strength of the ripple. Expected value between[0, 1]. */
260     var distortionStrength: Float = 0.0f
261         set(value) {
262             field = value
263             setFloatUniform("in_distort_radial", 75 * rawProgress * value)
264             setFloatUniform("in_distort_xy", 75 * value)
265         }
266 
267     /**
268      * Pixel density of the screen that the effects are rendered to.
269      *
270      * <p>This value should come from [resources.displayMetrics.density].
271      */
272     var pixelDensity: Float = 1.0f
273         set(value) {
274             field = value
275             setFloatUniform("in_pixelDensity", value)
276         }
277 
278     /** Parameters that are used to fade in/ out of the sparkle ring. */
279     val sparkleRingFadeParams =
280         FadeParams(
281             DEFAULT_FADE_IN_START,
282             DEFAULT_SPARKLE_RING_FADE_IN_END,
283             DEFAULT_SPARKLE_RING_FADE_OUT_START,
284             DEFAULT_FADE_OUT_END
285         )
286 
287     /**
288      * Parameters that are used to fade in/ out of the base ring.
289      *
290      * <p>Note that the shader draws the sparkle ring on top of the base ring.
291      */
292     val baseRingFadeParams =
293         FadeParams(
294             DEFAULT_FADE_IN_START,
295             DEFAULT_BASE_RING_FADE_IN_END,
296             DEFAULT_BASE_RING_FADE_OUT_START,
297             DEFAULT_FADE_OUT_END
298         )
299 
300     /** Parameters that are used to fade in/ out of the center fill. */
301     val centerFillFadeParams =
302         FadeParams(
303             DEFAULT_FADE_IN_START,
304             DEFAULT_CENTER_FILL_FADE_IN_END,
305             DEFAULT_CENTER_FILL_FADE_OUT_START,
306             DEFAULT_CENTER_FILL_FADE_OUT_END
307         )
308 
309     /**
310      * Parameters used for fade in and outs of the ripple.
311      *
312      * <p>Note that all the fade in/ outs are "linear" progression.
313      *
314      * ```
315      *          (opacity)
316      *          1
317      *          │
318      * maxAlpha ←       ――――――――――――
319      *          │      /            \
320      *          │     /              \
321      * minAlpha ←――――/                \―――― (alpha change)
322      *          │
323      *          │
324      *          0 ―――↑―――↑―――――――――↑―――↑――――1 (progress)
325      *               fadeIn        fadeOut
326      *               Start & End   Start & End
327      * ```
328      *
329      * <p>If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and
330      * [fadeOutEnd] to 1.
331      */
332     data class FadeParams(
333         /**
334          * The starting point of the fade out which ends at [fadeInEnd], given that the animation
335          * goes from 0 to 1.
336          */
337         var fadeInStart: Float = DEFAULT_FADE_IN_START,
338         /**
339          * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the
340          * animation goes from 0 to 1.
341          */
342         var fadeInEnd: Float,
343         /**
344          * The starting point of the fade out which ends at 1, given that the animation goes from 0
345          * to 1.
346          */
347         var fadeOutStart: Float,
348 
349         /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
350         var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
351     )
352 
353     /**
354      * Desired size of the ripple at a point t in [progress].
355      *
356      * <p>Note that [progress] is curved and normalized. Below is an example usage:
357      * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height=
358      * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f)
359      *
360      * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into:
361      * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height=
362      * maxHeight)
363      */
364     data class SizeAtProgress(
365         /** Time t in [0,1] progress range. */
366         var t: Float,
367         /** Target width size of the ripple at time [t]. */
368         var width: Float,
369         /** Target height size of the ripple at time [t]. */
370         var height: Float
371     )
372 
373     /** Updates and stores the ripple size. */
374     inner class RippleSize {
375         @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>()
376         @VisibleForTesting var currentSizeIndex = 0
377         @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f)
378 
379         var currentWidth: Float = 0f
380             private set
381         var currentHeight: Float = 0f
382             private set
383 
384         /**
385          * Sets the max size of the ripple.
386          *
387          * <p>Use this if the ripple shape simply changes linearly.
388          */
setMaxSizenull389         fun setMaxSize(width: Float, height: Float) {
390             setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height))
391         }
392 
393         /**
394          * Sets the list of [sizes].
395          *
396          * <p>Note that setting this clears the existing sizes.
397          */
setSizeAtProgressesnull398         fun setSizeAtProgresses(vararg sizes: SizeAtProgress) {
399             // Reset everything.
400             this.sizes.clear()
401             currentSizeIndex = 0
402 
403             this.sizes.addAll(sizes)
404             this.sizes.sortBy { it.t }
405         }
406 
407         /**
408          * Updates the current ripple size based on the progress.
409          *
410          * <p>Should be called when progress updates.
411          */
updatenull412         fun update(progress: Float) {
413             val targetIndex = updateTargetIndex(progress)
414             val prevIndex = Math.max(targetIndex - 1, 0)
415 
416             val targetSize = sizes[targetIndex]
417             val prevSize = sizes[prevIndex]
418 
419             val subProgress = subProgress(prevSize.t, targetSize.t, progress)
420 
421             currentWidth = targetSize.width * subProgress + prevSize.width
422             currentHeight = targetSize.height * subProgress + prevSize.height
423         }
424 
updateTargetIndexnull425         private fun updateTargetIndex(progress: Float): Int {
426             if (sizes.isEmpty()) {
427                 // It could be empty on init.
428                 if (progress > 0f) {
429                     Log.e(
430                         TAG,
431                         "Did you forget to set the ripple size? Use [setMaxSize] or " +
432                             "[setSizeAtProgresses] before playing the animation."
433                     )
434                 }
435                 // If there's no size is set, we set everything to 0 and return early.
436                 setSizeAtProgresses(initialSize)
437                 return currentSizeIndex
438             }
439 
440             var candidate = sizes[currentSizeIndex]
441 
442             while (progress > candidate.t) {
443                 currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1)
444                 candidate = sizes[currentSizeIndex]
445             }
446 
447             return currentSizeIndex
448         }
449     }
450 }
451