1 /* <lambda>null2 * Copyright (C) 2023 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.google.android.wallpaper.weathereffects.graphics.rain 18 19 import android.graphics.BitmapShader 20 import android.graphics.Canvas 21 import android.graphics.Color 22 import android.graphics.Paint 23 import android.graphics.RenderEffect 24 import android.graphics.Shader 25 import android.util.SizeF 26 import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer 27 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect 28 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils 29 import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop 30 import com.google.android.wallpaper.weathereffects.graphics.utils.SolidColorShader 31 import java.util.concurrent.Executor 32 import kotlin.random.Random 33 34 /** Defines and generates the rain weather effect animation. */ 35 class RainEffect( 36 /** The config of the rain effect. */ 37 private val rainConfig: RainEffectConfig, 38 /** The initial size of the surface where the effect will be shown. */ 39 surfaceSize: SizeF, 40 private val mainExecutor: Executor 41 ) : WeatherEffect { 42 43 private val rainPaint = Paint().also { it.shader = rainConfig.colorGradingShader } 44 // Set blur effect to reduce the outline noise. No need to set blur effect every time we 45 // re-generate the outline buffer. 46 private val outlineBuffer = 47 FrameBuffer(rainConfig.background.width, rainConfig.background.height).apply { 48 setRenderEffect(RenderEffect.createBlurEffect(2f, 2f, Shader.TileMode.CLAMP)) 49 } 50 private val outlineBufferPaint = Paint().also { it.shader = rainConfig.outlineShader } 51 52 private var elapsedTime: Float = 0f 53 54 init { 55 updateTextureUniforms() 56 adjustCropping(surfaceSize) 57 prepareColorGrading() 58 setIntensity(rainConfig.intensity) 59 } 60 61 override fun resize(newSurfaceSize: SizeF) = adjustCropping(newSurfaceSize) 62 63 override fun update(deltaMillis: Long, frameTimeNanos: Long) { 64 elapsedTime += deltaMillis * MILLIS_TO_SECONDS 65 66 rainConfig.rainShowerShader.setFloatUniform("time", elapsedTime) 67 rainConfig.glassRainShader.setFloatUniform("time", elapsedTime * 0.7f) 68 69 rainConfig.glassRainShader.setInputShader("texture", rainConfig.rainShowerShader) 70 rainConfig.colorGradingShader.setInputShader("texture", rainConfig.glassRainShader) 71 } 72 73 override fun draw(canvas: Canvas) { 74 canvas.drawPaint(rainPaint) 75 } 76 77 override fun reset() { 78 elapsedTime = Random.nextFloat() * 90f 79 } 80 81 override fun release() { 82 rainConfig.lut?.recycle() 83 outlineBuffer.close() 84 } 85 86 override fun setIntensity(intensity: Float) { 87 rainConfig.rainShowerShader.setFloatUniform("intensity", intensity) 88 rainConfig.glassRainShader.setFloatUniform("intensity", intensity * 0.6f) 89 rainConfig.colorGradingShader.setFloatUniform( 90 "intensity", 91 rainConfig.colorGradingIntensity * intensity 92 ) 93 val thickness = 1f + intensity * 10f 94 rainConfig.outlineShader.setFloatUniform("thickness", thickness) 95 96 // Need to recreate the outline buffer as the uniform has changed. 97 createOutlineBuffer() 98 } 99 100 private fun adjustCropping(surfaceSize: SizeF) { 101 val imageCropFgd = 102 ImageCrop.centerCoverCrop( 103 surfaceSize.width, 104 surfaceSize.height, 105 rainConfig.foreground.width.toFloat(), 106 rainConfig.foreground.height.toFloat() 107 ) 108 rainConfig.rainShowerShader.setFloatUniform( 109 "uvOffsetFgd", 110 imageCropFgd.leftOffset, 111 imageCropFgd.topOffset 112 ) 113 rainConfig.rainShowerShader.setFloatUniform( 114 "uvScaleFgd", 115 imageCropFgd.horizontalScale, 116 imageCropFgd.verticalScale 117 ) 118 119 val imageCropBgd = 120 ImageCrop.centerCoverCrop( 121 surfaceSize.width, 122 surfaceSize.height, 123 rainConfig.background.width.toFloat(), 124 rainConfig.background.height.toFloat() 125 ) 126 rainConfig.rainShowerShader.setFloatUniform( 127 "uvOffsetBgd", 128 imageCropBgd.leftOffset, 129 imageCropBgd.topOffset 130 ) 131 rainConfig.rainShowerShader.setFloatUniform( 132 "uvScaleBgd", 133 imageCropBgd.horizontalScale, 134 imageCropBgd.verticalScale 135 ) 136 137 rainConfig.rainShowerShader.setFloatUniform( 138 "screenSize", 139 surfaceSize.width, 140 surfaceSize.height 141 ) 142 rainConfig.glassRainShader.setFloatUniform( 143 "screenSize", 144 surfaceSize.width, 145 surfaceSize.height 146 ) 147 148 val screenAspectRatio = GraphicsUtils.getAspectRatio(surfaceSize) 149 rainConfig.rainShowerShader.setFloatUniform("screenAspectRatio", screenAspectRatio) 150 rainConfig.glassRainShader.setFloatUniform("screenAspectRatio", screenAspectRatio) 151 } 152 153 private fun updateTextureUniforms() { 154 val foregroundBuffer = 155 BitmapShader(rainConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR) 156 rainConfig.rainShowerShader.setInputBuffer("foreground", foregroundBuffer) 157 rainConfig.outlineShader.setInputBuffer("texture", foregroundBuffer) 158 159 rainConfig.rainShowerShader.setInputBuffer( 160 "background", 161 BitmapShader(rainConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR) 162 ) 163 } 164 165 private fun createOutlineBuffer() { 166 val canvas = outlineBuffer.beginDrawing() 167 canvas.drawPaint(outlineBufferPaint) 168 outlineBuffer.endDrawing() 169 170 outlineBuffer.tryObtainingImage( 171 { buffer -> 172 rainConfig.rainShowerShader.setInputBuffer( 173 "outlineBuffer", 174 BitmapShader(buffer, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR) 175 ) 176 }, 177 mainExecutor 178 ) 179 } 180 181 private fun prepareColorGrading() { 182 // Initialize the buffer with black, so that we don't ever draw garbage buffer. 183 rainConfig.glassRainShader.setInputShader("texture", SolidColorShader(Color.BLACK)) 184 rainConfig.colorGradingShader.setInputShader("texture", rainConfig.glassRainShader) 185 rainConfig.lut?.let { 186 rainConfig.colorGradingShader.setInputShader( 187 "lut", 188 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR) 189 ) 190 } 191 } 192 193 private companion object { 194 private const val MILLIS_TO_SECONDS = 1 / 1000f 195 } 196 } 197