• 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.statusbar.charging
17 
18 import android.graphics.Color
19 import android.graphics.PointF
20 import android.graphics.RuntimeShader
21 import android.util.MathUtils
22 
23 /**
24  * Shader class that renders an expanding charging ripple effect. A charging ripple contains
25  * three elements:
26  * 1. an expanding filled circle that appears in the beginning and quickly fades away
27  * 2. an expanding ring that appears throughout the effect
28  * 3. an expanding ring-shaped area that reveals noise over #2.
29  *
30  * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java.
31  */
32 class RippleShader internal constructor() : RuntimeShader(SHADER, false) {
33     companion object {
34         private const val SHADER_UNIFORMS = """uniform vec2 in_origin;
35                 uniform float in_progress;
36                 uniform float in_maxRadius;
37                 uniform float in_time;
38                 uniform float in_distort_radial;
39                 uniform float in_distort_xy;
40                 uniform float in_radius;
41                 uniform float in_fadeSparkle;
42                 uniform float in_fadeCircle;
43                 uniform float in_fadeRing;
44                 uniform float in_blur;
45                 uniform float in_pixelDensity;
46                 uniform vec4 in_color;
47                 uniform float in_sparkle_strength;"""
48         private const val SHADER_LIB = """float triangleNoise(vec2 n) {
49                     n  = fract(n * vec2(5.3987, 5.4421));
50                     n += dot(n.yx, n.xy + vec2(21.5351, 14.3137));
51                     float xy = n.x * n.y;
52                     return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0;
53                 }
54                 const float PI = 3.1415926535897932384626;
55 
56                 float threshold(float v, float l, float h) {
57                   return step(l, v) * (1.0 - step(h, v));
58                 }
59 
60                 float sparkles(vec2 uv, float t) {
61                   float n = triangleNoise(uv);
62                   float s = 0.0;
63                   for (float i = 0; i < 4; i += 1) {
64                     float l = i * 0.01;
65                     float h = l + 0.1;
66                     float o = smoothstep(n - l, h, n);
67                     o *= abs(sin(PI * o * (t + 0.55 * i)));
68                     s += o;
69                   }
70                   return s;
71                 }
72 
73                 float softCircle(vec2 uv, vec2 xy, float radius, float blur) {
74                   float blurHalf = blur * 0.5;
75                   float d = distance(uv, xy);
76                   return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius);
77                 }
78 
79                 float softRing(vec2 uv, vec2 xy, float radius, float blur) {
80                   float thickness_half = radius * 0.25;
81                   float circle_outer = softCircle(uv, xy, radius + thickness_half, blur);
82                   float circle_inner = softCircle(uv, xy, radius - thickness_half, blur);
83                   return circle_outer - circle_inner;
84                 }
85 
86                 vec2 distort(vec2 p, vec2 origin, float time,
87                     float distort_amount_radial, float distort_amount_xy) {
88                     float2 distance = origin - p;
89                     float angle = atan(distance.y, distance.x);
90                     return p + vec2(sin(angle * 8 + time * 0.003 + 1.641),
91                                     cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial
92                              + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123),
93                                     cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy;
94                 }"""
95         private const val SHADER_MAIN = """vec4 main(vec2 p) {
96                     vec2 p_distorted = distort(p, in_origin, in_time, in_distort_radial,
97                         in_distort_xy);
98 
99                     // Draw shapes
100                     float sparkleRing = softRing(p_distorted, in_origin, in_radius, in_blur);
101                     float sparkle = sparkles(p - mod(p, in_pixelDensity * 0.8), in_time * 0.00175)
102                         * sparkleRing * in_fadeSparkle;
103                     float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur);
104                     float rippleAlpha = max(circle * in_fadeCircle,
105                         softRing(p_distorted, in_origin, in_radius, in_blur) * in_fadeRing) * 0.45;
106                     vec4 ripple = in_color * rippleAlpha;
107                     return mix(ripple, vec4(sparkle), sparkle * in_sparkle_strength);
108                 }"""
109         private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN
110 
subProgressnull111         private fun subProgress(start: Float, end: Float, progress: Float): Float {
112             val min = Math.min(start, end)
113             val max = Math.max(start, end)
114             val sub = Math.min(Math.max(progress, min), max)
115             return (sub - start) / (end - start)
116         }
117     }
118 
119     /**
120      * Maximum radius of the ripple.
121      */
122     var radius: Float = 0.0f
123         set(value) {
124             field = value
125             setUniform("in_maxRadius", value)
126         }
127 
128     /**
129      * Origin coordinate of the ripple.
130      */
131     var origin: PointF = PointF()
132         set(value) {
133             field = value
134             setUniform("in_origin", floatArrayOf(value.x, value.y))
135         }
136 
137     /**
138      * Progress of the ripple. Float value between [0, 1].
139      */
140     var progress: Float = 0.0f
141         set(value) {
142             field = value
143             setUniform("in_progress", value)
144             setUniform("in_radius",
145                     (1 - (1 - value) * (1 - value) * (1 - value))* radius)
146             setUniform("in_blur", MathUtils.lerp(1.25f, 0.5f, value))
147 
148             val fadeIn = subProgress(0f, 0.1f, value)
149             val fadeOutNoise = subProgress(0.4f, 1f, value)
150             val fadeOutRipple = subProgress(0.3f, 1f, value)
151             val fadeCircle = subProgress(0f, 0.2f, value)
152             setUniform("in_fadeSparkle", Math.min(fadeIn, 1 - fadeOutNoise))
153             setUniform("in_fadeCircle", 1 - fadeCircle)
154             setUniform("in_fadeRing", Math.min(fadeIn, 1 - fadeOutRipple))
155         }
156 
157     /**
158      * Play time since the start of the effect.
159      */
160     var time: Float = 0.0f
161         set(value) {
162             field = value
163             setUniform("in_time", value)
164         }
165 
166     /**
167      * A hex value representing the ripple color, in the format of ARGB
168      */
169     var color: Int = 0xffffff.toInt()
170         set(value) {
171             field = value
172             val color = Color.valueOf(value)
173             setUniform("in_color", floatArrayOf(color.red(),
174                     color.green(), color.blue(), color.alpha()))
175         }
176 
177     /**
178      * Noise sparkle intensity. Expected value between [0, 1]. The sparkle is white, and thus
179      * with strength 0 it's transparent, leaving the ripple fully smooth, while with strength 1
180      * it's opaque white and looks the most grainy.
181      */
182     var sparkleStrength: Float = 0.0f
183         set(value) {
184             field = value
185             setUniform("in_sparkle_strength", value)
186         }
187 
188     /**
189      * Distortion strength of the ripple. Expected value between[0, 1].
190      */
191     var distortionStrength: Float = 0.0f
192         set(value) {
193             field = value
194             setUniform("in_distort_radial", 75 * progress * value)
195             setUniform("in_distort_xy", 75 * value)
196         }
197 
198     var pixelDensity: Float = 1.0f
199         set(value) {
200             field = value
201             setUniform("in_pixelDensity", value)
202         }
203 }
204