1 /*
2  * 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.fog
18 
19 import android.graphics.BitmapShader
20 import android.graphics.Canvas
21 import android.graphics.Paint
22 import android.graphics.Shader
23 import android.util.SizeF
24 import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect
25 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
26 import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop
27 import kotlin.math.sin
28 import kotlin.random.Random
29 
30 /** Defines and generates the fog weather effect animation. */
31 class FogEffect(
32     private val fogConfig: FogEffectConfig,
33     /** The initial size of the surface where the effect will be shown. */
34     surfaceSize: SizeF
35 ) : WeatherEffect {
36 
<lambda>null37     private val fogPaint = Paint().also { it.shader = fogConfig.colorGradingShader }
38     private var elapsedTime: Float = 0f
39 
40     init {
41         updateTextureUniforms()
42         adjustCropping(surfaceSize)
43         prepareColorGrading()
44         setIntensity(fogConfig.intensity)
45     }
46 
resizenull47     override fun resize(newSurfaceSize: SizeF) = adjustCropping(newSurfaceSize)
48 
49     override fun update(deltaMillis: Long, frameTimeNanos: Long) {
50         val deltaTime = deltaMillis * MILLIS_TO_SECONDS
51 
52         val time = frameTimeNanos.toFloat() * NANOS_TO_SECONDS
53         // Variation range [0.4, 1]. We don't want the variation to be 0.
54         val variation = sin(0.06f * time + sin(0.18f * time)) * 0.3f + 0.7f
55         elapsedTime += variation * deltaTime
56 
57         val scaledElapsedTime = elapsedTime * 0.248f
58 
59         val variationFgd0 = 0.256f * sin(scaledElapsedTime)
60         val variationFgd1 = 0.156f * sin(scaledElapsedTime) * sin(scaledElapsedTime)
61         val timeFgd0 = 0.4f * elapsedTime * 5f + variationFgd0
62         val timeFgd1 = 0.03f * elapsedTime * 5f + variationFgd1
63 
64         val variationBgd0 = 0.156f * sin((scaledElapsedTime + Math.PI.toFloat() / 2.0f))
65         val variationBgd1 =
66             0.0156f * sin((scaledElapsedTime + Math.PI.toFloat() / 3.0f)) * sin(scaledElapsedTime)
67         val timeBgd0 = 0.8f * elapsedTime * 5f + variationBgd0
68         val timeBgd1 = 0.2f * elapsedTime * 5f + variationBgd1
69 
70         fogConfig.shader.setFloatUniform("time", timeFgd0, timeFgd1, timeBgd0, timeBgd1)
71 
72         fogConfig.colorGradingShader.setInputShader("texture", fogConfig.shader)
73     }
74 
drawnull75     override fun draw(canvas: Canvas) {
76         canvas.drawPaint(fogPaint)
77     }
78 
resetnull79     override fun reset() {
80         elapsedTime = Random.nextFloat() * 90f
81     }
82 
releasenull83     override fun release() {
84         fogConfig.lut?.recycle()
85     }
86 
setIntensitynull87     override fun setIntensity(intensity: Float) {
88         fogConfig.shader.setFloatUniform("intensity", intensity)
89         fogConfig.colorGradingShader.setFloatUniform(
90             "intensity",
91             fogConfig.colorGradingIntensity * intensity
92         )
93     }
94 
adjustCroppingnull95     private fun adjustCropping(surfaceSize: SizeF) {
96         val imageCropFgd =
97             ImageCrop.centerCoverCrop(
98                 surfaceSize.width,
99                 surfaceSize.height,
100                 fogConfig.foreground.width.toFloat(),
101                 fogConfig.foreground.height.toFloat()
102             )
103         fogConfig.shader.setFloatUniform(
104             "uvOffsetFgd",
105             imageCropFgd.leftOffset,
106             imageCropFgd.topOffset
107         )
108         fogConfig.shader.setFloatUniform(
109             "uvScaleFgd",
110             imageCropFgd.horizontalScale,
111             imageCropFgd.verticalScale
112         )
113         val imageCropBgd =
114             ImageCrop.centerCoverCrop(
115                 surfaceSize.width,
116                 surfaceSize.height,
117                 fogConfig.background.width.toFloat(),
118                 fogConfig.background.height.toFloat()
119             )
120         fogConfig.shader.setFloatUniform(
121             "uvOffsetBgd",
122             imageCropBgd.leftOffset,
123             imageCropBgd.topOffset
124         )
125         fogConfig.shader.setFloatUniform(
126             "uvScaleBgd",
127             imageCropBgd.horizontalScale,
128             imageCropBgd.verticalScale
129         )
130         fogConfig.shader.setFloatUniform("screenSize", surfaceSize.width, surfaceSize.height)
131         fogConfig.shader.setFloatUniform(
132             "screenAspectRatio",
133             GraphicsUtils.getAspectRatio(surfaceSize)
134         )
135     }
136 
updateTextureUniformsnull137     private fun updateTextureUniforms() {
138         fogConfig.shader.setInputBuffer(
139             "foreground",
140             BitmapShader(fogConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
141         )
142 
143         fogConfig.shader.setInputBuffer(
144             "background",
145             BitmapShader(fogConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
146         )
147 
148         fogConfig.shader.setInputBuffer(
149             "clouds",
150             BitmapShader(fogConfig.cloudsTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
151         )
152 
153         fogConfig.shader.setFloatUniform(
154             "cloudsSize",
155             fogConfig.cloudsTexture.width.toFloat(),
156             fogConfig.cloudsTexture.height.toFloat()
157         )
158 
159         fogConfig.shader.setInputBuffer(
160             "fog",
161             BitmapShader(fogConfig.fogTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
162         )
163 
164         fogConfig.shader.setFloatUniform(
165             "fogSize",
166             fogConfig.fogTexture.width.toFloat(),
167             fogConfig.fogTexture.height.toFloat()
168         )
169 
170         fogConfig.shader.setFloatUniform("pixelDensity", fogConfig.pixelDensity)
171     }
172 
prepareColorGradingnull173     private fun prepareColorGrading() {
174         fogConfig.colorGradingShader.setInputShader("texture", fogConfig.shader)
175         fogConfig.lut?.let {
176             fogConfig.colorGradingShader.setInputShader(
177                 "lut",
178                 BitmapShader(it, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
179             )
180         }
181         fogConfig.colorGradingShader.setFloatUniform("intensity", fogConfig.colorGradingIntensity)
182     }
183 
184     private companion object {
185 
186         private const val MILLIS_TO_SECONDS = 1 / 1000f
187         private const val NANOS_TO_SECONDS = 1 / 1_000_000_000f
188     }
189 }
190