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