• 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.rain
18 
19 import android.graphics.Bitmap
20 import android.graphics.BitmapShader
21 import android.graphics.Canvas
22 import android.graphics.Paint
23 import android.graphics.RenderEffect
24 import android.graphics.RuntimeShader
25 import android.graphics.Shader
26 import android.util.SizeF
27 import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer
28 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect.Companion.DEFAULT_INTENSITY
29 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffectBase
30 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
31 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.getScale
32 import com.google.android.wallpaper.weathereffects.graphics.utils.TimeUtils
33 import java.util.concurrent.Executor
34 
35 /** Defines and generates the rain weather effect animation. */
36 class RainEffect(
37     /** The config of the rain effect. */
38     private val rainConfig: RainEffectConfig,
39     foreground: Bitmap,
40     background: Bitmap,
41     intensity: Float = DEFAULT_INTENSITY,
42     /** The initial size of the surface where the effect will be shown. */
43     surfaceSize: SizeF,
44     private val mainExecutor: Executor,
45 ) : WeatherEffectBase(foreground, background, surfaceSize) {
46 
47     private val rainPaint = Paint().also { it.shader = rainConfig.colorGradingShader }
48     // Outline buffer is set with bitmap size, so we need to multiply blur radius by scale to get
49     // consistent blur across different surface
50     private var outlineBuffer =
51         FrameBuffer(background.width, background.height).apply {
52             setRenderEffect(
53                 RenderEffect.createBlurEffect(
54                     BLUR_RADIUS / bitmapScale,
55                     BLUR_RADIUS / bitmapScale,
56                     Shader.TileMode.CLAMP,
57                 )
58             )
59         }
60     private val outlineBufferPaint = Paint().also { it.shader = rainConfig.outlineShader }
61 
62     init {
63         updateTextureUniforms()
64         adjustCropping(surfaceSize)
65         prepareColorGrading()
66         updateGridSize(surfaceSize)
67         setIntensity(intensity)
68     }
69 
70     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
71         elapsedTime += TimeUtils.millisToSeconds(deltaMillis)
72 
73         rainConfig.rainShowerShader.setFloatUniform("time", elapsedTime)
74         rainConfig.colorGradingShader.setInputShader("texture", rainConfig.rainShowerShader)
75     }
76 
77     override fun draw(canvas: Canvas) {
78         canvas.drawPaint(rainPaint)
79     }
80 
81     override fun release() {
82         super.release()
83         outlineBuffer.close()
84     }
85 
86     override fun setBitmaps(foreground: Bitmap?, background: Bitmap): Boolean {
87         if (!super.setBitmaps(foreground, background)) {
88             return false
89         }
90         outlineBuffer.close()
91         outlineBuffer = FrameBuffer(background.width, background.height)
92 
93         bitmapScale = getScale(parallaxMatrix)
94         // Different from snow effects, we only need to change blur radius when bitmaps change
95         // it only gives the range of rain splashes and doesn't influence the visual effects
96         outlineBuffer.setRenderEffect(
97             RenderEffect.createBlurEffect(
98                 BLUR_RADIUS / bitmapScale,
99                 BLUR_RADIUS / bitmapScale,
100                 Shader.TileMode.CLAMP,
101             )
102         )
103 
104         updateTextureUniforms()
105         return true
106     }
107 
108     override val shader: RuntimeShader
109         get() = rainConfig.rainShowerShader
110 
111     override val colorGradingShader: RuntimeShader
112         get() = rainConfig.colorGradingShader
113 
114     override val lut: Bitmap?
115         get() = rainConfig.lut
116 
117     override val colorGradingIntensity: Float
118         get() = rainConfig.colorGradingIntensity
119 
120     override fun updateTextureUniforms() {
121         val foregroundBuffer =
122             BitmapShader(super.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
123         rainConfig.rainShowerShader.setInputBuffer("foreground", foregroundBuffer)
124         rainConfig.outlineShader.setInputBuffer("texture", foregroundBuffer)
125 
126         rainConfig.rainShowerShader.setInputBuffer(
127             "background",
128             BitmapShader(super.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
129         )
130         createOutlineBuffer()
131     }
132 
133     /**
134      * It's necessary to create outline buffer only when bitmaps change, only intensity change won't
135      * create a new one cause we refer to intensity in each cell when drawing splashes.
136      */
137     private fun createOutlineBuffer() {
138         rainConfig.outlineShader.setFloatUniform(
139             "thickness",
140             MAX_RAIN_OUTLINE_THICKNESS / bitmapScale,
141         )
142         val canvas = outlineBuffer.beginDrawing()
143         canvas.drawPaint(outlineBufferPaint)
144         outlineBuffer.endDrawing()
145 
146         outlineBuffer.tryObtainingImage(
147             { buffer ->
148                 rainConfig.rainShowerShader.setInputBuffer(
149                     "outlineBuffer",
150                     BitmapShader(buffer, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
151                 )
152             },
153             mainExecutor,
154         )
155     }
156 
157     private fun prepareColorGrading() {
158         // Initialize the buffer with black, so that we don't ever draw garbage buffer.
159         rainConfig.colorGradingShader.setInputShader("texture", rainConfig.rainShowerShader)
160         rainConfig.lut?.let {
161             rainConfig.colorGradingShader.setInputShader(
162                 "lut",
163                 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR),
164             )
165         }
166     }
167 
168     override fun updateGridSize(newSurfaceSize: SizeF) {
169         val widthScreenScale =
170             GraphicsUtils.computeDefaultGridSize(newSurfaceSize, rainConfig.pixelDensity)
171         rainConfig.rainShowerShader.setFloatUniform("gridScale", widthScreenScale)
172     }
173 
174     companion object {
175         const val MAX_RAIN_OUTLINE_THICKNESS = 11f
176         const val BLUR_RADIUS = 2f
177     }
178 }
179