1 /* 2 * Copyright (C) 2024 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 18 19 import android.graphics.Bitmap 20 import android.graphics.BitmapShader 21 import android.graphics.Canvas 22 import android.graphics.Matrix 23 import android.graphics.RuntimeShader 24 import android.graphics.Shader 25 import android.util.SizeF 26 import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils 27 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.calculateTransformDifference 28 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.centerCropMatrix 29 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.getScale 30 import com.google.android.wallpaper.weathereffects.graphics.utils.MatrixUtils.invertAndTransposeMatrix 31 import kotlin.random.Random 32 33 /** Give default implementation of some functions in WeatherEffect */ 34 abstract class WeatherEffectBase( 35 protected var foreground: Bitmap, 36 protected var background: Bitmap, 37 /** The initial size of the surface where the effect will be shown. */ 38 private var surfaceSize: SizeF, 39 ) : WeatherEffect { 40 private var centerCropMatrix: Matrix = 41 centerCropMatrix( 42 surfaceSize, 43 SizeF(background.width.toFloat(), background.height.toFloat()), 44 ) 45 protected var parallaxMatrix = Matrix(centerCropMatrix) 46 // Currently, we use same transform for both foreground and background 47 protected open val transformMatrixBitmap: FloatArray = FloatArray(9) 48 // Apply to weather components not rely on image textures 49 // Should be identity matrix in editor, and only change when parallax applied in homescreen 50 private val transformMatrixWeather: FloatArray = FloatArray(9) 51 protected var bitmapScale = getScale(centerCropMatrix) 52 protected var elapsedTime: Float = 0f 53 54 abstract val shader: RuntimeShader 55 abstract val colorGradingShader: RuntimeShader 56 abstract val lut: Bitmap? 57 abstract val colorGradingIntensity: Float 58 setMatrixnull59 override fun setMatrix(matrix: Matrix) { 60 this.parallaxMatrix.set(matrix) 61 bitmapScale = getScale(parallaxMatrix) 62 adjustCropping(surfaceSize) 63 } 64 adjustCroppingnull65 open fun adjustCropping(newSurfaceSize: SizeF) { 66 invertAndTransposeMatrix(parallaxMatrix, transformMatrixBitmap) 67 calculateTransformDifference(centerCropMatrix, parallaxMatrix, transformMatrixWeather) 68 shader.setFloatUniform("transformMatrixBitmap", transformMatrixBitmap) 69 shader.setFloatUniform("transformMatrixWeather", transformMatrixWeather) 70 shader.setFloatUniform("screenSize", newSurfaceSize.width, newSurfaceSize.height) 71 shader.setFloatUniform("screenAspectRatio", GraphicsUtils.getAspectRatio(newSurfaceSize)) 72 } 73 updateGridSizenull74 open fun updateGridSize(newSurfaceSize: SizeF) {} 75 resizenull76 override fun resize(newSurfaceSize: SizeF) { 77 surfaceSize = newSurfaceSize 78 adjustCropping(newSurfaceSize) 79 updateGridSize(newSurfaceSize) 80 } 81 updatenull82 abstract override fun update(deltaMillis: Long, frameTimeNanos: Long) 83 84 abstract override fun draw(canvas: Canvas) 85 86 override fun reset() { 87 elapsedTime = Random.nextFloat() * 90f 88 } 89 releasenull90 override fun release() { 91 lut?.recycle() 92 } 93 setIntensitynull94 override fun setIntensity(intensity: Float) { 95 shader.setFloatUniform("intensity", intensity) 96 colorGradingShader.setFloatUniform("intensity", colorGradingIntensity * intensity) 97 } 98 setBitmapsnull99 override fun setBitmaps(foreground: Bitmap?, background: Bitmap): Boolean { 100 if (this.foreground == foreground && this.background == background) { 101 return false 102 } 103 // Only when background changes, we can infer the bitmap set changes 104 if (this.background != background) { 105 this.background.recycle() 106 this.foreground.recycle() 107 } 108 this.foreground = foreground ?: background 109 this.background = background 110 111 centerCropMatrix = 112 centerCropMatrix( 113 surfaceSize, 114 SizeF(background.width.toFloat(), background.height.toFloat()), 115 ) 116 parallaxMatrix.set(centerCropMatrix) 117 bitmapScale = getScale(centerCropMatrix) 118 shader.setInputBuffer( 119 "background", 120 BitmapShader(this.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 121 ) 122 shader.setInputBuffer( 123 "foreground", 124 BitmapShader(this.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 125 ) 126 adjustCropping(surfaceSize) 127 return true 128 } 129 updateTextureUniformsnull130 open fun updateTextureUniforms() { 131 shader.setInputBuffer( 132 "foreground", 133 BitmapShader(foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 134 ) 135 136 shader.setInputBuffer( 137 "background", 138 BitmapShader(background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR), 139 ) 140 } 141 142 companion object { 143 // When extracting the scale from the parallax matrix, there will be a very small difference 144 // due to floating-point precision. 145 // We use FLOAT_TOLERANCE to avoid triggering actions on these insignificant scale changes. 146 const val FLOAT_TOLERANCE = 0.0001F 147 } 148 } 149