• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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