1 /*
<lambda>null2  * Copyright (C) 2021 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.example.testapp
18 
19 import android.renderscript.toolkit.Range2d
20 import kotlin.math.max
21 import kotlin.math.min
22 import kotlin.math.pow
23 import kotlin.math.sqrt
24 
25 /**
26  * Reference implementation of a Blur operation.
27  */
28 @ExperimentalUnsignedTypes
29 fun referenceBlur(inputArray: ByteArray,
30                   vectorSize: Int,
31                   sizeX: Int,
32                   sizeY: Int,
33                   radius: Int = 5, restriction: Range2d?): ByteArray {
34     val maxRadius = 25
35     require (radius in 1..maxRadius) {
36         "RenderScriptToolkit blur. Radius should be between 1 and $maxRadius. $radius provided."
37     }
38     val gaussian = buildGaussian(radius)
39 
40     // Convert input data to float so that the blurring goes faster.
41     val inputValues = FloatArray(inputArray.size) { byteToUnitFloat(inputArray[it].toUByte()) }
42     val inputInFloat = FloatVector2dArray(inputValues, vectorSize, sizeX, sizeY)
43 
44     val scratch = horizontalBlur(inputInFloat, gaussian, radius, restriction)
45     val outInFloat = verticalBlur(scratch, gaussian, radius, restriction)
46 
47     // Convert the results back to bytes.
48     return ByteArray(outInFloat.values.size) { unitFloatClampedToUByte(outInFloat.values[it]).toByte() }
49 }
50 
51 /**
52  * Blurs along the horizontal direction using the specified gaussian weights.
53  */
horizontalBlurnull54 private fun horizontalBlur(
55     input: FloatVector2dArray,
56     gaussian: FloatArray,
57     radius: Int,
58     restriction: Range2d?
59 ): FloatVector2dArray {
60     var expandedRestriction: Range2d? = null
61     if (restriction != null) {
62         // Expand the restriction in the vertical direction so that the vertical pass
63         // will have all the data it needs.
64         expandedRestriction = Range2d(
65             restriction.startX,
66             restriction.endX,
67             max(restriction.startY - radius, 0),
68             min(restriction.endY + radius, input.sizeY)
69         )
70     }
71 
72     input.clipAccessToRange = true
73     val out = input.createSameSized()
74     out.forEach(expandedRestriction) { x, y ->
75         for ((gaussianIndex, delta: Int) in (-radius..radius).withIndex()) {
76             val v = input[x + delta, y] * gaussian[gaussianIndex]
77             out[x, y] += v
78         }
79     }
80     return out
81 }
82 
83 /**
84  * Blurs along the horizontal direction using the specified gaussian weights.
85  */
verticalBlurnull86 private fun verticalBlur(
87     input: FloatVector2dArray,
88     gaussian: FloatArray,
89     radius: Int,
90     restriction: Range2d?
91 ): FloatVector2dArray {
92     input.clipAccessToRange = true
93     val out = input.createSameSized()
94     out.forEach(restriction) { x, y ->
95         for ((gaussianIndex, delta: Int) in (-radius..radius).withIndex()) {
96             val v = input[x, y + delta] * gaussian[gaussianIndex]
97             out[x, y] += v
98         }
99     }
100     return out
101 }
102 
103 /**
104  * Builds an array of gaussian weights that will be used for doing the horizontal and vertical
105  * blur.
106  *
107  * @return An array of (2 * radius + 1) floats.
108  */
buildGaussiannull109 private fun buildGaussian(radius: Int): FloatArray {
110     val e: Float = kotlin.math.E.toFloat()
111     val pi: Float = kotlin.math.PI.toFloat()
112     val sigma: Float = 0.4f * radius.toFloat() + 0.6f
113     val coefficient1: Float = 1.0f / (sqrt(2.0f * pi) * sigma)
114     val coefficient2: Float = -1.0f / (2.0f * sigma * sigma)
115 
116     var sum = 0.0f
117     val gaussian = FloatArray(radius * 2 + 1)
118     for (r in -radius..radius) {
119         val floatR: Float = r.toFloat()
120         val v: Float = coefficient1 * e.pow(floatR * floatR * coefficient2)
121         gaussian[r + radius] = v
122         sum += v
123     }
124 
125     // Normalize so that the sum of the weights equal 1f.
126     val normalizeFactor: Float = 1.0f / sum
127     for (r in -radius..radius) {
128         gaussian[r + radius] *= normalizeFactor
129     }
130     return gaussian
131 }
132