1*e1eccf28SAndroid Build Coastguard Worker /*
<lambda>null2*e1eccf28SAndroid Build Coastguard Worker * Copyright (C) 2021 The Android Open Source Project
3*e1eccf28SAndroid Build Coastguard Worker *
4*e1eccf28SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*e1eccf28SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*e1eccf28SAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*e1eccf28SAndroid Build Coastguard Worker *
8*e1eccf28SAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0
9*e1eccf28SAndroid Build Coastguard Worker *
10*e1eccf28SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*e1eccf28SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*e1eccf28SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*e1eccf28SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*e1eccf28SAndroid Build Coastguard Worker * limitations under the License.
15*e1eccf28SAndroid Build Coastguard Worker */
16*e1eccf28SAndroid Build Coastguard Worker
17*e1eccf28SAndroid Build Coastguard Worker package com.example.testapp
18*e1eccf28SAndroid Build Coastguard Worker
19*e1eccf28SAndroid Build Coastguard Worker import android.renderscript.toolkit.Range2d
20*e1eccf28SAndroid Build Coastguard Worker import kotlin.math.max
21*e1eccf28SAndroid Build Coastguard Worker import kotlin.math.min
22*e1eccf28SAndroid Build Coastguard Worker import kotlin.math.pow
23*e1eccf28SAndroid Build Coastguard Worker import kotlin.math.sqrt
24*e1eccf28SAndroid Build Coastguard Worker
25*e1eccf28SAndroid Build Coastguard Worker /**
26*e1eccf28SAndroid Build Coastguard Worker * Reference implementation of a Blur operation.
27*e1eccf28SAndroid Build Coastguard Worker */
28*e1eccf28SAndroid Build Coastguard Worker @ExperimentalUnsignedTypes
29*e1eccf28SAndroid Build Coastguard Worker fun referenceBlur(inputArray: ByteArray,
30*e1eccf28SAndroid Build Coastguard Worker vectorSize: Int,
31*e1eccf28SAndroid Build Coastguard Worker sizeX: Int,
32*e1eccf28SAndroid Build Coastguard Worker sizeY: Int,
33*e1eccf28SAndroid Build Coastguard Worker radius: Int = 5, restriction: Range2d?): ByteArray {
34*e1eccf28SAndroid Build Coastguard Worker val maxRadius = 25
35*e1eccf28SAndroid Build Coastguard Worker require (radius in 1..maxRadius) {
36*e1eccf28SAndroid Build Coastguard Worker "RenderScriptToolkit blur. Radius should be between 1 and $maxRadius. $radius provided."
37*e1eccf28SAndroid Build Coastguard Worker }
38*e1eccf28SAndroid Build Coastguard Worker val gaussian = buildGaussian(radius)
39*e1eccf28SAndroid Build Coastguard Worker
40*e1eccf28SAndroid Build Coastguard Worker // Convert input data to float so that the blurring goes faster.
41*e1eccf28SAndroid Build Coastguard Worker val inputValues = FloatArray(inputArray.size) { byteToUnitFloat(inputArray[it].toUByte()) }
42*e1eccf28SAndroid Build Coastguard Worker val inputInFloat = FloatVector2dArray(inputValues, vectorSize, sizeX, sizeY)
43*e1eccf28SAndroid Build Coastguard Worker
44*e1eccf28SAndroid Build Coastguard Worker val scratch = horizontalBlur(inputInFloat, gaussian, radius, restriction)
45*e1eccf28SAndroid Build Coastguard Worker val outInFloat = verticalBlur(scratch, gaussian, radius, restriction)
46*e1eccf28SAndroid Build Coastguard Worker
47*e1eccf28SAndroid Build Coastguard Worker // Convert the results back to bytes.
48*e1eccf28SAndroid Build Coastguard Worker return ByteArray(outInFloat.values.size) { unitFloatClampedToUByte(outInFloat.values[it]).toByte() }
49*e1eccf28SAndroid Build Coastguard Worker }
50*e1eccf28SAndroid Build Coastguard Worker
51*e1eccf28SAndroid Build Coastguard Worker /**
52*e1eccf28SAndroid Build Coastguard Worker * Blurs along the horizontal direction using the specified gaussian weights.
53*e1eccf28SAndroid Build Coastguard Worker */
horizontalBlurnull54*e1eccf28SAndroid Build Coastguard Worker private fun horizontalBlur(
55*e1eccf28SAndroid Build Coastguard Worker input: FloatVector2dArray,
56*e1eccf28SAndroid Build Coastguard Worker gaussian: FloatArray,
57*e1eccf28SAndroid Build Coastguard Worker radius: Int,
58*e1eccf28SAndroid Build Coastguard Worker restriction: Range2d?
59*e1eccf28SAndroid Build Coastguard Worker ): FloatVector2dArray {
60*e1eccf28SAndroid Build Coastguard Worker var expandedRestriction: Range2d? = null
61*e1eccf28SAndroid Build Coastguard Worker if (restriction != null) {
62*e1eccf28SAndroid Build Coastguard Worker // Expand the restriction in the vertical direction so that the vertical pass
63*e1eccf28SAndroid Build Coastguard Worker // will have all the data it needs.
64*e1eccf28SAndroid Build Coastguard Worker expandedRestriction = Range2d(
65*e1eccf28SAndroid Build Coastguard Worker restriction.startX,
66*e1eccf28SAndroid Build Coastguard Worker restriction.endX,
67*e1eccf28SAndroid Build Coastguard Worker max(restriction.startY - radius, 0),
68*e1eccf28SAndroid Build Coastguard Worker min(restriction.endY + radius, input.sizeY)
69*e1eccf28SAndroid Build Coastguard Worker )
70*e1eccf28SAndroid Build Coastguard Worker }
71*e1eccf28SAndroid Build Coastguard Worker
72*e1eccf28SAndroid Build Coastguard Worker input.clipAccessToRange = true
73*e1eccf28SAndroid Build Coastguard Worker val out = input.createSameSized()
74*e1eccf28SAndroid Build Coastguard Worker out.forEach(expandedRestriction) { x, y ->
75*e1eccf28SAndroid Build Coastguard Worker for ((gaussianIndex, delta: Int) in (-radius..radius).withIndex()) {
76*e1eccf28SAndroid Build Coastguard Worker val v = input[x + delta, y] * gaussian[gaussianIndex]
77*e1eccf28SAndroid Build Coastguard Worker out[x, y] += v
78*e1eccf28SAndroid Build Coastguard Worker }
79*e1eccf28SAndroid Build Coastguard Worker }
80*e1eccf28SAndroid Build Coastguard Worker return out
81*e1eccf28SAndroid Build Coastguard Worker }
82*e1eccf28SAndroid Build Coastguard Worker
83*e1eccf28SAndroid Build Coastguard Worker /**
84*e1eccf28SAndroid Build Coastguard Worker * Blurs along the horizontal direction using the specified gaussian weights.
85*e1eccf28SAndroid Build Coastguard Worker */
verticalBlurnull86*e1eccf28SAndroid Build Coastguard Worker private fun verticalBlur(
87*e1eccf28SAndroid Build Coastguard Worker input: FloatVector2dArray,
88*e1eccf28SAndroid Build Coastguard Worker gaussian: FloatArray,
89*e1eccf28SAndroid Build Coastguard Worker radius: Int,
90*e1eccf28SAndroid Build Coastguard Worker restriction: Range2d?
91*e1eccf28SAndroid Build Coastguard Worker ): FloatVector2dArray {
92*e1eccf28SAndroid Build Coastguard Worker input.clipAccessToRange = true
93*e1eccf28SAndroid Build Coastguard Worker val out = input.createSameSized()
94*e1eccf28SAndroid Build Coastguard Worker out.forEach(restriction) { x, y ->
95*e1eccf28SAndroid Build Coastguard Worker for ((gaussianIndex, delta: Int) in (-radius..radius).withIndex()) {
96*e1eccf28SAndroid Build Coastguard Worker val v = input[x, y + delta] * gaussian[gaussianIndex]
97*e1eccf28SAndroid Build Coastguard Worker out[x, y] += v
98*e1eccf28SAndroid Build Coastguard Worker }
99*e1eccf28SAndroid Build Coastguard Worker }
100*e1eccf28SAndroid Build Coastguard Worker return out
101*e1eccf28SAndroid Build Coastguard Worker }
102*e1eccf28SAndroid Build Coastguard Worker
103*e1eccf28SAndroid Build Coastguard Worker /**
104*e1eccf28SAndroid Build Coastguard Worker * Builds an array of gaussian weights that will be used for doing the horizontal and vertical
105*e1eccf28SAndroid Build Coastguard Worker * blur.
106*e1eccf28SAndroid Build Coastguard Worker *
107*e1eccf28SAndroid Build Coastguard Worker * @return An array of (2 * radius + 1) floats.
108*e1eccf28SAndroid Build Coastguard Worker */
buildGaussiannull109*e1eccf28SAndroid Build Coastguard Worker private fun buildGaussian(radius: Int): FloatArray {
110*e1eccf28SAndroid Build Coastguard Worker val e: Float = kotlin.math.E.toFloat()
111*e1eccf28SAndroid Build Coastguard Worker val pi: Float = kotlin.math.PI.toFloat()
112*e1eccf28SAndroid Build Coastguard Worker val sigma: Float = 0.4f * radius.toFloat() + 0.6f
113*e1eccf28SAndroid Build Coastguard Worker val coefficient1: Float = 1.0f / (sqrt(2.0f * pi) * sigma)
114*e1eccf28SAndroid Build Coastguard Worker val coefficient2: Float = -1.0f / (2.0f * sigma * sigma)
115*e1eccf28SAndroid Build Coastguard Worker
116*e1eccf28SAndroid Build Coastguard Worker var sum = 0.0f
117*e1eccf28SAndroid Build Coastguard Worker val gaussian = FloatArray(radius * 2 + 1)
118*e1eccf28SAndroid Build Coastguard Worker for (r in -radius..radius) {
119*e1eccf28SAndroid Build Coastguard Worker val floatR: Float = r.toFloat()
120*e1eccf28SAndroid Build Coastguard Worker val v: Float = coefficient1 * e.pow(floatR * floatR * coefficient2)
121*e1eccf28SAndroid Build Coastguard Worker gaussian[r + radius] = v
122*e1eccf28SAndroid Build Coastguard Worker sum += v
123*e1eccf28SAndroid Build Coastguard Worker }
124*e1eccf28SAndroid Build Coastguard Worker
125*e1eccf28SAndroid Build Coastguard Worker // Normalize so that the sum of the weights equal 1f.
126*e1eccf28SAndroid Build Coastguard Worker val normalizeFactor: Float = 1.0f / sum
127*e1eccf28SAndroid Build Coastguard Worker for (r in -radius..radius) {
128*e1eccf28SAndroid Build Coastguard Worker gaussian[r + radius] *= normalizeFactor
129*e1eccf28SAndroid Build Coastguard Worker }
130*e1eccf28SAndroid Build Coastguard Worker return gaussian
131*e1eccf28SAndroid Build Coastguard Worker }
132