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