| /* |
| * 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 |
| } |