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