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.snow 18 19 import android.graphics.Bitmap 20 import android.graphics.BitmapShader 21 import android.graphics.Canvas 22 import android.graphics.Matrix 23 import android.graphics.Paint 24 import android.graphics.RenderEffect 25 import android.graphics.RuntimeShader 26 import android.graphics.Shader 27 import android.util.SizeF 28 import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer 29 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Companion.DEFAULT_INTENSITY 30 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffectBase 31 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils 32 import com.google.android.wallpaper.weathereffects.graphics.utils.MathUtils 33 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.getScale 34 import com.google.android.wallpaper.weathereffects.graphics.utils.TimeUtils 35 import java.util.concurrent.Executor 36 import kotlin.math.abs 37 38 /** Defines and generates the rain weather effect animation. */ 39 class SnowEffect( 40 /** The config of the snow effect. */ 41 private val snowConfig: SnowEffectConfig, 42 foreground: Bitmap, 43 background: Bitmap, 44 private var intensity: Float = DEFAULT_INTENSITY, 45 /** The initial size of the surface where the effect will be shown. */ 46 private var surfaceSize: SizeF, 47 /** App main executor. */ 48 private val mainExecutor: Executor, 49 ) : WeatherEffectBase(foreground, background, surfaceSize) { 50 51 private var snowSpeed: Float = 0.8f 52 private val snowPaint = Paint().also { it.shader = snowConfig.colorGradingShader } 53 54 private var frameBuffer = FrameBuffer(background.width, background.height) 55 private val frameBufferPaint = Paint().also { it.shader = snowConfig.accumulatedSnowShader } 56 57 init { 58 frameBuffer.setRenderEffect( 59 RenderEffect.createBlurEffect( 60 BLUR_RADIUS / bitmapScale, 61 BLUR_RADIUS / bitmapScale, 62 Shader.TileMode.CLAMP, 63 ) 64 ) 65 updateTextureUniforms() 66 adjustCropping(surfaceSize) 67 prepareColorGrading() 68 updateGridSize(surfaceSize) 69 setIntensity(intensity) 70 71 // Generate accumulated snow at the end after we updated all the uniforms. 72 generateAccumulatedSnow() 73 } 74 75 override fun update(deltaMillis: Long, frameTimeNanos: Long) { 76 elapsedTime += snowSpeed * TimeUtils.millisToSeconds(deltaMillis) 77 78 snowConfig.shader.setFloatUniform("time", elapsedTime) 79 snowConfig.colorGradingShader.setInputShader("texture", snowConfig.shader) 80 } 81 82 override fun draw(canvas: Canvas) { 83 canvas.drawPaint(snowPaint) 84 } 85 86 override fun release() { 87 super.release() 88 frameBuffer.close() 89 } 90 91 override fun setIntensity(intensity: Float) { 92 super.setIntensity(intensity) 93 /** 94 * Increase effect speed as weather intensity decreases. This compensates for the floaty 95 * appearance when there are fewer particles at the original speed. 96 */ 97 if (this.intensity != intensity) { 98 snowSpeed = MathUtils.map(intensity, 0f, 1f, 2.5f, 1.7f) 99 this.intensity = intensity 100 } 101 } 102 103 override fun setBitmaps(foreground: Bitmap?, background: Bitmap): Boolean { 104 if (!super.setBitmaps(foreground, background)) { 105 return false 106 } 107 108 frameBuffer.close() 109 frameBuffer = FrameBuffer(background.width, background.height) 110 val newScale = getScale(parallaxMatrix) 111 bitmapScale = newScale 112 frameBuffer.setRenderEffect( 113 RenderEffect.createBlurEffect( 114 BLUR_RADIUS / bitmapScale, 115 BLUR_RADIUS / bitmapScale, 116 Shader.TileMode.CLAMP, 117 ) 118 ) 119 // GenerateAccumulatedSnow needs foreground for accumulatedSnowShader, and needs frameBuffer 120 // which is also changed with background 121 generateAccumulatedSnow() 122 return true 123 } 124 125 override val shader: RuntimeShader 126 get() = snowConfig.shader 127 128 override val colorGradingShader: RuntimeShader 129 get() = snowConfig.colorGradingShader 130 131 override val lut: Bitmap? 132 get() = snowConfig.lut 133 134 override val colorGradingIntensity: Float 135 get() = snowConfig.colorGradingIntensity 136 137 override fun setMatrix(matrix: Matrix) { 138 val oldScale = bitmapScale 139 super.setMatrix(matrix) 140 // Blur radius should change with scale because it decides the fluffiness of snow 141 if (abs(bitmapScale - oldScale) > FLOAT_TOLERANCE) { 142 frameBuffer.setRenderEffect( 143 RenderEffect.createBlurEffect( 144 BLUR_RADIUS / bitmapScale, 145 BLUR_RADIUS / bitmapScale, 146 Shader.TileMode.CLAMP, 147 ) 148 ) 149 generateAccumulatedSnow() 150 } 151 } 152 153 override fun updateTextureUniforms() { 154 super.updateTextureUniforms() 155 snowConfig.shader.setInputBuffer( 156 "noise", 157 BitmapShader(snowConfig.noiseTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT), 158 ) 159 } 160 161 private fun prepareColorGrading() { 162 snowConfig.colorGradingShader.setInputShader("texture", snowConfig.shader) 163 snowConfig.lut?.let { 164 snowConfig.colorGradingShader.setInputShader( 165 "lut", 166 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 167 ) 168 } 169 } 170 171 private fun generateAccumulatedSnow() { 172 val renderingCanvas = frameBuffer.beginDrawing() 173 snowConfig.accumulatedSnowShader.setFloatUniform("scale", bitmapScale) 174 snowConfig.accumulatedSnowShader.setFloatUniform( 175 "snowThickness", 176 SNOW_THICKNESS / bitmapScale, 177 ) 178 snowConfig.accumulatedSnowShader.setFloatUniform("screenWidth", surfaceSize.width) 179 snowConfig.accumulatedSnowShader.setInputBuffer( 180 "foreground", 181 BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 182 ) 183 184 renderingCanvas.drawPaint(frameBufferPaint) 185 frameBuffer.endDrawing() 186 187 frameBuffer.tryObtainingImage( 188 { image -> 189 snowConfig.shader.setInputBuffer( 190 "accumulatedSnow", 191 BitmapShader(image, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 192 ) 193 }, 194 mainExecutor, 195 ) 196 } 197 198 override fun updateGridSize(newSurfaceSize: SizeF) { 199 val gridSize = GraphicsUtils.computeDefaultGridSize(newSurfaceSize, snowConfig.pixelDensity) 200 snowConfig.shader.setFloatUniform("gridSize", 7 * gridSize, 2f * gridSize) 201 } 202 203 companion object { 204 val BLUR_RADIUS = 4f 205 // Use blur effect for both blurring the snow accumulation and generating a gradient edge 206 // so that intensity can control snow thickness by cut the gradient edge in snow_effect 207 // shader. 208 val SNOW_THICKNESS = 6f 209 } 210 } 211