• 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.view.animation.Interpolator
21 import android.view.animation.PathInterpolator
22 import androidx.annotation.VisibleForTesting
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         const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
64         const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
65         const val RIPPLE_DEFAULT_ALPHA: Int = 115 // full opacity is 255.
66 
67         private const val SHADER_UNIFORMS =
68             """
69             uniform vec2 in_center;
70             uniform vec2 in_size;
71             uniform float in_cornerRadius;
72             uniform float in_thickness;
73             uniform float in_time;
74             uniform float in_distort_radial;
75             uniform float in_distort_xy;
76             uniform float in_fadeSparkle;
77             uniform float in_fadeFill;
78             uniform float in_fadeRing;
79             uniform float in_blur;
80             uniform float in_pixelDensity;
81             layout(color) uniform vec4 in_color;
82             uniform float in_sparkle_strength;
83         """
84 
85         private const val SHADER_CIRCLE_MAIN =
86             """
87             vec4 main(vec2 p) {
88                 vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
89                 float radius = in_size.x * 0.5;
90                 float sparkleRing = soften(circleRing(p_distorted-in_center, radius), in_blur);
91                 float inside = soften(sdCircle(p_distorted-in_center, radius * 1.25), in_blur);
92                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
93                     * (1.-sparkleRing) * in_fadeSparkle;
94 
95                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
96                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
97                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
98                 vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
99                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
100             }
101         """
102 
103         private const val SHADER_ROUNDED_BOX_MAIN =
104             """
105             vec4 main(vec2 p) {
106                 float sparkleRing = soften(roundedBoxRing(p-in_center, in_size, in_cornerRadius,
107                     in_thickness), in_blur);
108                 float inside = soften(sdRoundedBox(p-in_center, in_size * 1.25, in_cornerRadius),
109                     in_blur);
110                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
111                     * (1.-sparkleRing) * in_fadeSparkle;
112 
113                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
114                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
115                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
116                 vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
117                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
118             }
119         """
120 
121         private const val SHADER_ELLIPSE_MAIN =
122             """
123             vec4 main(vec2 p) {
124                 vec2 p_distorted = distort(p, in_time, in_distort_radial, in_distort_xy);
125 
126                 float sparkleRing = soften(ellipseRing(p_distorted-in_center, in_size), in_blur);
127                 float inside = soften(sdEllipse(p_distorted-in_center, in_size * 1.2), in_blur);
128                 float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
129                     * (1.-sparkleRing) * in_fadeSparkle;
130 
131                 float rippleInsideAlpha = (1.-inside) * in_fadeFill;
132                 float rippleRingAlpha = (1.-sparkleRing) * in_fadeRing;
133                 float rippleAlpha = max(rippleInsideAlpha, rippleRingAlpha) * in_color.a;
134                 vec4 ripple = vec4(in_color.rgb, 1.0) * rippleAlpha;
135                 return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
136             }
137         """
138 
139         private const val CIRCLE_SHADER =
140             SHADER_UNIFORMS +
141                 ShaderUtilLibrary.SHADER_LIB +
142                 SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
143                 SdfShaderLibrary.CIRCLE_SDF +
144                 SHADER_CIRCLE_MAIN
145         private const val ROUNDED_BOX_SHADER =
146             SHADER_UNIFORMS +
147                 ShaderUtilLibrary.SHADER_LIB +
148                 SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
149                 SdfShaderLibrary.ROUNDED_BOX_SDF +
150                 SHADER_ROUNDED_BOX_MAIN
151         private const val ELLIPSE_SHADER =
152             SHADER_UNIFORMS +
153                 ShaderUtilLibrary.SHADER_LIB +
154                 SdfShaderLibrary.SHADER_SDF_OPERATION_LIB +
155                 SdfShaderLibrary.ELLIPSE_SDF +
156                 SHADER_ELLIPSE_MAIN
157 
buildShadernull158         private fun buildShader(rippleShape: RippleShape): String =
159             when (rippleShape) {
160                 RippleShape.CIRCLE -> CIRCLE_SHADER
161                 RippleShape.ROUNDED_BOX -> ROUNDED_BOX_SHADER
162                 RippleShape.ELLIPSE -> ELLIPSE_SHADER
163             }
164 
subProgressnull165         private fun subProgress(start: Float, end: Float, progress: Float): Float {
166             // Avoid division by 0.
167             if (start == end) {
168                 // If start and end are the same and progress has exceeded the start/ end point,
169                 // treat it as 1, otherwise 0.
170                 return if (progress > start) 1f else 0f
171             }
172 
173             val min = Math.min(start, end)
174             val max = Math.max(start, end)
175             val sub = Math.min(Math.max(progress, min), max)
176             return (sub - start) / (end - start)
177         }
178 
getFadenull179         private fun getFade(fadeParams: FadeParams, rawProgress: Float): Float {
180             val fadeIn = subProgress(fadeParams.fadeInStart, fadeParams.fadeInEnd, rawProgress)
181             val fadeOut =
182                 1f - subProgress(fadeParams.fadeOutStart, fadeParams.fadeOutEnd, rawProgress)
183 
184             return Math.min(fadeIn, fadeOut)
185         }
186 
lerpnull187         private fun lerp(start: Float, stop: Float, amount: Float): Float {
188             return start + (stop - start) * amount
189         }
190 
191         // Copied from [Interpolators#STANDARD]. This is to remove dependency on AnimationLib.
192         private val STANDARD: Interpolator = PathInterpolator(0.2f, 0f, 0f, 1f)
193     }
194 
195     /** Sets the center position of the ripple. */
setCenternull196     fun setCenter(x: Float, y: Float) {
197         setFloatUniform("in_center", x, y)
198     }
199 
200     /**
201      * Blur multipliers for the ripple.
202      *
203      * <p>It interpolates from [blurStart] to [blurEnd] based on the [progress]. Increase number to
204      * add more blur.
205      */
206     var blurStart: Float = 1.25f
207     var blurEnd: Float = 0.5f
208 
209     /** Size of the ripple. */
210     val rippleSize = RippleSize()
211 
212     /**
213      * Linear progress of the ripple. Float value between [0, 1].
214      *
215      * <p>Note that the progress here is expected to be linear without any curve applied.
216      */
217     var rawProgress: Float = 0.0f
218         set(value) {
219             field = value
220             progress = STANDARD.getInterpolation(value)
221 
222             setFloatUniform("in_fadeSparkle", getFade(sparkleRingFadeParams, value))
223             setFloatUniform("in_fadeRing", getFade(baseRingFadeParams, value))
224             setFloatUniform("in_fadeFill", getFade(centerFillFadeParams, value))
225         }
226 
227     /** Progress with Standard easing curve applied. */
228     private var progress: Float = 0.0f
229         set(value) {
230             field = value
231 
232             rippleSize.update(value)
233 
234             setFloatUniform("in_size", rippleSize.currentWidth, rippleSize.currentHeight)
235             setFloatUniform("in_thickness", rippleSize.currentHeight * 0.5f)
236             // Corner radius is always max of the min between the current width and height.
237             setFloatUniform(
238                 "in_cornerRadius",
239                 Math.min(rippleSize.currentWidth, rippleSize.currentHeight)
240             )
241             setFloatUniform("in_blur", lerp(1.25f, 0.5f, value))
242         }
243 
244     /** Play time since the start of the effect. */
245     var time: Float = 0.0f
246         set(value) {
247             field = value
248             setFloatUniform("in_time", value)
249         }
250 
251     /** A hex value representing the ripple color, in the format of ARGB */
252     var color: Int = 0xffffff
253         set(value) {
254             field = value
255             setColorUniform("in_color", value)
256         }
257 
258     /**
259      * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus with
260      * strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1 it's
261      * opaque white and looks the most grainy.
262      */
263     var sparkleStrength: Float = 0.0f
264         set(value) {
265             field = value
266             setFloatUniform("in_sparkle_strength", value)
267         }
268 
269     /** Distortion strength of the ripple. Expected value between[0, 1]. */
270     var distortionStrength: Float = 0.0f
271         set(value) {
272             field = value
273             setFloatUniform("in_distort_radial", 75 * rawProgress * value)
274             setFloatUniform("in_distort_xy", 75 * value)
275         }
276 
277     /**
278      * Pixel density of the screen that the effects are rendered to.
279      *
280      * <p>This value should come from [resources.displayMetrics.density].
281      */
282     var pixelDensity: Float = 1.0f
283         set(value) {
284             field = value
285             setFloatUniform("in_pixelDensity", value)
286         }
287 
288     /** Parameters that are used to fade in/ out of the sparkle ring. */
289     val sparkleRingFadeParams =
290         FadeParams(
291             DEFAULT_FADE_IN_START,
292             DEFAULT_SPARKLE_RING_FADE_IN_END,
293             DEFAULT_SPARKLE_RING_FADE_OUT_START,
294             DEFAULT_FADE_OUT_END
295         )
296 
297     /**
298      * Parameters that are used to fade in/ out of the base ring.
299      *
300      * <p>Note that the shader draws the sparkle ring on top of the base ring.
301      */
302     val baseRingFadeParams =
303         FadeParams(
304             DEFAULT_FADE_IN_START,
305             DEFAULT_BASE_RING_FADE_IN_END,
306             DEFAULT_BASE_RING_FADE_OUT_START,
307             DEFAULT_FADE_OUT_END
308         )
309 
310     /** Parameters that are used to fade in/ out of the center fill. */
311     val centerFillFadeParams =
312         FadeParams(
313             DEFAULT_FADE_IN_START,
314             DEFAULT_CENTER_FILL_FADE_IN_END,
315             DEFAULT_CENTER_FILL_FADE_OUT_START,
316             DEFAULT_CENTER_FILL_FADE_OUT_END
317         )
318 
319     /**
320      * Parameters used for fade in and outs of the ripple.
321      *
322      * <p>Note that all the fade in/ outs are "linear" progression.
323      *
324      * ```
325      *          (opacity)
326      *          1
327      *          │
328      * maxAlpha ←       ――――――――――――
329      *          │      /            \
330      *          │     /              \
331      * minAlpha ←――――/                \―――― (alpha change)
332      *          │
333      *          │
334      *          0 ―――↑―――↑―――――――――↑―――↑――――1 (progress)
335      *               fadeIn        fadeOut
336      *               Start & End   Start & End
337      * ```
338      *
339      * <p>If no fade in/ out is needed, set [fadeInStart] and [fadeInEnd] to 0; [fadeOutStart] and
340      * [fadeOutEnd] to 1.
341      */
342     data class FadeParams(
343         /**
344          * The starting point of the fade out which ends at [fadeInEnd], given that the animation
345          * goes from 0 to 1.
346          */
347         var fadeInStart: Float = DEFAULT_FADE_IN_START,
348         /**
349          * The endpoint of the fade in when the fade in starts at [fadeInStart], given that the
350          * animation goes from 0 to 1.
351          */
352         var fadeInEnd: Float,
353         /**
354          * The starting point of the fade out which ends at 1, given that the animation goes from 0
355          * to 1.
356          */
357         var fadeOutStart: Float,
358 
359         /** The endpoint of the fade out, given that the animation goes from 0 to 1. */
360         var fadeOutEnd: Float = DEFAULT_FADE_OUT_END,
361     )
362 
363     /**
364      * Desired size of the ripple at a point t in [progress].
365      *
366      * <p>Note that [progress] is curved and normalized. Below is an example usage:
367      * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 0.2f, width= 500f, height=
368      * 700f), SizeAtProgress(t= 1f, width= 100f, height= 300f)
369      *
370      * <p>For simple ripple effects, you will want to use [setMaxSize] as it is translated into:
371      * SizeAtProgress(t= 0f, width= 0f, height= 0f), SizeAtProgress(t= 1f, width= maxWidth, height=
372      * maxHeight)
373      */
374     data class SizeAtProgress(
375         /** Time t in [0,1] progress range. */
376         var t: Float,
377         /** Target width size of the ripple at time [t]. */
378         var width: Float,
379         /** Target height size of the ripple at time [t]. */
380         var height: Float
381     )
382 
383     /** Updates and stores the ripple size. */
384     inner class RippleSize {
385         @VisibleForTesting var sizes = mutableListOf<SizeAtProgress>()
386         @VisibleForTesting var currentSizeIndex = 0
387         @VisibleForTesting val initialSize = SizeAtProgress(0f, 0f, 0f)
388 
389         var currentWidth: Float = 0f
390             private set
391         var currentHeight: Float = 0f
392             private set
393 
394         /**
395          * Sets the max size of the ripple.
396          *
397          * <p>Use this if the ripple shape simply changes linearly.
398          */
setMaxSizenull399         fun setMaxSize(width: Float, height: Float) {
400             setSizeAtProgresses(initialSize, SizeAtProgress(1f, width, height))
401         }
402 
403         /**
404          * Sets the list of [sizes].
405          *
406          * <p>Note that setting this clears the existing sizes.
407          */
setSizeAtProgressesnull408         fun setSizeAtProgresses(vararg sizes: SizeAtProgress) {
409             // Reset everything.
410             this.sizes.clear()
411             currentSizeIndex = 0
412 
413             this.sizes.addAll(sizes)
414             this.sizes.sortBy { it.t }
415         }
416 
417         /**
418          * Updates the current ripple size based on the progress.
419          *
420          * <p>Should be called when progress updates.
421          */
updatenull422         fun update(progress: Float) {
423             val targetIndex = updateTargetIndex(progress)
424             val prevIndex = Math.max(targetIndex - 1, 0)
425 
426             val targetSize = sizes[targetIndex]
427             val prevSize = sizes[prevIndex]
428 
429             val subProgress = subProgress(prevSize.t, targetSize.t, progress)
430 
431             currentWidth = targetSize.width * subProgress + prevSize.width
432             currentHeight = targetSize.height * subProgress + prevSize.height
433         }
434 
updateTargetIndexnull435         private fun updateTargetIndex(progress: Float): Int {
436             if (sizes.isEmpty()) {
437                 // It could be empty on init.
438                 if (progress > 0f) {
439                     Log.e(
440                         TAG,
441                         "Did you forget to set the ripple size? Use [setMaxSize] or " +
442                             "[setSizeAtProgresses] before playing the animation."
443                     )
444                 }
445                 // If there's no size is set, we set everything to 0 and return early.
446                 setSizeAtProgresses(initialSize)
447                 return currentSizeIndex
448             }
449 
450             var candidate = sizes[currentSizeIndex]
451 
452             while (progress > candidate.t) {
453                 currentSizeIndex = Math.min(currentSizeIndex + 1, sizes.size - 1)
454                 candidate = sizes[currentSizeIndex]
455             }
456 
457             return currentSizeIndex
458         }
459     }
460 }
461