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.Bitmap
20 import android.graphics.BitmapShader
21 import android.graphics.Canvas
22 import android.graphics.Color
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.SolidColorShader
33 import com.google.android.wallpaper.weathereffects.graphics.utils.TimeUtils
34 import java.util.concurrent.Executor
35 
36 /** Defines and generates the rain weather effect animation. */
37 class RainEffect(
38     /** The config of the rain effect. */
39     private val rainConfig: RainEffectConfig,
40     foreground: Bitmap,
41     background: Bitmap,
42     intensity: Float = DEFAULT_INTENSITY,
43     /** The initial size of the surface where the effect will be shown. */
44     surfaceSize: SizeF,
45     private val mainExecutor: Executor,
46 ) : WeatherEffectBase(foreground, background, surfaceSize) {
47 
48     private val rainPaint = Paint().also { it.shader = rainConfig.colorGradingShader }
49 
50     // Set blur effect to reduce the outline noise. No need to set blur effect every time we
51     // re-generate the outline buffer.
52     private var outlineBuffer =
53         FrameBuffer(background.width, background.height).apply {
54             setRenderEffect(RenderEffect.createBlurEffect(2f, 2f, Shader.TileMode.CLAMP))
55         }
56     private val outlineBufferPaint = Paint().also { it.shader = rainConfig.outlineShader }
57 
58     init {
59         updateTextureUniforms()
60         adjustCropping(surfaceSize)
61         prepareColorGrading()
62         updateGridSize(surfaceSize)
63         setIntensity(intensity)
64     }
65 
66     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
67         elapsedTime += TimeUtils.millisToSeconds(deltaMillis)
68 
69         rainConfig.rainShowerShader.setFloatUniform("time", elapsedTime)
70         rainConfig.glassRainShader.setFloatUniform("time", elapsedTime)
71 
72         rainConfig.glassRainShader.setInputShader("texture", rainConfig.rainShowerShader)
73         rainConfig.colorGradingShader.setInputShader("texture", rainConfig.glassRainShader)
74     }
75 
76     override fun draw(canvas: Canvas) {
77         canvas.drawPaint(rainPaint)
78     }
79 
80     override fun release() {
81         super.release()
82         outlineBuffer.close()
83     }
84 
85     override fun setIntensity(intensity: Float) {
86         super.setIntensity(intensity)
87         rainConfig.glassRainShader.setFloatUniform("intensity", intensity)
88         val thickness = 1f + intensity * 10f
89         rainConfig.outlineShader.setFloatUniform("thickness", thickness)
90 
91         // Need to recreate the outline buffer as the uniform has changed.
92         createOutlineBuffer()
93     }
94 
95     override fun setBitmaps(foreground: Bitmap?, background: Bitmap) {
96         super.setBitmaps(foreground, background)
97         outlineBuffer =
98             FrameBuffer(background.width, background.height).apply {
99                 setRenderEffect(RenderEffect.createBlurEffect(2f, 2f, Shader.TileMode.CLAMP))
100             }
101         updateTextureUniforms()
102 
103         // Need to recreate the outline buffer as the outlineBuffer has changed due to background
104         createOutlineBuffer()
105     }
106 
107     override val shader: RuntimeShader
108         get() = rainConfig.rainShowerShader
109 
110     override val colorGradingShader: RuntimeShader
111         get() = rainConfig.colorGradingShader
112 
113     override val lut: Bitmap?
114         get() = rainConfig.lut
115 
116     override val colorGradingIntensity: Float
117         get() = rainConfig.colorGradingIntensity
118 
119     override fun adjustCropping(newSurfaceSize: SizeF) {
120         super.adjustCropping(newSurfaceSize)
121         rainConfig.glassRainShader.setFloatUniform(
122             "screenSize",
123             newSurfaceSize.width,
124             newSurfaceSize.height,
125         )
126 
127         val screenAspectRatio = GraphicsUtils.getAspectRatio(newSurfaceSize)
128         rainConfig.glassRainShader.setFloatUniform("screenAspectRatio", screenAspectRatio)
129     }
130 
131     override fun updateTextureUniforms() {
132         val foregroundBuffer =
133             BitmapShader(super.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
134         rainConfig.rainShowerShader.setInputBuffer("foreground", foregroundBuffer)
135         rainConfig.outlineShader.setInputBuffer("texture", foregroundBuffer)
136 
137         rainConfig.rainShowerShader.setInputBuffer(
138             "background",
139             BitmapShader(super.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
140         )
141     }
142 
143     private fun createOutlineBuffer() {
144         val canvas = outlineBuffer.beginDrawing()
145         canvas.drawPaint(outlineBufferPaint)
146         outlineBuffer.endDrawing()
147 
148         outlineBuffer.tryObtainingImage(
149             { buffer ->
150                 rainConfig.rainShowerShader.setInputBuffer(
151                     "outlineBuffer",
152                     BitmapShader(buffer, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
153                 )
154             },
155             mainExecutor,
156         )
157     }
158 
159     private fun prepareColorGrading() {
160         // Initialize the buffer with black, so that we don't ever draw garbage buffer.
161         rainConfig.glassRainShader.setInputShader("texture", SolidColorShader(Color.BLACK))
162         rainConfig.colorGradingShader.setInputShader("texture", rainConfig.glassRainShader)
163         rainConfig.lut?.let {
164             rainConfig.colorGradingShader.setInputShader(
165                 "lut",
166                 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
167             )
168         }
169     }
170 
171     override fun updateGridSize(newSurfaceSize: SizeF) {
172         val widthScreenScale =
173             GraphicsUtils.computeDefaultGridSize(newSurfaceSize, rainConfig.pixelDensity)
174         rainConfig.rainShowerShader.setFloatUniform("gridScale", widthScreenScale)
175         rainConfig.glassRainShader.setFloatUniform("gridScale", widthScreenScale)
176     }
177 }
178