blob: 66c2a0559843cc54b0b318ff92035ea721855c95 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.example.testapp
import android.renderscript.toolkit.Range2d
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
/**
* Reference implementation of a Blur operation.
*/
@ExperimentalUnsignedTypes
fun referenceBlur(inputArray: ByteArray,
vectorSize: Int,
sizeX: Int,
sizeY: Int,
radius: Int = 5, restriction: Range2d?): ByteArray {
val maxRadius = 25
require (radius in 1..maxRadius) {
"RenderScriptToolkit blur. Radius should be between 1 and $maxRadius. $radius provided."
}
val gaussian = buildGaussian(radius)
// Convert input data to float so that the blurring goes faster.
val inputValues = FloatArray(inputArray.size) { byteToUnitFloat(inputArray[it].toUByte()) }
val inputInFloat = FloatVector2dArray(inputValues, vectorSize, sizeX, sizeY)
val scratch = horizontalBlur(inputInFloat, gaussian, radius, restriction)
val outInFloat = verticalBlur(scratch, gaussian, radius, restriction)
// Convert the results back to bytes.
return ByteArray(outInFloat.values.size) { unitFloatClampedToUByte(outInFloat.values[it]).toByte() }
}
/**
* Blurs along the horizontal direction using the specified gaussian weights.
*/
private fun horizontalBlur(
input: FloatVector2dArray,
gaussian: FloatArray,
radius: Int,
restriction: Range2d?
): FloatVector2dArray {
var expandedRestriction: Range2d? = null
if (restriction != null) {
// Expand the restriction in the vertical direction so that the vertical pass
// will have all the data it needs.
expandedRestriction = Range2d(
restriction.startX,
restriction.endX,
max(restriction.startY - radius, 0),
min(restriction.endY + radius, input.sizeY)
)
}
input.clipAccessToRange = true
val out = input.createSameSized()
out.forEach(expandedRestriction) { x, y ->
for ((gaussianIndex, delta: Int) in (-radius..radius).withIndex()) {
val v = input[x + delta, y] * gaussian[gaussianIndex]
out[x, y] += v
}
}
return out
}
/**
* Blurs along the horizontal direction using the specified gaussian weights.
*/
private fun verticalBlur(
input: FloatVector2dArray,
gaussian: FloatArray,
radius: Int,
restriction: Range2d?
): FloatVector2dArray {
input.clipAccessToRange = true
val out = input.createSameSized()
out.forEach(restriction) { x, y ->
for ((gaussianIndex, delta: Int) in (-radius..radius).withIndex()) {
val v = input[x, y + delta] * gaussian[gaussianIndex]
out[x, y] += v
}
}
return out
}
/**
* Builds an array of gaussian weights that will be used for doing the horizontal and vertical
* blur.
*
* @return An array of (2 * radius + 1) floats.
*/
private fun buildGaussian(radius: Int): FloatArray {
val e: Float = kotlin.math.E.toFloat()
val pi: Float = kotlin.math.PI.toFloat()
val sigma: Float = 0.4f * radius.toFloat() + 0.6f
val coefficient1: Float = 1.0f / (sqrt(2.0f * pi) * sigma)
val coefficient2: Float = -1.0f / (2.0f * sigma * sigma)
var sum = 0.0f
val gaussian = FloatArray(radius * 2 + 1)
for (r in -radius..radius) {
val floatR: Float = r.toFloat()
val v: Float = coefficient1 * e.pow(floatR * floatR * coefficient2)
gaussian[r + radius] = v
sum += v
}
// Normalize so that the sum of the weights equal 1f.
val normalizeFactor: Float = 1.0f / sum
for (r in -radius..radius) {
gaussian[r + radius] *= normalizeFactor
}
return gaussian
}