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.BitmapShader
20 import android.graphics.Canvas
21 import android.graphics.Paint
22 import android.graphics.RenderEffect
23 import android.graphics.Shader
24 import android.util.SizeF
25 import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer
26 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect
27 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
28 import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop
29 import com.google.android.wallpaper.weathereffects.graphics.utils.MathUtils
30 import java.util.concurrent.Executor
31 import kotlin.random.Random
32 
33 /** Defines and generates the rain weather effect animation. */
34 class SnowEffect(
35     /** The config of the snow effect. */
36     private val snowConfig: SnowEffectConfig,
37     /** The initial size of the surface where the effect will be shown. */
38     surfaceSize: SizeF,
39     /** App main executor. */
40     private val mainExecutor: Executor
41 ) : WeatherEffect {
42 
43     private var snowSpeed: Float = 0.8f
44     private val snowPaint = Paint().also { it.shader = snowConfig.colorGradingShader }
45     private var elapsedTime: Float = 0f
46 
47     private val frameBuffer = FrameBuffer(snowConfig.background.width, snowConfig.background.height)
48     private val frameBufferPaint = Paint().also { it.shader = snowConfig.accumulatedSnowShader }
49 
50     init {
51         frameBuffer.setRenderEffect(RenderEffect.createBlurEffect(4f, 4f, Shader.TileMode.CLAMP))
52         updateTextureUniforms()
53         adjustCropping(surfaceSize)
54         prepareColorGrading()
55         setIntensity(snowConfig.intensity)
56 
57         // Generate accumulated snow at the end after we updated all the uniforms.
58         generateAccumulatedSnow()
59     }
60 
61     override fun resize(newSurfaceSize: SizeF) = adjustCropping(newSurfaceSize)
62 
63     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
64         elapsedTime += snowSpeed * deltaMillis * MILLIS_TO_SECONDS
65 
66         snowConfig.shader.setFloatUniform("time", elapsedTime)
67         snowConfig.colorGradingShader.setInputShader("texture", snowConfig.shader)
68     }
69 
70     override fun draw(canvas: Canvas) {
71         canvas.drawPaint(snowPaint)
72     }
73 
74     override fun reset() {
75         elapsedTime = Random.nextFloat() * 90f
76     }
77 
78     override fun release() {
79         snowConfig.lut?.recycle()
80         frameBuffer.close()
81     }
82 
83     override fun setIntensity(intensity: Float) {
84         /**
85          * Increase effect speed as weather intensity decreases. This compensates for the floaty
86          * appearance when there are fewer particles at the original speed.
87          */
88         snowSpeed = MathUtils.map(intensity, 0f, 1f, 2.5f, 1.7f)
89 
90         snowConfig.shader.setFloatUniform("intensity", intensity)
91         snowConfig.colorGradingShader.setFloatUniform(
92             "intensity",
93             snowConfig.colorGradingIntensity * intensity
94         )
95         snowConfig.accumulatedSnowShader.setFloatUniform(
96             "snowThickness",
97             snowConfig.maxAccumulatedSnowThickness * intensity
98         )
99         // Regenerate accumulated snow since the uniform changed.
100         generateAccumulatedSnow()
101     }
102 
103     private fun adjustCropping(surfaceSize: SizeF) {
104         val imageCropFgd =
105             ImageCrop.centerCoverCrop(
106                 surfaceSize.width,
107                 surfaceSize.height,
108                 snowConfig.foreground.width.toFloat(),
109                 snowConfig.foreground.height.toFloat()
110             )
111         snowConfig.shader.setFloatUniform(
112             "uvOffsetFgd",
113             imageCropFgd.leftOffset,
114             imageCropFgd.topOffset
115         )
116         snowConfig.shader.setFloatUniform(
117             "uvScaleFgd",
118             imageCropFgd.horizontalScale,
119             imageCropFgd.verticalScale
120         )
121         val imageCropBgd =
122             ImageCrop.centerCoverCrop(
123                 surfaceSize.width,
124                 surfaceSize.height,
125                 snowConfig.background.width.toFloat(),
126                 snowConfig.background.height.toFloat()
127             )
128         snowConfig.shader.setFloatUniform(
129             "uvOffsetBgd",
130             imageCropBgd.leftOffset,
131             imageCropBgd.topOffset
132         )
133         snowConfig.shader.setFloatUniform(
134             "uvScaleBgd",
135             imageCropBgd.horizontalScale,
136             imageCropBgd.verticalScale
137         )
138         snowConfig.shader.setFloatUniform("screenSize", surfaceSize.width, surfaceSize.height)
139         snowConfig.shader.setFloatUniform(
140             "screenAspectRatio",
141             GraphicsUtils.getAspectRatio(surfaceSize)
142         )
143     }
144 
145     private fun updateTextureUniforms() {
146         snowConfig.shader.setInputBuffer(
147             "foreground",
148             BitmapShader(snowConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
149         )
150 
151         snowConfig.shader.setInputBuffer(
152             "background",
153             BitmapShader(snowConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
154         )
155 
156         snowConfig.shader.setInputBuffer(
157             "noise",
158             BitmapShader(snowConfig.noiseTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
159         )
160     }
161 
162     private fun prepareColorGrading() {
163         snowConfig.colorGradingShader.setInputShader("texture", snowConfig.shader)
164         snowConfig.lut?.let {
165             snowConfig.colorGradingShader.setInputShader(
166                 "lut",
167                 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
168             )
169         }
170     }
171 
172     private fun generateAccumulatedSnow() {
173         val renderingCanvas = frameBuffer.beginDrawing()
174         snowConfig.accumulatedSnowShader.setFloatUniform(
175             "imageWidth",
176             renderingCanvas.width.toFloat()
177         )
178         snowConfig.accumulatedSnowShader.setInputBuffer(
179             "foreground",
180             BitmapShader(snowConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
181         )
182         renderingCanvas.drawPaint(frameBufferPaint)
183         frameBuffer.endDrawing()
184 
185         frameBuffer.tryObtainingImage(
186             { image ->
187                 snowConfig.shader.setInputBuffer(
188                     "accumulatedSnow",
189                     BitmapShader(image, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
190                 )
191             },
192             mainExecutor
193         )
194     }
195 
196     private companion object {
197         private const val MILLIS_TO_SECONDS = 1 / 1000f
198     }
199 }
200