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.utils 18 19 import android.content.Context 20 import android.content.res.AssetManager 21 import android.graphics.Bitmap 22 import android.graphics.BitmapFactory 23 import android.graphics.Rect 24 import android.graphics.RuntimeShader 25 import android.renderscript.Allocation 26 import android.renderscript.Element 27 import android.renderscript.RenderScript 28 import android.renderscript.ScriptIntrinsicBlur 29 import android.util.SizeF 30 import androidx.annotation.FloatRange 31 32 /** Contains functions for rendering. */ 33 object GraphicsUtils { 34 /* Default width dp is calculated as default_display_width / default_display_density. */ 35 private const val DEFAULT_WIDTH_DP = 1080 / 2.625f 36 37 /** 38 * Loads a shader from an asset file. 39 * 40 * @param assetManager an [AssetManager] instance. 41 * @param path path to the shader to load. 42 * @return returns a [RuntimeShader] object. 43 */ 44 fun loadShader(assetManager: AssetManager, path: String): RuntimeShader { 45 val shader = loadRawShader(assetManager, path) 46 val finalShader = resolveShaderIncludes(assetManager, shader) 47 return RuntimeShader(finalShader) 48 } 49 50 /** 51 * Loads a Bitmap from an asset file. 52 * 53 * @param assetManager an [AssetManager] instance. 54 * @param path path to the texture bitmap to load. 55 * @return returns a Bitmap. 56 */ 57 fun loadTexture(assetManager: AssetManager, path: String): Bitmap? { 58 return assetManager.open(path).use { 59 BitmapFactory.decodeStream( 60 it, 61 Rect(), 62 BitmapFactory.Options().apply { inPreferredConfig = Bitmap.Config.HARDWARE }, 63 ) 64 } 65 } 66 67 /** 68 * Blurs an image and returns it as a new one. 69 * 70 * @param context the application. 71 * @param sourceBitmap the original image that we want to blur. 72 * @param blurRadius the amount that we want to blur (only values from 0 to 25). 73 * @param config the bitmap config (optional). 74 * @return returns a Bitmap. 75 */ 76 fun blurImage( 77 context: Context, 78 sourceBitmap: Bitmap, 79 @FloatRange(from = 0.0, to = 25.0) blurRadius: Float, 80 config: Bitmap.Config = Bitmap.Config.ARGB_8888, 81 ): Bitmap { 82 // TODO: This might not be the ideal option, find a better one. 83 val blurredImage = Bitmap.createBitmap(sourceBitmap.copy(config, true)) 84 val renderScript = RenderScript.create(context) 85 val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript)) 86 val allocationIn = Allocation.createFromBitmap(renderScript, sourceBitmap) 87 val allocationOut = Allocation.createFromBitmap(renderScript, blurredImage) 88 blur.setRadius(blurRadius) 89 blur.setInput(allocationIn) 90 blur.forEach(allocationOut) 91 allocationOut.copyTo(blurredImage) 92 return blurredImage 93 } 94 95 /** 96 * @return the [Float] representing the aspect ratio of width / height, -1 if either width or 97 * height is equal to or less than 0. 98 */ 99 fun getAspectRatio(size: SizeF): Float { 100 val width = size.width 101 val height = size.height 102 103 return if (width <= 0 || height <= 0) { 104 -1f 105 } else width / height 106 } 107 108 /** 109 * Compute the weather effect default grid size. This takes into consideration the different 110 * display densities and aspect ratio so the effect looks good on displays with different sizes. 111 * 112 * @param surfaceSize the size of the surface where the wallpaper is being rendered. 113 * @param density the current display density. 114 * @return a [Float] representing the default size. 115 */ 116 fun computeDefaultGridSize(surfaceSize: SizeF, density: Float): Float { 117 val displayWidthDp = surfaceSize.width / density 118 val adjustedScale = 119 when { 120 // "COMPACT" 121 displayWidthDp < 600 -> 1f 122 // "MEDIUM" 123 displayWidthDp >= 600 && displayWidthDp < 840 -> 0.9f 124 // "EXPANDED" 125 else -> 0.8f 126 } 127 return adjustedScale * displayWidthDp / DEFAULT_WIDTH_DP 128 } 129 130 private fun resolveShaderIncludes(assetManager: AssetManager, string: String): String { 131 val match = Regex("[ \\t]*#include +\"([\\w\\d./]+)\"") 132 return string.replace(match) { m -> 133 val (includePath) = m.destructured 134 getResolvedShaderPath(assetManager, includePath) 135 } 136 } 137 138 private fun getResolvedShaderPath(assetManager: AssetManager, includePath: String): String { 139 val string = loadRawShader(assetManager, includePath) 140 return resolveShaderIncludes(assetManager, string) 141 } 142 143 private fun loadRawShader(assetManager: AssetManager, path: String): String { 144 return assetManager.open(path).bufferedReader().use { it.readText() } 145 } 146 } 147