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