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 17 package com.android.systemui.statusbar.charging 18 19 import android.graphics.Color 20 import android.graphics.PointF 21 import android.graphics.RuntimeShader 22 import android.util.MathUtils 23 24 /** 25 * Shader class that renders a distorted ripple for the UDFPS dwell effect. 26 * Adjustable shader parameters: 27 * - progress 28 * - origin 29 * - color 30 * - time 31 * - maxRadius 32 * - distortionStrength. 33 * See per field documentation for more details. 34 * 35 * Modeled after frameworks/base/graphics/java/android/graphics/drawable/RippleShader.java. 36 */ 37 class DwellRippleShader internal constructor() : RuntimeShader(SHADER, false) { 38 companion object { 39 private const val SHADER_UNIFORMS = """uniform vec2 in_origin; 40 uniform float in_time; 41 uniform float in_radius; 42 uniform float in_blur; 43 uniform vec4 in_color; 44 uniform float in_phase1; 45 uniform float in_phase2; 46 uniform float in_distortion_strength;""" 47 private const val SHADER_LIB = """ 48 float softCircle(vec2 uv, vec2 xy, float radius, float blur) { 49 float blurHalf = blur * 0.5; 50 float d = distance(uv, xy); 51 return 1. - smoothstep(1. - blurHalf, 1. + blurHalf, d / radius); 52 } 53 54 float softRing(vec2 uv, vec2 xy, float radius, float blur) { 55 float thickness_half = radius * 0.25; 56 float circle_outer = softCircle(uv, xy, radius + thickness_half, blur); 57 float circle_inner = softCircle(uv, xy, radius - thickness_half, blur); 58 return circle_outer - circle_inner; 59 } 60 61 vec2 distort(vec2 p, float time, float distort_amount_xy, float frequency) { 62 return p + vec2(sin(p.y * frequency + in_phase1), 63 cos(p.x * frequency * -1.23 + in_phase2)) * distort_amount_xy; 64 } 65 66 vec4 ripple(vec2 p, float distort_xy, float frequency) { 67 vec2 p_distorted = distort(p, in_time, distort_xy, frequency); 68 float circle = softCircle(p_distorted, in_origin, in_radius * 1.2, in_blur); 69 float rippleAlpha = max(circle, 70 softRing(p_distorted, in_origin, in_radius, in_blur)) * 0.25; 71 return in_color * rippleAlpha; 72 } 73 """ 74 private const val SHADER_MAIN = """vec4 main(vec2 p) { 75 vec4 color1 = ripple(p, 76 34 * in_distortion_strength, // distort_xy 77 0.012 // frequency 78 ); 79 vec4 color2 = ripple(p, 80 49 * in_distortion_strength, // distort_xy 81 0.018 // frequency 82 ); 83 // Alpha blend between two layers. 84 return vec4(color1.xyz + color2.xyz 85 * (1 - color1.w), color1.w + color2.w * (1-color1.w)); 86 }""" 87 private const val SHADER = SHADER_UNIFORMS + SHADER_LIB + SHADER_MAIN 88 } 89 90 /** 91 * Maximum radius of the ripple. 92 */ 93 var maxRadius: Float = 0.0f 94 95 /** 96 * Origin coordinate of the ripple. 97 */ 98 var origin: PointF = PointF() 99 set(value) { 100 field = value 101 setUniform("in_origin", floatArrayOf(value.x, value.y)) 102 } 103 104 /** 105 * Progress of the ripple. Float value between [0, 1]. 106 */ 107 var progress: Float = 0.0f 108 set(value) { 109 field = value 110 setUniform("in_radius", 111 (1 - (1 - value) * (1 - value) * (1 - value))* maxRadius) 112 setUniform("in_blur", MathUtils.lerp(1f, 0.7f, value)) 113 } 114 115 /** 116 * Distortion strength between [0, 1], with 0 being no distortion and 1 being full distortion. 117 */ 118 var distortionStrength: Float = 0.0f 119 set(value) { 120 field = value 121 setUniform("in_distortion_strength", value) 122 } 123 124 /** 125 * Play time since the start of the effect in seconds. 126 */ 127 var time: Float = 0.0f 128 set(value) { 129 field = value * 0.001f 130 setUniform("in_time", field) 131 setUniform("in_phase1", field * 3f + 0.367f) 132 setUniform("in_phase2", field * 7.2f * 1.531f) 133 } 134 135 /** 136 * A hex value representing the ripple color, in the format of ARGB 137 */ 138 var color: Int = 0xffffff.toInt() 139 set(value) { 140 field = value 141 val color = Color.valueOf(value) 142 setUniform("in_color", floatArrayOf(color.red(), 143 color.green(), color.blue(), color.alpha())) 144 } 145 } 146