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