diff options
Diffstat (limited to 'test-app/src/main/java/com/google/android/renderscript_test')
22 files changed, 3906 insertions, 0 deletions
diff --git a/test-app/src/main/java/com/google/android/renderscript_test/AllTests.kt b/test-app/src/main/java/com/google/android/renderscript_test/AllTests.kt new file mode 100644 index 0000000..934456f --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/AllTests.kt @@ -0,0 +1,1244 @@ +/* + * 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.google.android.renderscript_test + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.renderscript.RenderScript +import com.google.android.renderscript.BlendingMode +import com.google.android.renderscript.LookupTable +import com.google.android.renderscript.Range2d +import com.google.android.renderscript.Rgba3dArray +import com.google.android.renderscript.Toolkit +import com.google.android.renderscript.YuvFormat +import kotlin.math.abs +import kotlin.math.min + +data class TestLayout( + val sizeX: Int, + val sizeY: Int, + val restriction: Range2d? +) + +// List of dimensions (sizeX, sizeY) to try when generating random data. +val commonLayoutsToTry = listOf( + // Small layouts to start with + TestLayout(3, 4, null), + TestLayout(3, 4, Range2d(0, 1, 0, 3)), + TestLayout(3, 4, Range2d(2, 3, 1, 4)), + // The size of most of the original RenderScript Intrinsic tests + TestLayout(160, 100, null), + /* Other tests, if you're patient: + TestLayout(10, 14, null), + TestLayout(10, 14, Range2d(2, 3, 8, 14)), + TestLayout(125, 227, Range2d(50, 125, 100, 227)), + TestLayout(800, 600, null), + // Weirdly shaped ones + TestLayout(1, 1, null), // A single item + estLayout(16000, 1, null), // A single item + TestLayout(1, 16000, null), // One large row + // A very large test + TestLayout(1024, 2048, null), + */ +) + +enum class Intrinsic { + BLEND, + BLUR, + COLOR_MATRIX, + CONVOLVE, + HISTOGRAM, + LUT, + LUT3D, + RESIZE, + YUV_TO_RGB, +} + + +class Tester(context: Context, private val validate: Boolean) { + private val renderscriptContext = RenderScript.create(context) + private val testImage1 = BitmapFactory.decodeResource(context.resources, R.drawable.img800x450a) + private val testImage2 = BitmapFactory.decodeResource(context.resources, R.drawable.img800x450b) + + init { + validateTestImage(testImage1) + validateTestImage(testImage2) + } + + /** + * Verify that the test images are in format that works for our tests. + */ + private fun validateTestImage(bitmap: Bitmap) { + require(bitmap.config == Bitmap.Config.ARGB_8888) + require(bitmap.rowBytes == bitmap.width * 4) { + "Can't handle bitmaps that have extra padding. " + + "${bitmap.rowBytes} != ${bitmap.width} * 4." } + require(bitmap.byteCount == bitmap.rowBytes * bitmap.height) + } + + fun destroy() { + renderscriptContext.destroy() + } + + // Test one single intrinsic. Return true on success and false on failure. + @ExperimentalUnsignedTypes + fun testOne(intrinsic: Intrinsic, timer: TimingTracker) = + when(intrinsic) { + Intrinsic.BLEND -> ::testBlend + Intrinsic.BLUR -> ::testBlur + Intrinsic.COLOR_MATRIX -> ::testColorMatrix + Intrinsic.CONVOLVE -> ::testConvolve + Intrinsic.HISTOGRAM -> ::testHistogram + Intrinsic.LUT -> ::testLut + Intrinsic.LUT3D -> ::testLut3d + Intrinsic.RESIZE -> ::testResize + Intrinsic.YUV_TO_RGB -> ::testYuvToRgb + }.let { test -> test(timer) } + + @ExperimentalUnsignedTypes + private fun testBlend(timer: TimingTracker): Boolean { + return BlendingMode.values().all { mode -> + testOneBitmapBlend(timer, testImage1, testImage2, mode, null) and + testOneBitmapBlend( + timer, testImage1, testImage2, mode, + Range2d(6, 23, 2, 4) + ) and + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + testOneRandomBlend(timer, sizeX, sizeY, mode, restriction) + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomBlend( + timer: TimingTracker, + sizeX: Int, + sizeY: Int, + mode: BlendingMode, + restriction: Range2d? + ): Boolean { + val sourceArray = randomByteArray(0x50521f0, sizeX, sizeY, 4) + val destArray = randomByteArray(0x2932147, sizeX, sizeY, 4) + // Make clones because these will be modified by the blend. + val intrinsicDestArray = destArray.clone() + val referenceDestArray = destArray.clone() + val toolkitDestArray = destArray.clone() + + timer.measure("IntrinsicBlend") { + intrinsicBlend( + renderscriptContext, mode, sourceArray, intrinsicDestArray, sizeX, sizeY, + restriction + ) + } + timer.measure("ToolkitBlend") { + Toolkit.blend(mode, sourceArray, toolkitDestArray, sizeX, sizeY, restriction) + } + if (!validate) return true + + timer.measure("ReferenceBlend") { + referenceBlend(mode, sourceArray, referenceDestArray, sizeX, sizeY, restriction) + } + + return validateSame( + "Blend_$mode", intrinsicDestArray, referenceDestArray, toolkitDestArray + ) { + println("blend $mode ($sizeX, $sizeY) $restriction") + logArray("Blend_$mode src", sourceArray, 48) + logArray("Blend_$mode dst", destArray, 48) + logArray("Blend_$mode reference out", referenceDestArray, 48) + logArray("Blend_$mode intrinsic out", intrinsicDestArray, 48) + logArray("Blend_$mode toolkit out", toolkitDestArray, 48) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapBlend( + timer: TimingTracker, + sourceBitmap: Bitmap, + destBitmap: Bitmap, + mode: BlendingMode, + restriction: Range2d? + ): Boolean { + // Make clones because these will be modified by the blend. + val intrinsicDestBitmap = duplicateBitmap(destBitmap) + val toolkitDestBitmap = duplicateBitmap(destBitmap) + val referenceDestBitmap = duplicateBitmap(destBitmap) + + timer.measure("IntrinsicBlend") { + intrinsicBlend( + renderscriptContext, mode, sourceBitmap, intrinsicDestBitmap, restriction + ) + } + timer.measure("ToolkitBlend") { + Toolkit.blend(mode, sourceBitmap, toolkitDestBitmap, restriction) + } + if (!validate) return true + + val referenceDestArray = getBitmapBytes(referenceDestBitmap) + timer.measure("ReferenceBlend") { + referenceBlend( + mode, getBitmapBytes(sourceBitmap), referenceDestArray, sourceBitmap.width, + sourceBitmap.height, restriction + ) + } + + val intrinsicDestArray = getBitmapBytes(intrinsicDestBitmap) + val toolkitDestArray = getBitmapBytes(toolkitDestBitmap) + return validateSame( + "BlendBitmap_$mode", intrinsicDestArray, referenceDestArray, toolkitDestArray + ) { + println("BlendBitmap $mode $restriction") + //logArray("BlendBitmap_$mode src", sourceArray, 48) + //logArray("BlendBitmap_$mode dst", destArray, 48) + logArray("BlendBitmap_$mode reference out", referenceDestArray, 48) + logArray("BlendBitmap_$mode intrinsic out", intrinsicDestArray, 48) + logArray("BlendBitmap_$mode toolkit out", toolkitDestArray, 48) + } + } + + @ExperimentalUnsignedTypes + private fun testBlur(timer: TimingTracker): Boolean { + return arrayOf(1, 3, 8, 25).all { radius -> + testOneBitmapBlur(timer, testImage1, radius, null) and + testOneBitmapBlur(timer, testImage1, radius, Range2d(6, 23, 2, 4)) and + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + arrayOf(1, 4).all { vectorSize -> + testOneRandomBlur(timer, vectorSize, sizeX, sizeY, radius, restriction) + } + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomBlur( + timer: TimingTracker, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + radius: Int, + restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, vectorSize) + val intrinsicOutArray = timer.measure("IntrinsicBlur") { + intrinsicBlur( + renderscriptContext, inputArray, vectorSize, sizeX, sizeY, radius, restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitBlur") { + Toolkit.blur(inputArray, vectorSize, sizeX, sizeY, radius, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceBlur") { + referenceBlur(inputArray, vectorSize, sizeX, sizeY, radius, restriction) + } + return validateSame("blur", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("blur $vectorSize ($sizeX, $sizeY) radius = $radius $restriction") + logArray("blur input ", inputArray) + logArray("blur reference out", referenceOutArray) + logArray("blur intrinsic out", intrinsicOutArray) + logArray("blur toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapBlur( + timer: TimingTracker, + bitmap: Bitmap, + radius: Int, + restriction: Range2d? + ): Boolean { + val intrinsicOutArray = timer.measure("IntrinsicBlur") { + intrinsicBlur(renderscriptContext, bitmap, radius, restriction) + } + + val toolkitOutBitmap = timer.measure("ToolkitBlur") { + Toolkit.blur(bitmap, radius, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceBlur") { + referenceBlur( + getBitmapBytes(bitmap), + vectorSizeOfBitmap(bitmap), + bitmap.width, + bitmap.height, + radius, + restriction + ) + } + + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame("blur", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("BlurBitmap ${bitmap.config} $radius $restriction") + logArray("blur reference out", referenceOutArray) + logArray("blur intrinsic out", intrinsicOutArray) + logArray("blur toolkit out", toolkitOutArray) + } + } + + enum class ColorMatrixConversionType { + RGB_TO_YUV, + YUV_TO_RGB, + GREYSCALE, + RANDOM + } + + @ExperimentalUnsignedTypes + private fun testColorMatrix(timer: TimingTracker): Boolean { + return ColorMatrixConversionType.values().all { conversion -> + testOneBitmapColorMatrix(timer, testImage1, conversion, null) and + testOneBitmapColorMatrix( + timer, + testImage1, + conversion, + Range2d(6, 23, 2, 4) + ) and + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + (1..4).all { inputVectorSize -> + (1..4).all { outputVectorSize -> + testOneRandomColorMatrix( + timer, + inputVectorSize, + sizeX, + sizeY, + outputVectorSize, + conversion, + restriction + ) + } + } + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomColorMatrix( + timer: TimingTracker, + inputVectorSize: Int, + sizeX: Int, + sizeY: Int, + outputVectorSize: Int, + conversion: ColorMatrixConversionType, + restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, paddedSize(inputVectorSize)) + val addVector = randomFloatArray(0x243238, 4, 1, 1, 0.3f) + val matrix = when (conversion) { + ColorMatrixConversionType.RGB_TO_YUV -> Toolkit.rgbToYuvMatrix + ColorMatrixConversionType.YUV_TO_RGB -> Toolkit.yuvToRgbMatrix + ColorMatrixConversionType.GREYSCALE -> Toolkit.greyScaleColorMatrix + ColorMatrixConversionType.RANDOM -> randomFloatArray(0x234348, 4, 4, 1) + } + + val intrinsicOutArray = timer.measure("IntrinsicColorMatrix") { + intrinsicColorMatrix( + renderscriptContext, + conversion, + inputArray, + inputVectorSize, + sizeX, + sizeY, + outputVectorSize, + matrix, + addVector, + restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitColorMatrix") { + Toolkit.colorMatrix( + inputArray, + inputVectorSize, + sizeX, + sizeY, + outputVectorSize, + matrix, + addVector, + restriction + ) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceColorMatrix") { + referenceColorMatrix( + inputArray, inputVectorSize, sizeX, sizeY, outputVectorSize, matrix, addVector, + restriction + ) + } + + return validateSame("colorMatrix", intrinsicOutArray, referenceOutArray, toolkitOutArray, + outputVectorSize == 3) { + println("colorMatrix ($sizeX, $sizeY) $inputVectorSize->$outputVectorSize $restriction") + logArray("colorMatrix matrix ", matrix, 16) + logArray("colorMatrix addVector", addVector, 4) + logArray("colorMatrix in ", inputArray) + logArray("colorMatrix reference out", referenceOutArray, 300) + logArray("colorMatrix intrinsic out", intrinsicOutArray, 300) + logArray("colorMatrix toolkit out", toolkitOutArray, 300) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapColorMatrix( + timer: TimingTracker, + bitmap: Bitmap, + conversion: ColorMatrixConversionType, + restriction: Range2d? + ): Boolean { + val addVector = randomFloatArray(0x243238, 4, 1, 1, 0.3f) + val matrix = when (conversion) { + ColorMatrixConversionType.RGB_TO_YUV -> Toolkit.rgbToYuvMatrix + ColorMatrixConversionType.YUV_TO_RGB -> Toolkit.yuvToRgbMatrix + ColorMatrixConversionType.GREYSCALE -> Toolkit.greyScaleColorMatrix + ColorMatrixConversionType.RANDOM -> randomFloatArray(0x234348, 4, 4, 1) + } + + val intrinsicOutArray = timer.measure("IntrinsicColorMatrix") { + intrinsicColorMatrix( + renderscriptContext, conversion, bitmap, matrix, addVector, restriction + ) + } + val toolkitOutBitmap = timer.measure("ToolkitColorMatrix") { + Toolkit.colorMatrix(bitmap, matrix, addVector, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceColorMatrix") { + referenceColorMatrix( + getBitmapBytes(bitmap), vectorSizeOfBitmap(bitmap), bitmap.width, bitmap.height, + vectorSizeOfBitmap(bitmap), matrix, addVector, restriction + ) + } + + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame("ColorMatrix", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("colorMatrixBitmap $restriction") + logArray("colorMatrixBitmap matrix ", matrix, 16) + logArray("colorMatrixBitmap addVector", addVector, 4) + logArray("colorMatrixBitmap reference out", referenceOutArray) + logArray("colorMatrixBitmap intrinsic out", intrinsicOutArray) + logArray("colorMatrixBitmap toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testConvolve(timer: TimingTracker): Boolean { + val coefficientsToTry = listOf( + randomFloatArray(0x2937021, 3, 3, 1, 0.1f), + randomFloatArray(0x2937021, 5, 5, 1, 0.05f) + ) + return coefficientsToTry.all { coefficients -> + testOneBitmapConvolve(timer, testImage1, coefficients, null) and + testOneBitmapConvolve(timer, testImage1, coefficients, Range2d(6, 23, 2, 4)) and + + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + (1..4).all { vectorSize -> + testOneRandomConvolve( + timer, + vectorSize, + sizeX, + sizeY, + coefficients, + restriction + ) + } + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomConvolve( + timer: TimingTracker, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + coefficients: FloatArray, + restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, paddedSize(vectorSize)) + + val intrinsicOutArray = timer.measure("IntrinsicConvolve") { + intrinsicConvolve( + renderscriptContext, inputArray, vectorSize, sizeX, sizeY, coefficients, restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitConvolve") { + Toolkit.convolve(inputArray, vectorSize, sizeX, sizeY, coefficients, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceConvolve") { + referenceConvolve(inputArray, vectorSize, sizeX, sizeY, coefficients, restriction) + } + + val task = if (coefficients.size == 9) "convolve3x3 $vectorSize" else "convolve5x5 $vectorSize" + return validateSame(task, intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("Convolve $vectorSize ($sizeX, $sizeY) $restriction") + logArray("Convolve coefficients", coefficients, 25) + logArray("Convolve in ", inputArray) + logArray("Convolve reference out", referenceOutArray) + logArray("Convolve intrinsic out", intrinsicOutArray) + logArray("Convolve toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapConvolve( + timer: TimingTracker, + bitmap: Bitmap, + coefficients: FloatArray, + restriction: Range2d? + ): Boolean { + val intrinsicOutArray = timer.measure("IntrinsicConvolve") { + intrinsicConvolve(renderscriptContext, bitmap, coefficients, restriction) + } + val toolkitOutBitmap = timer.measure("ToolkitConvolve") { + Toolkit.convolve(bitmap, coefficients, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceConvolve") { + referenceConvolve( + getBitmapBytes(bitmap), vectorSizeOfBitmap(bitmap), bitmap.width, bitmap.height, + coefficients, restriction + ) + } + + val task = if (coefficients.size == 9) "convolve3x3" else "convolve5x5" + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame(task, intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("ConvolveBitmap $restriction") + logArray("ConvolveBitmap coefficients", coefficients, 25) + //logArray("ConvolveBitmap in ", inputArray) + logArray("ConvolveBitmap reference out", referenceOutArray) + logArray("ConvolveBitmap intrinsic out", intrinsicOutArray) + logArray("ConvolveBitmap toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testHistogram(timer: TimingTracker): Boolean { + val coefficients = floatArrayOf(0.1f, 0.3f, 0.5f, 0.05f) + return testOneBitmapHistogram(timer, testImage1, null) and + testOneBitmapHistogram(timer, testImage1, Range2d(6, 23, 2, 4)) and + testOneBitmapHistogramDot(timer, testImage1, null, null) and + testOneBitmapHistogramDot(timer, testImage1, coefficients, null) and + testOneBitmapHistogramDot(timer, testImage1, coefficients, Range2d(6, 23, 2, 4)) and + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + (1..4).all { vectorSize -> + testOneRandomHistogram(timer, vectorSize, sizeX, sizeY, restriction) && + testOneRandomHistogramDot( + timer, + vectorSize, + sizeX, + sizeY, + null, + restriction + ) && + testOneRandomHistogramDot( + timer, + vectorSize, + sizeX, + sizeY, + coefficients.sliceArray(0 until vectorSize), + restriction + ) + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomHistogram( + timer: TimingTracker, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, paddedSize(vectorSize)) + + val intrinsicOutput = timer.measure("IntrinsicHistogram") { + intrinsicHistogram( + renderscriptContext, inputArray, vectorSize, sizeX, sizeY, restriction + ) + } + val toolkitOutput = timer.measure("ToolkitHistogram") { + Toolkit.histogram(inputArray, vectorSize, sizeX, sizeY, restriction) + } + if (!validate) return true + + val referenceOutput = timer.measure("ReferenceHistogram") { + referenceHistogram( + inputArray, vectorSize, sizeX, sizeY, restriction + ) + } + + return validateSame("histogram", intrinsicOutput, referenceOutput, toolkitOutput, 0) { + println("histogram $vectorSize ($sizeX, $sizeY) $restriction") + logArray("histogram in ", inputArray, 200) + logArray("histogram reference out", referenceOutput, 200) + logArray("histogram intrinsic out", intrinsicOutput, 200) + logArray("histogram toolkit out", toolkitOutput, 200) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapHistogram( + timer: TimingTracker, + bitmap: Bitmap, + restriction: Range2d? + ): Boolean { + val intrinsicOutput = timer.measure("IntrinsicHistogram") { + intrinsicHistogram(renderscriptContext, bitmap, restriction) + } + val toolkitOutput = timer.measure("ToolkitHistogram") { + Toolkit.histogram(bitmap, restriction) + } + if (!validate) return true + + val referenceOutput = timer.measure("ReferenceHistogram") { + referenceHistogram( + getBitmapBytes(bitmap), vectorSizeOfBitmap(bitmap), bitmap.width, bitmap.height, + restriction + ) + } + + return validateSame("histogram", intrinsicOutput, referenceOutput, toolkitOutput, 0) { + println("HistogramBitmap $restriction") + logArray("HistogramBitmap reference out", referenceOutput) + logArray("HistogramBitmap intrinsic out", intrinsicOutput) + logArray("HistogramBitmap toolkit out", toolkitOutput) + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomHistogramDot( + timer: TimingTracker, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + coefficients: FloatArray?, restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, paddedSize(vectorSize)) + + val intrinsicOutArray = timer.measure("IntrinsicHistogramDot") { + intrinsicHistogramDot( + renderscriptContext, inputArray, vectorSize, sizeX, sizeY, coefficients, restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitHistogramDot") { + Toolkit.histogramDot( + inputArray, vectorSize, sizeX, sizeY, coefficients, restriction + ) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceHistogramDot") { + referenceHistogramDot(inputArray, vectorSize, sizeX, sizeY, coefficients, restriction) + } + + return validateSame("histogramDot", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("histogramDot $vectorSize ($sizeX, $sizeY) $restriction") + logArray("histogramDot coefficients ", coefficients) + logArray("histogramDot in ", inputArray) + logArray("histogramDot reference out", referenceOutArray, 256) + logArray("histogramDot intrinsic out", intrinsicOutArray, 256) + logArray("histogramDot toolkit out", toolkitOutArray, 256) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapHistogramDot( + timer: TimingTracker, + bitmap: Bitmap, + coefficients: FloatArray?, + restriction: Range2d? + ): Boolean { + val intrinsicOutArray = timer.measure("IntrinsicHistogramDot") { + intrinsicHistogramDot(renderscriptContext, bitmap, coefficients, restriction) + } + val toolkitOutArray = timer.measure("ToolkitHistogramDot") { + Toolkit.histogramDot(bitmap, coefficients, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceHistogramDot") { + referenceHistogramDot( + getBitmapBytes(bitmap), + vectorSizeOfBitmap(bitmap), + bitmap.width, + bitmap.height, + coefficients, + restriction + ) + } + + return validateSame( + "HistogramDotBitmap", + intrinsicOutArray, + referenceOutArray, + toolkitOutArray + ) { + println("HistogramDotBitmap $restriction") + logArray("HistogramDotBitmap coefficients ", coefficients) + //logArray("HistogramDotBitmap in ", inputArray) + logArray("HistogramDotBitmap reference out", referenceOutArray, 256) + logArray("HistogramDotBitmap intrinsic out", intrinsicOutArray, 256) + logArray("HistogramDotBitmap toolkit out", toolkitOutArray, 256) + } + } + + @ExperimentalUnsignedTypes + private fun testLut(timer: TimingTracker): Boolean { + return testOneBitmapLut(timer, testImage1, null) and + testOneBitmapLut(timer, testImage1, Range2d(6, 23, 2, 4)) and + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + testOneRandomLut(timer, sizeX, sizeY, restriction) + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomLut( + timer: TimingTracker, + sizeX: Int, + sizeY: Int, + restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, 4) + val newRed = randomByteArray(0x32425, 256, 1, 1) + val newGreen = randomByteArray(0x1F3225, 256, 1, 1) + val newBlue = randomByteArray(0x32D4F27, 256, 1, 1) + val newAlpha = randomByteArray(0x3A20001, 256, 1, 1) + val table = LookupTable() + table.red = newRed + table.blue = newBlue + table.green = newGreen + table.alpha = newAlpha + + val intrinsicOutArray = timer.measure("IntrinsicLUT") { + intrinsicLut( + renderscriptContext, inputArray, sizeX, sizeY, newRed, newGreen, newBlue, newAlpha, + restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitLUT") { + Toolkit.lut(inputArray, sizeX, sizeY, table, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceLUT") { + referenceLut(inputArray, sizeX, sizeY, table, restriction) + } + + return validateSame("LUT", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("lut ($sizeX, $sizeY) $restriction") + logArray("LUT red ", newRed, 256) + logArray("LUT green", newGreen, 256) + logArray("LUT blue ", newBlue, 256) + logArray("LUT alpha", newAlpha, 256) + logArray("LUT in ", inputArray) + logArray("LUT reference out", referenceOutArray) + logArray("LUT intrinsic out", intrinsicOutArray) + logArray("LUT toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapLut( + timer: TimingTracker, + bitmap: Bitmap, + restriction: Range2d? + ): Boolean { + val newRed = randomByteArray(0x32425, 256, 1, 1) + val newGreen = randomByteArray(0x1F3225, 256, 1, 1) + val newBlue = randomByteArray(0x32D4F27, 256, 1, 1) + val newAlpha = randomByteArray(0x3A20001, 256, 1, 1) + val table = LookupTable() + table.red = newRed + table.blue = newBlue + table.green = newGreen + table.alpha = newAlpha + + val intrinsicOutArray = timer.measure("IntrinsicLUT") { + intrinsicLut( + renderscriptContext, bitmap, newRed, newGreen, newBlue, newAlpha, restriction + ) + } + val toolkitOutBitmap = timer.measure("ToolkitLUT") { + Toolkit.lut(bitmap, table, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceLUT") { + referenceLut( + getBitmapBytes(bitmap), + bitmap.width, + bitmap.height, + table, + restriction + ) + } + + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame("LutBitmap", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("LutBitmap $restriction") + logArray("LutBitmap red ", newRed, 256) + logArray("LutBitmap green", newGreen, 256) + logArray("LutBitmap blue ", newBlue, 256) + logArray("LutBitmap alpha", newAlpha, 256) + //logArray("LutBitmap in ", inputArray, 80) + logArray("LutBitmap reference out", referenceOutArray) + logArray("LutBitmap intrinsic out", intrinsicOutArray) + logArray("LutBitmap toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testLut3d(timer: TimingTracker): Boolean { + val cubeSizesToTry = listOf( + Dimension(2, 2, 2), + Dimension(32, 32, 16), + Dimension(256, 256, 256) + ) + return cubeSizesToTry.all { cubeSize -> + val identityCube = identityCube(cubeSize) + val randomCube = randomCube(0x23424, cubeSize) + testOneBitmapLut3d(timer, testImage1, cubeSize, identityCube, 1, null) and + testOneBitmapLut3d(timer, testImage2, cubeSize, randomCube, 3, null) and + testOneBitmapLut3d(timer, testImage2, cubeSize, randomCube, 3, Range2d(6, 23, 2, 4)) and + commonLayoutsToTry.all { (sizeX, sizeY, restriction) -> + testOneRandomLut3d(timer, sizeX, sizeY, cubeSize, identityCube, 1, restriction) && + testOneRandomLut3d( + timer, + sizeX, + sizeY, + cubeSize, + randomCube, + 3, + restriction + ) + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomLut3d( + timer: TimingTracker, + sizeX: Int, + sizeY: Int, + cubeSize: Dimension, + cubeArray: ByteArray, + allowedIntError: Int, restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, sizeX, sizeY, 4) + + val intrinsicOutArray = timer.measure("IntrinsicLut3d") { + intrinsicLut3d( + renderscriptContext, inputArray, sizeX, sizeY, cubeArray, cubeSize, restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitLut3d") { + val toolkitCube = Rgba3dArray(cubeArray, cubeSize.sizeX, cubeSize.sizeY, cubeSize.sizeZ) + Toolkit.lut3d(inputArray, sizeX, sizeY, toolkitCube, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceLut3d") { + val cube = Rgba3dArray(cubeArray, cubeSize.sizeX, cubeSize.sizeY, cubeSize.sizeZ) + referenceLut3d(inputArray, sizeX, sizeY, cube, restriction) + } + + return validateSame( + "lut3d", + intrinsicOutArray, + referenceOutArray, + toolkitOutArray, + false, + allowedIntError + ) { + println("lut3d ($sizeX, $sizeY) $restriction") + logArray("lut3d cube", cubeArray, 256) + logArray("lut3d in ", inputArray, 64) + logArray("lut3d reference out", referenceOutArray, 64) + logArray("lut3d intrinsic out", intrinsicOutArray, 64) + logArray("lut3d toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapLut3d( + timer: TimingTracker, + bitmap: Bitmap, + cubeSize: Dimension, + cubeArray: ByteArray, + allowedIntError: Int, restriction: Range2d? + ): Boolean { + val intrinsicOutArray = timer.measure("IntrinsicLut3d") { + intrinsicLut3d(renderscriptContext, bitmap, cubeArray, cubeSize, restriction) + } + val toolkitOutBitmap = timer.measure("ToolkitLut3d") { + val toolkitCube = Rgba3dArray(cubeArray, cubeSize.sizeX, cubeSize.sizeY, cubeSize.sizeZ) + Toolkit.lut3d(bitmap, toolkitCube, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceLut3d") { + val cube = Rgba3dArray(cubeArray, cubeSize.sizeX, cubeSize.sizeY, cubeSize.sizeZ) + referenceLut3d(getBitmapBytes(bitmap), bitmap.width, bitmap.height, cube, restriction) + } + + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame( + "Lut3dBitmap", + intrinsicOutArray, + referenceOutArray, + toolkitOutArray, + false, + allowedIntError + ) { + println("Lut3dBitmap $restriction") + logArray("Lut3dBitmap cube", cubeArray, 256) + //logArray("Lut3dBitmap in ", inputArray, 64) + logArray("Lut3dBitmap reference out", referenceOutArray, 64) + logArray("Lut3dBitmap intrinsic out", intrinsicOutArray, 64) + logArray("Lut3dBitmap toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testResize(timer: TimingTracker): Boolean { + val factorsToTry = listOf( + Pair(1f, 1f), + Pair(0.5f, 1f), + Pair(2f, 2f), + Pair(0.5f, 2f), + Pair(2f, 0.5f), + // The RenderScript Intrinsic tests used the above factors. It's tempting to use + // less regular ones like Pair(6.37f, 0.17f) however this creates small offset + // errors between the result provided by the C++ code and the SIMD code. This is + // due to the SIMD code using a scaled integer to increment going from one pixel to the + // next, while the C++ code uses float operations. + ) + val layoutsToTry = listOf( + TestLayout(37, 47, null), + TestLayout(60, 10, null), + TestLayout(6, 4, Range2d(1, 3, 0, 2)), + TestLayout(10, 14, Range2d(2, 3, 3, 7)), + ) + + return factorsToTry.all { (scaleX, scaleY) -> + // Do one resize that's greater than 4x, as that's used in the code but don't do it + // for everything, as some images will get very large + testOneRandomResize(timer, 1, 25, 30, 6f, 6f, null) and + testOneBitmapResize(timer, testImage1, scaleX, scaleY, null) and + testOneBitmapResize(timer, testImage1, scaleX, scaleY, Range2d(6, 23, 2, 4)) and + layoutsToTry.all { (sizeX, sizeY, restriction) -> + (1..4).all { vectorSize -> + testOneRandomResize( + timer, + vectorSize, + sizeX, + sizeY, + scaleX, + scaleY, + restriction + ) + } + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomResize( + timer: TimingTracker, + vectorSize: Int, + inSizeX: Int, + inSizeY: Int, + scaleX: Float, + scaleY: Float, + restriction: Range2d? + ): Boolean { + val inputArray = randomByteArray(0x50521f0, inSizeX, inSizeY, paddedSize(vectorSize)) + val outSizeX = (inSizeX * scaleX).toInt() + val outSizeY = (inSizeY * scaleY).toInt() + + val intrinsicOutArray = timer.measure("IntrinsicResize") { + intrinsicResize( + renderscriptContext, inputArray, vectorSize, inSizeX, inSizeY, outSizeX, outSizeY, + restriction + ) + } + val toolkitOutArray = timer.measure("ToolkitResize") { + Toolkit.resize( + inputArray, vectorSize, inSizeX, inSizeY, outSizeX, outSizeY, restriction + ) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceResize") { + referenceResize( + inputArray, vectorSize, inSizeX, inSizeY, outSizeX, outSizeY, restriction + ) + } + + return validateSame("resize", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("resize $vectorSize ($inSizeX, $inSizeY) by ($scaleX, $scaleY) to ($outSizeX, $outSizeY), $restriction") + logArray("resize in ", inputArray) + logArray("resize reference out", referenceOutArray) + logArray("resize intrinsic out", intrinsicOutArray) + logArray("resize toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testOneBitmapResize( + timer: TimingTracker, + bitmap: Bitmap, + scaleX: Float, + scaleY: Float, + restriction: Range2d? + ): Boolean { + // println("Doing resize $inSizeX x $inSizeY x $vectorSize, $scaleX x $scaleY, $restriction") + val outSizeX = (bitmap.width * scaleX).toInt() + val outSizeY = (bitmap.height * scaleY).toInt() + + val intrinsicOutArray = timer.measure("IntrinsicResize") { + intrinsicResize(renderscriptContext, bitmap, outSizeX, outSizeY, restriction) + } + val toolkitOutBitmap = timer.measure("ToolkitResize") { + Toolkit.resize(bitmap, outSizeX, outSizeY, restriction) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceResize") { + referenceResize( + getBitmapBytes(bitmap), + vectorSizeOfBitmap(bitmap), + bitmap.width, + bitmap.height, + outSizeX, + outSizeY, + restriction + ) + } + + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame("ResizeBitmap", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("ResizeBitmap by ($scaleX, $scaleY) to ($outSizeX, $outSizeY), $restriction") + //logArray("ResizeBitmap in ", inputArray, 100) + logArray("ResizeBitmap reference out", referenceOutArray) + logArray("ResizeBitmap intrinsic out", intrinsicOutArray) + logArray("ResizeBitmap toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testYuvToRgb(timer: TimingTracker): Boolean { + val layoutsToTry = listOf( + // Don't try sizeX with odd values. That's not allowed by definition of some + // of the video formats. + TestLayout(10, 14, null), + TestLayout(64, 40, null), + TestLayout(96, 94, null), + ) + return layoutsToTry.all { (sizeX, sizeY, _) -> + YuvFormat.values().all { format -> + testOneRandomYuvToRgb(timer, sizeX, sizeY, format) and + testOneRandomYuvToRgbBitmap(timer, sizeX, sizeY, format) + } + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomYuvToRgb( + timer: TimingTracker, + sizeX: Int, + sizeY: Int, + format: YuvFormat + ): Boolean { + // The RenderScript Intrinsic does not handle this combination correctly. + if (format == YuvFormat.YV12 && sizeX % 32 != 0) { + return true + } + val inputArray = randomYuvArray(0x50521f0, sizeX, sizeY, format) + + val intrinsicOutArray = timer.measure("IntrinsicYuvToRgb") { + intrinsicYuvToRgb(renderscriptContext, inputArray, sizeX, sizeY, format) + } + val toolkitOutArray = timer.measure("ToolkitYuvToRgb") { + Toolkit.yuvToRgb(inputArray, sizeX, sizeY, format) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceYuvToRgb") { + referenceYuvToRgb(inputArray, sizeX, sizeY, format) + } + + return validateSame("yuvToRgb", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("yuvToRgb ($sizeX, $sizeY) $format") + logArray("yuvToRgb in ", inputArray) + logArray("yuvToRgb reference out", referenceOutArray) + logArray("yuvToRgb intrinsic out", intrinsicOutArray) + logArray("yuvToRgb toolkit out", toolkitOutArray) + } + } + + @ExperimentalUnsignedTypes + private fun testOneRandomYuvToRgbBitmap( + timer: TimingTracker, + sizeX: Int, + sizeY: Int, + format: YuvFormat + ): Boolean { + // The RenderScript Intrinsic does not handle this combination correctly. + if (format == YuvFormat.YV12 && sizeX % 32 != 0) { + return true + } + val inputArray = randomYuvArray(0x50521f0, sizeX, sizeY, format) + + val intrinsicOutArray = timer.measure("IntrinsicYuvToRgb") { + intrinsicYuvToRgb(renderscriptContext, inputArray, sizeX, sizeY, format) + } + val toolkitOutBitmap = timer.measure("ToolkitYuvToRgb") { + Toolkit.yuvToRgbBitmap(inputArray, sizeX, sizeY, format) + } + if (!validate) return true + + val referenceOutArray = timer.measure("ReferenceYuvToRgb") { + referenceYuvToRgb(inputArray, sizeX, sizeY, format) + } + + val toolkitOutArray = getBitmapBytes(toolkitOutBitmap) + return validateSame("yuvToRgb", intrinsicOutArray, referenceOutArray, toolkitOutArray) { + println("yuvToRgb ($sizeX, $sizeY) $format") + logArray("yuvToRgb in ", inputArray) + logArray("yuvToRgb reference out", referenceOutArray) + logArray("yuvToRgb intrinsic out", intrinsicOutArray) + logArray("yuvToRgb toolkit out", toolkitOutArray) + } + } + + /** + * Verifies that the arrays returned by the Intrinsic, the reference code, and the Toolkit + * are all within a margin of error. + * + * RenderScript Intrinsic test (rc/android/cts/rscpp/RSCppTest.java) used 3 for ints. + * For floats, rc/android/cts/rscpp/verify.rscript uses 0.0001f. + */ + @ExperimentalUnsignedTypes + private fun validateSame( + task: String, + intrinsic: ByteArray, + reference: ByteArray, + toolkit: ByteArray, + skipFourth: Boolean = false, + allowedIntDelta: Int = 3, + errorLogging: () -> Unit + ): Boolean { + val success = validateAgainstReference( + task, reference, "Intrinsic", intrinsic, skipFourth, allowedIntDelta + ) and validateAgainstReference( + task, reference, "Toolkit", toolkit, skipFourth, allowedIntDelta + ) + if (!success) { + println("$task FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!") + errorLogging() + } + return success + } + + private fun validateSame( + task: String, + intrinsic: IntArray, + reference: IntArray, + toolkit: IntArray, + allowedIntDelta: Int = 3, + errorLogging: () -> Unit + ): Boolean { + val success = validateAgainstReference( + task, reference, "Intrinsic", intrinsic, allowedIntDelta + ) and validateAgainstReference( + task, reference, "Toolkit", toolkit, allowedIntDelta + ) + if (!success) { + println("$task FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!FAIL!") + errorLogging() + } + return success + } + + @ExperimentalUnsignedTypes + private fun validateAgainstReference( + task: String, + in1: ByteArray, + name2: String, + in2: ByteArray, + skipFourth: Boolean, + allowedIntDelta: Int + ): Boolean { + if (in1.size != in2.size) { + println("$task. Sizes don't match: Reference ${in1.size}, $name2 ${in2.size}") + return false + } + var same = true + val maxDetails = 80 + val diffs = CharArray(min(in1.size, maxDetails)) {'.'} + for (i in in1.indices) { + if (skipFourth && i % 4 == 3) { + continue + } + val delta = abs(in1[i].toUByte().toInt() - in2[i].toUByte().toInt()) + if (delta > allowedIntDelta) { + if (same) { + println( + "$task. At $i, Reference is ${in1[i].toUByte()}, $name2 is ${in2[i].toUByte()}" + ) + } + if (i < maxDetails) diffs[i] = 'X' + same = false + } + } + if (!same) { + for (i in 0 until (min(in1.size, maxDetails) / 4)) print("%-3d|".format(i)) + println() + println(diffs) + } + return same + } + + private fun validateAgainstReference( + task: String, + in1: IntArray, + name2: String, + in2: IntArray, + allowedIntDelta: Int + ): Boolean { + if (in1.size != in2.size) { + println("$task. Sizes don't match: Reference ${in1.size}, $name2 ${in2.size}") + return false + } + for (i in in1.indices) { + val delta = abs(in1[i] - in2[i]) + if (delta > allowedIntDelta) { + println("$task. At $i, Reference is ${in1[i]}, $name2 is ${in2[i]}") + return false + } + } + return true + } +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/BufferUtils.kt b/test-app/src/main/java/com/google/android/renderscript_test/BufferUtils.kt new file mode 100644 index 0000000..51ece8e --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/BufferUtils.kt @@ -0,0 +1,508 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.renderscript.Element +import android.renderscript.RenderScript +import com.google.android.renderscript.Range2d +import com.google.android.renderscript.Rgba3dArray +import com.google.android.renderscript.YuvFormat +import java.nio.ByteBuffer +import java.util.Random +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min + +/** + * A vector of 4 integers. + */ +class Int4( + var x: Int = 0, + var y: Int = 0, + var z: Int = 0, + var w: Int = 0 +) { + operator fun plus(other: Int4) = Int4(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun plus(n: Int) = Int4(x + n, y + n, z + n, w + n) + + operator fun minus(other: Int4) = Int4(x - other.x, y - other.y, z - other.z, w - other.w) + operator fun minus(n: Int) = Int4(x - n, y - n, z - n, w - n) + + operator fun times(other: Int4) = Int4(x * other.x, y * other.y, z * other.z, w * other.w) + operator fun times(n: Int) = Int4(x * n, y * n, z * n, w * n) + + fun toFloat4() = Float4(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat()) +} + +fun min(a: Int4, b: Int4) = Int4(min(a.x, b.x), min(a.y, b.y), min(a.z, b.z), min(a.w, b.w)) + +/** + * A vector of 4 floats. + */ +data class Float4( + var x: Float = 0f, + var y: Float = 0f, + var z: Float = 0f, + var w: Float = 0f +) { + operator fun plus(other: Float4) = Float4(x + other.x, y + other.y, z + other.z, w + other.w) + operator fun plus(f: Float) = Float4(x + f, y + f, z + f, w + f) + + operator fun minus(other: Float4) = Float4(x - other.x, y - other.y, z - other.z, w - other.w) + operator fun minus(f: Float) = Float4(x - f, y - f, z - f, w - f) + + operator fun times(other: Float4) = Float4(x * other.x, y * other.y, z * other.z, w * other.w) + operator fun times(f: Float) = Float4(x * f, y * f, z * f, w * f) + + operator fun div(other: Float4) = Float4(x / other.x, y / other.y, z / other.z, w / other.w) + operator fun div(f: Float) = Float4(x / f, y / f, z / f, w / f) + + fun intFloor() = Int4(floor(x).toInt(), floor(y).toInt(), floor(z).toInt(), floor(w).toInt()) +} + +/** + * Convert a UByteArray to a Float4 vector + */ +@ExperimentalUnsignedTypes +fun UByteArray.toFloat4(): Float4 { + require(size == 4) + return Float4(this[0].toFloat(), this[1].toFloat(), this[2].toFloat(), this[3].toFloat()) +} + +/** + * Convert a ByteArray to a Float4 vector + */ +@ExperimentalUnsignedTypes +fun ByteArray.toFloat4(): Float4 { + require(size == 4) + return Float4( + this[0].toUByte().toFloat(), + this[1].toUByte().toFloat(), + this[2].toUByte().toFloat(), + this[3].toUByte().toFloat() + ) +} + +data class Dimension(val sizeX: Int, val sizeY: Int, val sizeZ: Int) + +/** + * An RGBA value represented by 4 Int. + * + * Note that the arithmetical operations consider a 0..255 value the equivalent of 0f..1f. + * After adding or subtracting, the value is clamped. After multiplying, the value is rescaled to + * stay in the 0..255 range. This is useful for the Blend operation. + */ +@ExperimentalUnsignedTypes +data class Rgba( + var r: Int = 0, + var g: Int = 0, + var b: Int = 0, + var a: Int = 0 +) { + operator fun plus(other: Rgba) = + Rgba(r + other.r, g + other.g, b + other.b, a + other.a).clampToUByteRange() + + operator fun minus(other: Rgba) = + Rgba(r - other.r, g - other.g, b - other.b, a - other.a).clampToUByteRange() + + operator fun times(other: Rgba) = Rgba(r * other.r, g * other.g, b * other.b, a * other.a) shr 8 + operator fun times(scalar: Int) = Rgba(r * scalar, g * scalar, b * scalar, a * scalar) shr 8 + + infix fun xor(other: Rgba) = Rgba(r xor other.r, g xor other.g, b xor other.b, a xor other.a) + + infix fun shr(other: Int) = Rgba(r shr other, g shr other, b shr other, a shr other) + + private fun clampToUByteRange() = Rgba( + r.clampToUByteRange(), + g.clampToUByteRange(), + b.clampToUByteRange(), + a.clampToUByteRange() + ) +} + +/** + * A 2D array of UByte vectors, stored in row-major format. + * + * Arrays of vectorSize == 3 are padded to 4. + */ +@ExperimentalUnsignedTypes +class Vector2dArray( + val values: UByteArray, + val vectorSize: Int, + val sizeX: Int, + val sizeY: Int +) { + /** + * If true, index access that would try to get a value that's out of bounds will simply + * return the border value instead. E.g. for [3, -3] would return the value for [3, 0], + * assuming that the sizeX > 3. + */ + var clipReadToRange: Boolean = false + + operator fun get(x: Int, y: Int): UByteArray { + var fixedX = x + var fixedY = y + if (clipReadToRange) { + fixedX = min(max(x, 0), sizeX - 1) + fixedY = min(max(y, 0), sizeY - 1) + } else { + require(x in 0 until sizeX && y in 0 until sizeY) { "Out of bounds" } + } + val start = indexOfVector(fixedX, fixedY) + return UByteArray(paddedSize(vectorSize)) { values[start + it] } + } + + operator fun set(x: Int, y: Int, value: UByteArray) { + require(value.size == paddedSize(vectorSize)) { "Not the expected vector size" } + require(x in 0 until sizeX && y in 0 until sizeY) { "Out of bounds" } + val start = indexOfVector(x, y) + for (i in value.indices) { + values[start + i] = value[i] + } + } + + private fun indexOfVector(x: Int, y: Int) = ((y * sizeX) + x) * paddedSize(vectorSize) + + fun createSameSized() = Vector2dArray(UByteArray(values.size), vectorSize, sizeX, sizeY) + + fun forEach(restriction: Range2d?, work: (Int, Int) -> (Unit)) { + forEachCell(sizeX, sizeY, restriction, work) + } +} + +/** + * A 2D array of float vectors, stored in row-major format. + * + * Arrays of vectorSize == 3 are padded to 4. + */ +class FloatVector2dArray( + val values: FloatArray, + val vectorSize: Int, + val sizeX: Int, + val sizeY: Int +) { + /** + * If true, index access that would try to get a value that's out of bounds will simply + * return the border value instead. E.g. for [3, -3] would return the value for [3, 0], + * assuming that the sizeX > 3. + */ + var clipAccessToRange: Boolean = false + + operator fun get(x: Int, y: Int): FloatArray { + var fixedX = x + var fixedY = y + if (clipAccessToRange) { + fixedX = min(max(x, 0), sizeX - 1) + fixedY = min(max(y, 0), sizeY - 1) + } else { + require(x in 0 until sizeX && y in 0 until sizeY) { "Out of bounds" } + } + val start = indexOfVector(fixedX, fixedY) + return FloatArray(vectorSize) { values[start + it] } + } + + operator fun set(x: Int, y: Int, value: FloatArray) { + require(x in 0 until sizeX && y in 0 until sizeY) { "Out of bounds" } + val start = indexOfVector(x, y) + for (i in value.indices) { + values[start + i] = value[i] + } + } + + private fun indexOfVector(x: Int, y: Int) = ((y * sizeX) + x) * paddedSize(vectorSize) + + fun createSameSized() = FloatVector2dArray(FloatArray(values.size), vectorSize, sizeX, sizeY) + + fun forEach(restriction: Range2d?, work: (Int, Int) -> (Unit)) { + forEachCell(sizeX, sizeY, restriction, work) + } +} + +/** + * A 2D array of RGBA data. + */ +@ExperimentalUnsignedTypes +class Rgba2dArray( + private val values: ByteArray, + val sizeX: Int, + val sizeY: Int +) { + operator fun get(x: Int, y: Int): Rgba { + val i = indexOfVector(x, y) + return Rgba( + values[i].toUByte().toInt(), + values[i + 1].toUByte().toInt(), + values[i + 2].toUByte().toInt(), + values[i + 3].toUByte().toInt() + ) + } + + operator fun set(x: Int, y: Int, value: Rgba) { + // Verify that x, y, z, w are in the 0..255 range + require(value.r in 0..255) + require(value.g in 0..255) + require(value.b in 0..255) + require(value.a in 0..255) + val i = indexOfVector(x, y) + values[i] = value.r.toUByte().toByte() + values[i + 1] = value.g.toUByte().toByte() + values[i + 2] = value.b.toUByte().toByte() + values[i + 3] = value.a.toUByte().toByte() + } + + private fun indexOfVector(x: Int, y: Int) = ((y * sizeX) + x) * 4 + + fun forEachCell(restriction: Range2d?, work: (Int, Int) -> (Unit)) = + forEachCell(sizeX, sizeY, restriction, work) +} + +/** + * Return a value that's between start and end, with the fraction indicating how far along. + */ +fun mix(start: Float, end: Float, fraction: Float) = start + (end - start) * fraction + +fun mix(a: Float4, b: Float4, fraction: Float) = Float4( + mix(a.x, b.x, fraction), + mix(a.y, b.y, fraction), + mix(a.z, b.z, fraction), + mix(a.w, b.w, fraction) +) + +/** + * For vectors of size 3, the original RenderScript has them occupy the same space as a size 4. + * While RenderScript had a method to avoid this padding, it did not apply to Intrinsics. + * + * To preserve compatibility, the Toolkit doing the same. + */ +fun paddedSize(vectorSize: Int) = if (vectorSize == 3) 4 else vectorSize + +/** + * Create a ByteArray of the specified size filled with random data. + */ +fun randomByteArray(seed: Long, sizeX: Int, sizeY: Int, elementSize: Int): ByteArray { + val r = Random(seed) + return ByteArray(sizeX * sizeY * elementSize) { (r.nextInt(255) - 128).toByte() } +} + +/** + * Create a FloatArray of the specified size filled with random data. + * + * By default, the random data is between 0f and 1f. The factor can be used to scale that. + */ +fun randomFloatArray( + seed: Long, + sizeX: Int, + sizeY: Int, + elementSize: Int, + factor: Float = 1f +): FloatArray { + val r = Random(seed) + return FloatArray(sizeX * sizeY * elementSize) { r.nextFloat() * factor } +} + +/** + * Create a cube of the specified size filled with random data. + */ +fun randomCube(seed: Long, cubeSize: Dimension): ByteArray { + val r = Random(seed) + return ByteArray(cubeSize.sizeX * cubeSize.sizeY * cubeSize.sizeZ * 4) { + (r.nextInt(255) - 128).toByte() + } +} + +/** + * Create the identity cube, i.e. one that if used in Lut3d, the output is the same as the input + */ +@ExperimentalUnsignedTypes +fun identityCube(cubeSize: Dimension): ByteArray { + val data = ByteArray(cubeSize.sizeX * cubeSize.sizeY * cubeSize.sizeZ * 4) + val cube = Rgba3dArray(data, cubeSize.sizeX, cubeSize.sizeY, cubeSize.sizeZ) + for (z in 0 until cubeSize.sizeZ) { + for (y in 0 until cubeSize.sizeY) { + for (x in 0 until cubeSize.sizeX) { + cube[x, y, z] = + byteArrayOf( + (x * 255 / (cubeSize.sizeX - 1)).toByte(), + (y * 255 / (cubeSize.sizeY - 1)).toByte(), + (z * 255 / (cubeSize.sizeZ - 1)).toByte(), + (255).toByte() + ) + } + } + } + return data +} + +fun randomYuvArray(seed: Long, sizeX: Int, sizeY: Int, format: YuvFormat): ByteArray { + // YUV formats are not well defined for odd dimensions + require(sizeX % 2 == 0 && sizeY % 2 == 0) + val halfSizeX = sizeX / 2 + val halfSizeY = sizeY / 2 + var totalSize = 0 + when (format) { + YuvFormat.YV12 -> { + val strideX = roundUpTo16(sizeX) + totalSize = strideX * sizeY + roundUpTo16(strideX / 2) * halfSizeY * 2 + } + YuvFormat.NV21 -> totalSize = sizeX * sizeY + halfSizeX * halfSizeY * 2 + else -> require(false) { "Unknown YUV format $format" } + } + + return randomByteArray(seed, totalSize, 1, 1) +} + +/** + * Converts a float to a byte, clamping to make it fit the limited range. + */ +@ExperimentalUnsignedTypes +fun Float.clampToUByte(): UByte = min(255, max(0, (this + 0.5f).toInt())).toUByte() + +/** + * Converts a FloatArray to UByteArray, clamping. + */ +@ExperimentalUnsignedTypes +fun FloatArray.clampToUByte() = UByteArray(size) { this[it].clampToUByte() } + +/** + * Limits an Int to what can fit in a UByte. + */ +fun Int.clampToUByteRange(): Int = min(255, max(0, this)) + +/** + * Converts an Int to a UByte, clamping. + */ +@ExperimentalUnsignedTypes +fun Int.clampToUByte(): UByte = this.clampToUByteRange().toUByte() + +/** + * Converts a float (0f .. 1f) to a byte (0 .. 255) + */ +@ExperimentalUnsignedTypes +fun unitFloatClampedToUByte(num: Float): UByte = (num * 255f).clampToUByte() + +/** + * Convert a byte (0 .. 255) to a float (0f .. 1f) + */ +@ExperimentalUnsignedTypes +fun byteToUnitFloat(num: UByte) = num.toFloat() * 0.003921569f + +@ExperimentalUnsignedTypes +fun UByteArray.toFloatArray() = FloatArray(size) { this[it].toFloat() } + +/** + * For each cell that's in the 2D array defined by sizeX and sizeY, and clipped down by the + * restriction, invoke the work function. + */ +fun forEachCell(sizeX: Int, sizeY: Int, restriction: Range2d?, work: (Int, Int) -> (Unit)) { + val startX = restriction?.startX ?: 0 + val startY = restriction?.startY ?: 0 + val endX = restriction?.endX ?: sizeX + val endY = restriction?.endY ?: sizeY + for (y in startY until endY) { + for (x in startX until endX) { + work(x, y) + } + } +} + +operator fun FloatArray.times(other: FloatArray) = FloatArray(size) { this[it] * other[it] } +operator fun FloatArray.times(other: Float) = FloatArray(size) { this[it] * other } +operator fun FloatArray.plus(other: FloatArray) = FloatArray(size) { this[it] + other[it] } +operator fun FloatArray.minus(other: FloatArray) = FloatArray(size) { this[it] - other[it] } + +fun renderScriptVectorElementForU8(rs: RenderScript?, vectorSize: Int): Element { + when (vectorSize) { + 1 -> return Element.U8(rs) + 2 -> return Element.U8_2(rs) + 3 -> return Element.U8_3(rs) + 4 -> return Element.U8_4(rs) + } + throw java.lang.IllegalArgumentException("RenderScriptToolkit tests. Only vectors of size 1-4 are supported. $vectorSize provided.") +} + +fun renderScriptVectorElementForI32(rs: RenderScript?, vectorSize: Int): Element { + when (vectorSize) { + 1 -> return Element.I32(rs) + 2 -> return Element.I32_2(rs) + 3 -> return Element.I32_3(rs) + 4 -> return Element.I32_4(rs) + } + throw java.lang.IllegalArgumentException("RenderScriptToolkit tests. Only vectors of size 1-4 are supported. $vectorSize provided.") +} + +/* When we'll handle floats +fun renderScriptVectorElementForF32(rs: RenderScript?, vectorSize: Int): Element { + when (vectorSize) { + 1 -> return Element.F32(rs) + 2 -> return Element.F32_2(rs) + 3 -> return Element.F32_3(rs) + 4 -> return Element.F32_4(rs) + } + throw java.lang.IllegalArgumentException("RenderScriptToolkit tests. Only vectors of size 1-4 are supported. $vectorSize provided.") +}*/ + +fun renderScriptElementForBitmap(context: RenderScript, bitmap: Bitmap): Element { + return when (val config = bitmap.config) { + Bitmap.Config.ALPHA_8 -> Element.A_8(context) + Bitmap.Config.ARGB_8888 -> Element.RGBA_8888(context) + else -> throw IllegalArgumentException("RenderScript Toolkit can't support bitmaps with config $config.") + } +} + +fun getBitmapBytes(bitmap: Bitmap): ByteArray { + val buffer: ByteBuffer = ByteBuffer.allocate(bitmap.byteCount) + bitmap.copyPixelsToBuffer(buffer) + return buffer.array() +} + +fun vectorSizeOfBitmap(bitmap: Bitmap): Int { + return when (val config = bitmap.config) { + Bitmap.Config.ALPHA_8 -> 1 + Bitmap.Config.ARGB_8888 -> 4 + else -> throw IllegalArgumentException("RenderScript Toolkit can't support bitmaps with config $config.") + } +} + +fun duplicateBitmap(original: Bitmap): Bitmap { + val copy = Bitmap.createBitmap(original.width, original.height, original.config) + val canvas = Canvas(copy) + canvas.drawBitmap(original, 0f, 0f, null) + return copy +} + +@ExperimentalUnsignedTypes +fun logArray(prefix: String, array: ByteArray, number: Int = 20) { + val values = array.joinToString(limit = number) { it.toUByte().toString() } + println("$prefix[${array.size}] $values}\n") +} + +fun logArray(prefix: String, array: IntArray, number: Int = 20) { + val values = array.joinToString(limit = number) + println("$prefix[${array.size}] $values}\n") +} + +fun logArray(prefix: String, array: FloatArray?, number: Int = 20) { + val values = array?.joinToString(limit = number) { "%.2f".format(it) } ?: "(null)" + println("$prefix[${array?.size}] $values}\n") +} + +fun roundUpTo16(value: Int): Int { + require(value >= 0) + return (value + 15) and 15.inv() +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicBlend.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicBlend.kt new file mode 100644 index 0000000..17176df --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicBlend.kt @@ -0,0 +1,188 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicBlend +import android.renderscript.Type +import com.google.android.renderscript.BlendingMode +import com.google.android.renderscript.Range2d + +/** + * Does a Blend operation using the RenderScript Intrinsics. + */ +fun intrinsicBlend( + context: RenderScript, + mode: BlendingMode, + sourceArray: ByteArray, + destArray: ByteArray, + sizeX: Int, + sizeY: Int, + restriction: Range2d? +) { + val scriptBlend = ScriptIntrinsicBlend.create(context, Element.U8_4(context)) + val builder = Type.Builder(context, Element.U8_4(context)) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val sourceAllocation = Allocation.createTyped(context, arrayType) + val destAllocation = Allocation.createTyped(context, arrayType) + sourceAllocation.copyFrom(sourceArray) + destAllocation.copyFrom(destArray) + + callBlendForEach(scriptBlend, sourceAllocation, destAllocation, mode, restriction) + destAllocation.copyTo(destArray) + + sourceAllocation.destroy() + destAllocation.destroy() + arrayType.destroy() + scriptBlend.destroy() +} + +fun intrinsicBlend( + context: RenderScript, + mode: BlendingMode, + sourceBitmap: Bitmap, + destBitmap: Bitmap, + restriction: Range2d? +) { + val scriptBlend = ScriptIntrinsicBlend.create(context, Element.U8_4(context)) + val sourceAllocation = Allocation.createFromBitmap(context, sourceBitmap) + val destAllocation = Allocation.createFromBitmap(context, destBitmap) + sourceAllocation.copyFrom(sourceBitmap) + destAllocation.copyFrom(destBitmap) + + callBlendForEach(scriptBlend, sourceAllocation, destAllocation, mode, restriction) + destAllocation.copyTo(destBitmap) + + sourceAllocation.destroy() + destAllocation.destroy() + scriptBlend.destroy() +} + +private fun callBlendForEach( + scriptBlend: ScriptIntrinsicBlend, + sourceAllocation: Allocation, + destAllocation: Allocation, + mode: BlendingMode, + restriction: Range2d? +) { + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + when (mode) { + BlendingMode.CLEAR -> scriptBlend.forEachClear( + sourceAllocation, destAllocation, options + ) + BlendingMode.SRC -> scriptBlend.forEachSrc( + sourceAllocation, destAllocation, options + ) + BlendingMode.DST -> scriptBlend.forEachDst( + sourceAllocation, destAllocation, options + ) + BlendingMode.SRC_OVER -> scriptBlend.forEachSrcOver( + sourceAllocation, destAllocation, options + ) + BlendingMode.DST_OVER -> scriptBlend.forEachDstOver( + sourceAllocation, destAllocation, options + ) + BlendingMode.SRC_IN -> scriptBlend.forEachSrcIn( + sourceAllocation, destAllocation, options + ) + BlendingMode.DST_IN -> scriptBlend.forEachDstIn( + sourceAllocation, destAllocation, options + ) + BlendingMode.SRC_OUT -> scriptBlend.forEachSrcOut( + sourceAllocation, destAllocation, options + ) + BlendingMode.DST_OUT -> scriptBlend.forEachDstOut( + sourceAllocation, destAllocation, options + ) + BlendingMode.SRC_ATOP -> scriptBlend.forEachSrcAtop( + sourceAllocation, destAllocation, options + ) + BlendingMode.DST_ATOP -> scriptBlend.forEachDstAtop( + sourceAllocation, destAllocation, options + ) + BlendingMode.XOR -> scriptBlend.forEachXor( + sourceAllocation, destAllocation, options + ) + BlendingMode.MULTIPLY -> scriptBlend.forEachMultiply( + sourceAllocation, destAllocation, options + ) + BlendingMode.ADD -> scriptBlend.forEachAdd( + sourceAllocation, destAllocation, options + ) + BlendingMode.SUBTRACT -> scriptBlend.forEachSubtract( + sourceAllocation, destAllocation, options + ) + } + } else { + when (mode) { + BlendingMode.CLEAR -> scriptBlend.forEachClear( + sourceAllocation, destAllocation + ) + BlendingMode.SRC -> scriptBlend.forEachSrc( + sourceAllocation, destAllocation + ) + BlendingMode.DST -> scriptBlend.forEachDst( + sourceAllocation, destAllocation + ) + BlendingMode.SRC_OVER -> scriptBlend.forEachSrcOver( + sourceAllocation, destAllocation + ) + BlendingMode.DST_OVER -> scriptBlend.forEachDstOver( + sourceAllocation, destAllocation + ) + BlendingMode.SRC_IN -> scriptBlend.forEachSrcIn( + sourceAllocation, destAllocation + ) + BlendingMode.DST_IN -> scriptBlend.forEachDstIn( + sourceAllocation, destAllocation + ) + BlendingMode.SRC_OUT -> scriptBlend.forEachSrcOut( + sourceAllocation, destAllocation + ) + BlendingMode.DST_OUT -> scriptBlend.forEachDstOut( + sourceAllocation, destAllocation + ) + BlendingMode.SRC_ATOP -> scriptBlend.forEachSrcAtop( + sourceAllocation, destAllocation + ) + BlendingMode.DST_ATOP -> scriptBlend.forEachDstAtop( + sourceAllocation, destAllocation + ) + BlendingMode.XOR -> scriptBlend.forEachXor( + sourceAllocation, destAllocation + ) + BlendingMode.MULTIPLY -> scriptBlend.forEachMultiply( + sourceAllocation, destAllocation + ) + BlendingMode.ADD -> scriptBlend.forEachAdd( + sourceAllocation, destAllocation + ) + BlendingMode.SUBTRACT -> scriptBlend.forEachSubtract( + sourceAllocation, destAllocation + ) + } + } +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicBlur.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicBlur.kt new file mode 100644 index 0000000..0fd503f --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicBlur.kt @@ -0,0 +1,108 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicBlur +import android.renderscript.Type +import com.google.android.renderscript.Range2d + +/** + * Does a Blur operation using the RenderScript Intrinsics. + */ +fun intrinsicBlur( + context: RenderScript, + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + radius: Int, + restriction: Range2d? +): ByteArray { + val scriptBlur = ScriptIntrinsicBlur.create( + context, + if (vectorSize == 4) Element.RGBA_8888(context) else Element.U8(context) + ) + val builder = + Type.Builder( + context, + renderScriptVectorElementForU8(context, vectorSize) + ) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, arrayType) + inputAllocation.copyFrom(inputArray) + val outAllocation = Allocation.createTyped(context, arrayType) + + val intrinsicOutArray = ByteArray(sizeX * sizeY * vectorSize) + scriptBlur.setRadius(radius.toFloat()) + scriptBlur.setInput(inputAllocation) + + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptBlur.forEach(outAllocation, options) + } else { + scriptBlur.forEach(outAllocation) + } + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + arrayType.destroy() + scriptBlur.destroy() + return intrinsicOutArray +} + +fun intrinsicBlur( + context: RenderScript, + bitmap: Bitmap, + radius: Int, + restriction: Range2d? +): ByteArray { + val baseElement = renderScriptElementForBitmap(context, bitmap) + val scriptBlur = ScriptIntrinsicBlur.create(context, baseElement) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + val outAllocation = Allocation.createTyped(context, inputAllocation.type) + val intrinsicOutArray = ByteArray(bitmap.byteCount) + + scriptBlur.setRadius(radius.toFloat()) + scriptBlur.setInput(inputAllocation) + + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptBlur.forEach(outAllocation, options) + } else { + scriptBlur.forEach(outAllocation) + } + outAllocation.copyTo(intrinsicOutArray) + + inputAllocation.destroy() + outAllocation.destroy() + scriptBlur.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicColorMatrix.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicColorMatrix.kt new file mode 100644 index 0000000..224b925 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicColorMatrix.kt @@ -0,0 +1,162 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Matrix4f +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicColorMatrix +import android.renderscript.Type +import android.renderscript.Float4 +import com.google.android.renderscript.Range2d + +/** + * Does a ColorMatrix operation using the RenderScript Intrinsics. + */ +fun intrinsicColorMatrix( + context: RenderScript, + conversion: Tester.ColorMatrixConversionType, + inputArray: ByteArray, + inputVectorSize: Int, + sizeX: Int, + sizeY: Int, + outputVectorSize: Int, + matrix: FloatArray, + addVector: FloatArray, + restriction: Range2d? +): ByteArray { + val scriptColorMatrix = ScriptIntrinsicColorMatrix.create(context) + val inputBuilder = Type.Builder( + context, renderScriptVectorElementForU8( + context, + inputVectorSize + ) + ) + inputBuilder.setX(sizeX) + inputBuilder.setY(sizeY) + val inputArrayType = inputBuilder.create() + val inputAllocation = Allocation.createTyped(context, inputArrayType) + val outputBuilder = Type.Builder( + context, renderScriptVectorElementForU8( + context, + outputVectorSize + ) + ) + outputBuilder.setX(sizeX) + outputBuilder.setY(sizeY) + val outputArrayType = outputBuilder.create() + val outAllocation = Allocation.createTyped(context, outputArrayType) + + inputAllocation.copyFrom(inputArray) + val intrinsicOutArray = ByteArray(sizeX * sizeY * paddedSize(outputVectorSize)) + when (conversion) { + Tester.ColorMatrixConversionType.RGB_TO_YUV -> scriptColorMatrix.setRGBtoYUV() + Tester.ColorMatrixConversionType.YUV_TO_RGB -> scriptColorMatrix.setYUVtoRGB() + Tester.ColorMatrixConversionType.GREYSCALE -> scriptColorMatrix.setGreyscale() + Tester.ColorMatrixConversionType.RANDOM -> { + val m = Matrix4f() + var index = 0 + // RS is column major + for (x in 0..3) { + for (y in 0..3) { + m.set(x, y, matrix[index++]) + } + } + scriptColorMatrix.setColorMatrix(m) + } + } + val vector = Float4( + addVector[0], + addVector[1], + addVector[2], + addVector[3] + ) + scriptColorMatrix.setAdd(vector) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptColorMatrix.forEach(inputAllocation, outAllocation, options) + } else { + scriptColorMatrix.forEach(inputAllocation, outAllocation) + } + outAllocation.copyTo(intrinsicOutArray) + + inputAllocation.destroy() + outAllocation.destroy() + inputArrayType.destroy() + outputArrayType.destroy() + scriptColorMatrix.destroy() + return intrinsicOutArray +} + +fun intrinsicColorMatrix( + context: RenderScript, + conversion: Tester.ColorMatrixConversionType, + bitmap: Bitmap, + matrix: FloatArray, + addVector: FloatArray, + restriction: Range2d? +): ByteArray { + val scriptColorMatrix = ScriptIntrinsicColorMatrix.create(context) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + val outAllocation = Allocation.createTyped(context, inputAllocation.type) + val intrinsicOutArray = ByteArray(bitmap.byteCount) + + when (conversion) { + Tester.ColorMatrixConversionType.RGB_TO_YUV -> scriptColorMatrix.setRGBtoYUV() + Tester.ColorMatrixConversionType.YUV_TO_RGB -> scriptColorMatrix.setYUVtoRGB() + Tester.ColorMatrixConversionType.GREYSCALE -> scriptColorMatrix.setGreyscale() + Tester.ColorMatrixConversionType.RANDOM -> { + val m = Matrix4f() + var index = 0 + // RS is column major + for (x in 0..3) { + for (y in 0..3) { + m.set(x, y, matrix[index++]) + } + } + scriptColorMatrix.setColorMatrix(m) + } + } + val vector = Float4( + addVector[0], + addVector[1], + addVector[2], + addVector[3] + ) + scriptColorMatrix.setAdd(vector) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptColorMatrix.forEach(inputAllocation, outAllocation, options) + } else { + scriptColorMatrix.forEach(inputAllocation, outAllocation) + } + outAllocation.copyTo(intrinsicOutArray) + + inputAllocation.destroy() + outAllocation.destroy() + scriptColorMatrix.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicConvolve.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicConvolve.kt new file mode 100644 index 0000000..6b8fecb --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicConvolve.kt @@ -0,0 +1,140 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicConvolve3x3 +import android.renderscript.ScriptIntrinsicConvolve5x5 +import android.renderscript.Type +import com.google.android.renderscript.Range2d + +/** + * Does a Convolve operation using the RenderScript Intrinsics. + */ +fun intrinsicConvolve( + context: RenderScript, + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + coefficients: FloatArray, + restriction: Range2d? +): ByteArray { + val baseElement = renderScriptVectorElementForU8(context, vectorSize) + val builder = Type.Builder(context, baseElement) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, arrayType) + val outAllocation = Allocation.createTyped(context, arrayType) + inputAllocation.copyFrom(inputArray) + val intrinsicOutArray = ByteArray(sizeX * sizeY * paddedSize(vectorSize)) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + } + invokeConvolveKernel( + coefficients, + context, + baseElement, + inputAllocation, + restriction, + outAllocation + ) + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + arrayType.destroy() + return intrinsicOutArray +} + +fun intrinsicConvolve( + context: RenderScript, + bitmap: Bitmap, + coefficients: FloatArray, + restriction: Range2d? +): ByteArray { + val baseElement = renderScriptElementForBitmap(context, bitmap) + + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + val outAllocation = Allocation.createTyped(context, inputAllocation.type) + val intrinsicOutArray = ByteArray(bitmap.byteCount) + inputAllocation.copyFrom(bitmap) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + } + invokeConvolveKernel( + coefficients, + context, + baseElement, + inputAllocation, + restriction, + outAllocation + ) + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + return intrinsicOutArray +} + +private fun invokeConvolveKernel( + coefficients: FloatArray, + context: RenderScript, + baseElement: Element, + inputAllocation: Allocation?, + restriction: Range2d?, + outAllocation: Allocation? +) { + when (coefficients.size) { + 9 -> { + val scriptConvolve3x3 = + ScriptIntrinsicConvolve3x3.create(context, baseElement) + scriptConvolve3x3.setCoefficients(coefficients) + scriptConvolve3x3.setInput(inputAllocation) + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptConvolve3x3.forEach(outAllocation, options) + } else { + scriptConvolve3x3.forEach(outAllocation) + } + scriptConvolve3x3.destroy() + } + 25 -> { + val scriptConvolve5x5 = + ScriptIntrinsicConvolve5x5.create(context, baseElement) + scriptConvolve5x5.setCoefficients(coefficients) + scriptConvolve5x5.setInput(inputAllocation) + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptConvolve5x5.forEach(outAllocation, options) + } else { + scriptConvolve5x5.forEach(outAllocation) + } + scriptConvolve5x5.destroy() + } + else -> { + throw IllegalArgumentException("RenderScriptToolkit tests. Only 3x3 and 5x5 convolutions are supported. ${coefficients.size} coefficients provided.") + } + } +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicHistogram.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicHistogram.kt new file mode 100644 index 0000000..31a27d9 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicHistogram.kt @@ -0,0 +1,196 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicHistogram +import android.renderscript.Type +import com.google.android.renderscript.Range2d + +/** + * Does a Histogram operation using the RenderScript Intrinsics. + */ +fun intrinsicHistogram( + context: RenderScript, + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + restriction: Range2d? +): IntArray { + val element = renderScriptVectorElementForU8(context, vectorSize) + val scriptHistogram = ScriptIntrinsicHistogram.create(context, element) + val builder = Type.Builder(context, element) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, arrayType) + val outAllocation = + Allocation.createSized( + context, + renderScriptVectorElementForI32(context, vectorSize), + 256 + ) + inputAllocation.copyFrom(inputArray) + scriptHistogram.setOutput(outAllocation) + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptHistogram.forEach(inputAllocation, options) + } else { + scriptHistogram.forEach(inputAllocation) + } + + val intrinsicOutArray = IntArray(256 * paddedSize(vectorSize)) + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + arrayType.destroy() + scriptHistogram.destroy() + return intrinsicOutArray +} + +fun intrinsicHistogram( + context: RenderScript, + bitmap: Bitmap, + restriction: Range2d? +): IntArray { + val baseElement = renderScriptElementForBitmap(context, bitmap) + val scriptHistogram = ScriptIntrinsicHistogram.create(context, baseElement) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + val vectorSize = vectorSizeOfBitmap(bitmap) + val outAllocation = + Allocation.createSized( + context, + renderScriptVectorElementForI32(context, vectorSize), + 256 + ) + scriptHistogram.setOutput(outAllocation) + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptHistogram.forEach(inputAllocation, options) + } else { + scriptHistogram.forEach(inputAllocation) + } + + val intrinsicOutArray = IntArray(256 * vectorSize) + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + scriptHistogram.destroy() + return intrinsicOutArray +} + +fun intrinsicHistogramDot( + context: RenderScript, + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + coefficients: FloatArray?, + restriction: Range2d? +): IntArray { + val element = renderScriptVectorElementForU8(context, vectorSize) + val scriptHistogram = ScriptIntrinsicHistogram.create(context, element) + val builder = Type.Builder(context, element) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, arrayType) + val outAllocation = + Allocation.createSized(context, Element.I32(context), 256) + inputAllocation.copyFrom(inputArray) + + if (coefficients != null) { + require(coefficients.size == vectorSize) { + "RenderScriptToolkit tests. $vectorSize coefficients are required for histogram. " + + "${coefficients.size} provided." + } + scriptHistogram.setDotCoefficients( + coefficients[0], + if (vectorSize > 1) coefficients[1] else 0f, + if (vectorSize > 2) coefficients[2] else 0f, + if (vectorSize > 3) coefficients[3] else 0f + ) + } + scriptHistogram.setOutput(outAllocation) + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptHistogram.forEach_Dot(inputAllocation, options) + } else { + scriptHistogram.forEach_Dot(inputAllocation) + } + val intrinsicOutArray = IntArray(256) + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + arrayType.destroy() + scriptHistogram.destroy() + return intrinsicOutArray +} + +fun intrinsicHistogramDot( + context: RenderScript, + bitmap: Bitmap, + coefficients: FloatArray?, + restriction: Range2d? +): IntArray { + val baseElement = renderScriptElementForBitmap(context, bitmap) + val scriptHistogram = ScriptIntrinsicHistogram.create(context, baseElement) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + val outAllocation = + Allocation.createSized(context, Element.I32(context), 256) + + if (coefficients != null) { + require(coefficients.size == 4) { + "RenderScriptToolkit tests. Four coefficients are required for histogram. " + + "${coefficients.size} provided." + } + scriptHistogram.setDotCoefficients( + coefficients[0], + coefficients[1], + coefficients[2], + coefficients[3] + ) + } + scriptHistogram.setOutput(outAllocation) + if (restriction != null) { + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptHistogram.forEach_Dot(inputAllocation, options) + } else { + scriptHistogram.forEach_Dot(inputAllocation) + } + val intrinsicOutArray = IntArray(256) + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + scriptHistogram.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicLut.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicLut.kt new file mode 100644 index 0000000..108eb87 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicLut.kt @@ -0,0 +1,118 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicLUT +import android.renderscript.Type +import com.google.android.renderscript.Range2d + +/** + * Does a LookUpTable operation using the RenderScript Intrinsics. + */ +@ExperimentalUnsignedTypes +fun intrinsicLut( + context: RenderScript, + inputArray: ByteArray, + sizeX: Int, + sizeY: Int, + newRed: ByteArray, + newGreen: ByteArray, + newBlue: ByteArray, + newAlpha: ByteArray, + restriction: Range2d? +): ByteArray { + val scriptLut: ScriptIntrinsicLUT = ScriptIntrinsicLUT.create( + context, + Element.U8_4(context) + ) + val builder = Type.Builder(context, Element.U8_4(context)) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, arrayType) + val outAllocation = Allocation.createTyped(context, arrayType) + inputAllocation.copyFrom(inputArray) + val intrinsicOutArray = ByteArray(sizeX * sizeY * 4) + + for (v in 0..255) { + scriptLut.setRed(v, newRed[v].toUByte().toInt()) + scriptLut.setGreen(v, newGreen[v].toUByte().toInt()) + scriptLut.setBlue(v, newBlue[v].toUByte().toInt()) + scriptLut.setAlpha(v, newAlpha[v].toUByte().toInt()) + } + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptLut.forEach(inputAllocation, outAllocation, options) + } else { + scriptLut.forEach(inputAllocation, outAllocation) + } + + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + arrayType.destroy() + scriptLut.destroy() + return intrinsicOutArray +} + +@ExperimentalUnsignedTypes +fun intrinsicLut( + context: RenderScript, + bitmap: Bitmap, + newRed: ByteArray, + newGreen: ByteArray, + newBlue: ByteArray, + newAlpha: ByteArray, + restriction: Range2d? +): ByteArray { + val baseElement = renderScriptElementForBitmap(context, bitmap) + val scriptLut: ScriptIntrinsicLUT = ScriptIntrinsicLUT.create(context, baseElement) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + val outAllocation = Allocation.createTyped(context, inputAllocation.type) + val intrinsicOutArray = ByteArray(bitmap.byteCount) + + for (v in 0..255) { + scriptLut.setRed(v, newRed[v].toUByte().toInt()) + scriptLut.setGreen(v, newGreen[v].toUByte().toInt()) + scriptLut.setBlue(v, newBlue[v].toUByte().toInt()) + scriptLut.setAlpha(v, newAlpha[v].toUByte().toInt()) + } + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptLut.forEach(inputAllocation, outAllocation, options) + } else { + scriptLut.forEach(inputAllocation, outAllocation) + } + + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + scriptLut.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicLut3d.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicLut3d.kt new file mode 100644 index 0000000..c9f08f6 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicLut3d.kt @@ -0,0 +1,123 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsic3DLUT +import android.renderscript.Type +import com.google.android.renderscript.Range2d + +/** + * Does a 3D LookUpTable operation using the RenderScript Intrinsics. + */ +fun intrinsicLut3d( + context: RenderScript, + inputArray: ByteArray, + sizeX: Int, + sizeY: Int, + cubeArray: ByteArray, + cubeSize: Dimension, + restriction: Range2d? +): ByteArray { + val scriptLut3d: ScriptIntrinsic3DLUT = ScriptIntrinsic3DLUT.create( + context, Element.U8_4( + context + ) + ) + val builder = Type.Builder(context, Element.U8_4(context)) + builder.setX(sizeX) + builder.setY(sizeY) + val arrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, arrayType) + val outAllocation = Allocation.createTyped(context, arrayType) + inputAllocation.copyFrom(inputArray) + val intrinsicOutArray = ByteArray(sizeX * sizeY * 4) + + val cubeTypeBuilder: Type.Builder = + Type.Builder(context, Element.U8_4(context)) + cubeTypeBuilder.setX(cubeSize.sizeX) + cubeTypeBuilder.setY(cubeSize.sizeY) + cubeTypeBuilder.setZ(cubeSize.sizeZ) + val cubeType: Type = cubeTypeBuilder.create() + val cubeAllocation = Allocation.createTyped(context, cubeType) + cubeAllocation.copyFrom(cubeArray) + scriptLut3d.setLUT(cubeAllocation) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptLut3d.forEach(inputAllocation, outAllocation, options) + } else { + scriptLut3d.forEach(inputAllocation, outAllocation) + } + + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + cubeAllocation.destroy() + arrayType.destroy() + cubeType.destroy() + scriptLut3d.destroy() + return intrinsicOutArray +} + +fun intrinsicLut3d( + context: RenderScript, + bitmap: Bitmap, + cubeArray: ByteArray, + cubeSize: Dimension, + restriction: Range2d? +): ByteArray { + val baseElement = renderScriptElementForBitmap(context, bitmap) + val scriptLut3d: ScriptIntrinsic3DLUT = ScriptIntrinsic3DLUT.create(context, baseElement) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + val outAllocation = Allocation.createTyped(context, inputAllocation.type) + val intrinsicOutArray = ByteArray(bitmap.byteCount) + + val cubeTypeBuilder: Type.Builder = + Type.Builder(context, Element.U8_4(context)) + cubeTypeBuilder.setX(cubeSize.sizeX) + cubeTypeBuilder.setY(cubeSize.sizeY) + cubeTypeBuilder.setZ(cubeSize.sizeZ) + val cubeType: Type = cubeTypeBuilder.create() + val cubeAllocation = Allocation.createTyped(context, cubeType) + cubeAllocation.copyFrom(cubeArray) + scriptLut3d.setLUT(cubeAllocation) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptLut3d.forEach(inputAllocation, outAllocation, options) + } else { + scriptLut3d.forEach(inputAllocation, outAllocation) + } + + outAllocation.copyTo(intrinsicOutArray) + inputAllocation.destroy() + outAllocation.destroy() + cubeAllocation.destroy() + cubeType.destroy() + scriptLut3d.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicResize.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicResize.kt new file mode 100644 index 0000000..647efdd --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicResize.kt @@ -0,0 +1,119 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.Bitmap +import android.renderscript.Allocation +import android.renderscript.RenderScript +import android.renderscript.Script +import android.renderscript.ScriptIntrinsicResize +import android.renderscript.Type +import com.google.android.renderscript.Range2d + +/** + * Does a Resize operation using the RenderScript Intrinsics. + */ +fun intrinsicResize( + context: RenderScript, + inputArray: ByteArray, + vectorSize: Int, + inSizeX: Int, + inSizeY: Int, + outSizeX: Int, + outSizeY: Int, + restriction: Range2d? +): ByteArray { + val scriptResize = ScriptIntrinsicResize.create(context) + val builder = Type.Builder( + context, + renderScriptVectorElementForU8(context, vectorSize) + ) + builder.setX(inSizeX) + builder.setY(inSizeY) + val inputArrayType = builder.create() + val inputAllocation = Allocation.createTyped(context, inputArrayType) + builder.setX(outSizeX) + builder.setY(outSizeY) + val outputArrayType = builder.create() + val outAllocation = Allocation.createTyped(context, outputArrayType) + val intrinsicOutArray = ByteArray(outSizeX * outSizeY * paddedSize(vectorSize)) + + inputAllocation.copyFrom(inputArray) + scriptResize.setInput(inputAllocation) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptResize.forEach_bicubic(outAllocation, options) + } else { + scriptResize.forEach_bicubic(outAllocation) + } + outAllocation.copyTo(intrinsicOutArray) + + inputAllocation.destroy() + outAllocation.destroy() + scriptResize.destroy() + inputArrayType.destroy() + outputArrayType.destroy() + return intrinsicOutArray +} + +fun intrinsicResize( + context: RenderScript, + bitmap: Bitmap, + outSizeX: Int, + outSizeY: Int, + restriction: Range2d? +): ByteArray { + val scriptResize = ScriptIntrinsicResize.create(context) + val inputAllocation = Allocation.createFromBitmap(context, bitmap) + inputAllocation.copyFrom(bitmap) + + val vectorSize = when (bitmap.config) { + Bitmap.Config.ARGB_8888 -> 4 + Bitmap.Config.ALPHA_8 -> 1 + else -> error("Unrecognized bitmap config $bitmap.config") + } + val builder = Type.Builder( + context, + renderScriptVectorElementForU8(context, vectorSize) + ) + builder.setX(outSizeX) + builder.setY(outSizeY) + val outputArrayType = builder.create() + val outAllocation = Allocation.createTyped(context, outputArrayType) + val intrinsicOutArray = ByteArray(outSizeX * outSizeY * vectorSize) + + scriptResize.setInput(inputAllocation) + if (restriction != null) { + outAllocation.copyFrom(intrinsicOutArray) // To initialize to zero + val options = Script.LaunchOptions() + options.setX(restriction.startX, restriction.endX) + options.setY(restriction.startY, restriction.endY) + scriptResize.forEach_bicubic(outAllocation, options) + } else { + scriptResize.forEach_bicubic(outAllocation) + } + outAllocation.copyTo(intrinsicOutArray) + + inputAllocation.destroy() + outAllocation.destroy() + outputArrayType.destroy() + scriptResize.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicYuvToRgb.kt b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicYuvToRgb.kt new file mode 100644 index 0000000..2ae8a89 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/IntrinsicYuvToRgb.kt @@ -0,0 +1,70 @@ +/* + * 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.google.android.renderscript_test + +import android.graphics.ImageFormat +import android.renderscript.Allocation +import android.renderscript.Element +import android.renderscript.RenderScript +import android.renderscript.ScriptIntrinsicYuvToRGB +import android.renderscript.Type +import com.google.android.renderscript.YuvFormat + +/** + * Does a YUV to RGB operation using the RenderScript Intrinsics. + */ +fun intrinsicYuvToRgb( + context: RenderScript, + inputArray: ByteArray, + sizeX: Int, + sizeY: Int, + format: YuvFormat +): ByteArray { + val scriptYuvToRgb = ScriptIntrinsicYuvToRGB.create( + context, + Element.YUV(context) + ) + val inputBuilder = Type.Builder(context, Element.YUV(context)) + inputBuilder.setX(sizeX) + inputBuilder.setY(sizeY) + when (format) { + YuvFormat.NV21 -> inputBuilder.setYuvFormat(ImageFormat.NV21) + YuvFormat.YV12 -> inputBuilder.setYuvFormat(ImageFormat.YV12) + else -> require(false) { "Unknown YUV format $format" } + } + val inputArrayType = inputBuilder.create() + val inputAllocation = Allocation.createTyped(context, inputArrayType) + + val outputBuilder = Type.Builder(context, Element.U8_4(context)) + outputBuilder.setX(sizeX) + outputBuilder.setY(sizeY) + val outputArrayType = outputBuilder.create() + val outAllocation = Allocation.createTyped(context, outputArrayType) + val intrinsicOutArray = ByteArray(sizeX * sizeY * 4) + + inputAllocation.copyFrom(inputArray) + scriptYuvToRgb.setInput(inputAllocation) + scriptYuvToRgb.forEach(outAllocation) + outAllocation.copyTo(intrinsicOutArray) + + inputAllocation.destroy() + outAllocation.destroy() + inputArrayType.destroy() + outputArrayType.destroy() + scriptYuvToRgb.destroy() + return intrinsicOutArray +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/MainActivity.kt b/test-app/src/main/java/com/google/android/renderscript_test/MainActivity.kt new file mode 100644 index 0000000..10ea5f7 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/MainActivity.kt @@ -0,0 +1,39 @@ +/* + * 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.google.android.renderscript_test + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity + + +@ExperimentalUnsignedTypes +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + // To debug resources not destroyed + // "A resource failed to call destroy." + try { + Class.forName("dalvik.system.CloseGuard") + .getMethod("setEnabled", Boolean::class.javaPrimitiveType) + .invoke(null, true) + } catch (e: ReflectiveOperationException) { + throw RuntimeException(e) + } + } +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceBlend.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceBlend.kt new file mode 100644 index 0000000..1d9b1fd --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceBlend.kt @@ -0,0 +1,82 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.BlendingMode +import com.google.android.renderscript.Range2d + +/** + * Reference implementation of a Blend operation. + * + * See the class Rgba for details of arithmetic operation using that class. + */ +@ExperimentalUnsignedTypes +fun referenceBlend( + mode: BlendingMode, + sourceArray: ByteArray, + destArray: ByteArray, + sizeX: Int, + sizeY: Int, + restriction: Range2d? +) { + val source = Rgba2dArray(sourceArray, sizeX, sizeY) + val dest = Rgba2dArray(destArray, sizeX, sizeY) + + /** + * For each corresponding RGBA value of the source and destination arrays, invoke the blend + * function and store the result in the destination array. + */ + fun blendEachPair(blendFunction: (src: Rgba, dst: Rgba) -> Rgba) { + dest.forEachCell(restriction) { x, y -> + dest[x, y] = blendFunction(source[x, y], dest[x, y]) + } + } + + when (mode) { + BlendingMode.CLEAR -> blendEachPair { _, _ -> Rgba(0, 0, 0, 0) } + BlendingMode.SRC -> blendEachPair { src, _ -> src } + BlendingMode.DST -> { /* This doesn't do anything. */ } + BlendingMode.SRC_OVER -> blendEachPair { src, dst -> blendOver(src, dst) } + BlendingMode.DST_OVER -> blendEachPair { src, dst -> blendOver(dst, src) } + BlendingMode.SRC_IN -> blendEachPair { src, dst -> blendIn(src, dst) } + BlendingMode.DST_IN -> blendEachPair { src, dst -> blendIn(dst, src) } + BlendingMode.SRC_OUT -> blendEachPair { src, dst -> blendOut(src, dst) } + BlendingMode.DST_OUT -> blendEachPair { src, dst -> blendOut(dst, src) } + BlendingMode.SRC_ATOP -> blendEachPair { src, dst -> blendAtop(src, dst) } + BlendingMode.DST_ATOP -> blendEachPair { src, dst -> blendAtop(dst, src) } + BlendingMode.XOR -> blendEachPair { src, dst -> src xor dst } + BlendingMode.MULTIPLY -> blendEachPair { src, dst -> src * dst } + BlendingMode.ADD -> blendEachPair { src, dst -> dst + src } + BlendingMode.SUBTRACT -> blendEachPair { src, dst -> dst - src } + } +} + +@ExperimentalUnsignedTypes +private fun blendOver(src: Rgba, dst: Rgba) = src + (dst * (255 - src.a)) + +@ExperimentalUnsignedTypes +private fun blendIn(src: Rgba, dst: Rgba) = src * dst.a + +@ExperimentalUnsignedTypes +private fun blendOut(src: Rgba, dst: Rgba) = src * (255 - dst.a) + +@ExperimentalUnsignedTypes +private fun blendAtop(src: Rgba, dst: Rgba): Rgba { + val value = src * dst.a + dst * (255 - src.a) + value.a = dst.a + return value +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceBlur.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceBlur.kt new file mode 100644 index 0000000..7b4792d --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceBlur.kt @@ -0,0 +1,131 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.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 +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceColorMatrix.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceColorMatrix.kt new file mode 100644 index 0000000..0800873 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceColorMatrix.kt @@ -0,0 +1,57 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.Range2d + +/** + * Reference implementation of a ColorMatrix operation. + */ +@ExperimentalUnsignedTypes +fun referenceColorMatrix(inputArray: ByteArray, + inputVectorSize: Int, + sizeX: Int, + sizeY: Int, + outputVectorSize: Int, + matrix: FloatArray, addVector: FloatArray, + restriction: Range2d?): ByteArray { + require (matrix.size == 16) { "RenderScriptToolkit colorMatrix. Matrix should have 16 values. ${matrix.size} provided." } + + val input = Vector2dArray(inputArray.asUByteArray(), inputVectorSize, sizeX, sizeY) + val outputArray = ByteArray(sizeX * sizeY * paddedSize(outputVectorSize)) + val output = Vector2dArray(outputArray.asUByteArray(), outputVectorSize, sizeX, sizeY) + + output.forEach (restriction) { x, y -> + val inUByteValue = input[x, y] + val inFloatValue = FloatArray(4) { if (it >= inputVectorSize) 0f else byteToUnitFloat(inUByteValue[it]) } + val outFloatValue = multiplyAndAdd(matrix, inFloatValue, addVector) + val outUByteValue = UByteArray(paddedSize(output.vectorSize)) { unitFloatClampedToUByte(outFloatValue[it]) } + output[x, y] = outUByteValue + } + return outputArray +} + +private fun multiplyAndAdd(matrix: FloatArray, inVector: FloatArray, addVector: FloatArray): FloatArray { + // In RenderScript, matrix were set in column major format + val result = addVector.clone() + for (i in 0..3) { + for (j in 0..3) { + result[i] += matrix[j * 4 + i] * inVector[j] + } + } + return result +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceConvolve.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceConvolve.kt new file mode 100644 index 0000000..4c87099 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceConvolve.kt @@ -0,0 +1,68 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.Range2d + +/** + * Reference implementation of a Convolve operation. + */ +@ExperimentalUnsignedTypes +fun referenceConvolve( + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + coefficients: FloatArray, + restriction: Range2d? +): ByteArray { + val input = Vector2dArray(inputArray.asUByteArray(), vectorSize, sizeX, sizeY) + val radius = when (coefficients.size) { + 9 -> 1 + 25 -> 2 + else -> { + throw IllegalArgumentException("RenderScriptToolkit Convolve. Only 3x3 and 5x5 convolutions are supported. ${coefficients.size} coefficients provided.") + } + } + + input.clipReadToRange = true + val output = input.createSameSized() + input.forEach(restriction) { x, y -> + output[x, y] = convolveOne(input, x, y, coefficients, radius) + } + return output.values.asByteArray() +} + +@ExperimentalUnsignedTypes +private fun convolveOne( + inputAlloc: Vector2dArray, + x: Int, + y: Int, + coefficients: FloatArray, + radius: Int +): UByteArray { + var sum = FloatArray(paddedSize(inputAlloc.vectorSize)) + var coefficientIndex = 0 + for (deltaY in -radius..radius) { + for (deltaX in -radius..radius) { + val inputVector = inputAlloc[x + deltaX, y + deltaY] + sum += inputVector.toFloatArray() * coefficients[coefficientIndex] + coefficientIndex++ + } + } + return sum.clampToUByte() +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceHistogram.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceHistogram.kt new file mode 100644 index 0000000..0e26457 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceHistogram.kt @@ -0,0 +1,98 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.Range2d + +/** + * Reference implementation of a Histogram operation. + * + * Return an array of 4 * 256 ints. + * Position 0 is the number of R with a value of 0, + * Position 1 is the number of G with a value of 0, + * Position 2 is the number of B with a value of 0, + * Position 3 is the number of A with a value of 0, + * Position 4 is the number of R with a value of 1, + * etc. +*/ +@ExperimentalUnsignedTypes +fun referenceHistogram( + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + restriction: Range2d? +): IntArray { + val input = Vector2dArray(inputArray.asUByteArray(), vectorSize, sizeX, sizeY) + + val counts = IntArray(paddedSize(input.vectorSize) * 256) + input.forEach(restriction) { x, y -> + val value = input[x, y] + for (i in 0 until vectorSize) { + counts[value[i].toInt() * paddedSize(input.vectorSize) + i]++ + } + } + return counts +} + +/** + * Reference implementation of a HistogramDot operation. + * + * Each RGBA input value is dot-multiplied first by the specified coefficients. + * The resulting value is converted to an integer and used for the histogram. + */ +@ExperimentalUnsignedTypes +fun referenceHistogramDot( + inputArray: ByteArray, + vectorSize: Int, + sizeX: Int, + sizeY: Int, + coefficients: FloatArray?, + restriction: Range2d? +): IntArray { + val floatCoefficients = coefficients ?: floatArrayOf(0.299f, 0.587f, 0.114f, 0f) + val input = Vector2dArray(inputArray.asUByteArray(), vectorSize, sizeX, sizeY) + var coefficientSum = 0f + for (c in floatCoefficients) { + require (c >= 0) { + "RenderScriptToolkit histogramDot. Coefficients must be positive. $c provided." + } + coefficientSum += c + } + require(coefficientSum <= 1f) { "RenderScriptToolkit histogramDot. Coefficients should " + + "add to 1.0 or less. $coefficientSum provided." } + + // Compute integer + val intCoefficients = IntArray(input.vectorSize) { (floatCoefficients[it] * 256f + 0.5f).toInt() } + + val counts = IntArray(256) + input.forEach(restriction) { x, y -> + val value = input[x, y] + // While we could do the computation using floats, we won't get the same results as + // the existing intrinsics. + var sum = 0 + // We don't use value.indices because we want to accumulate only 3 values, in the case + // of vectorSize == 3. + for (i in 0 until vectorSize) { + sum += intCoefficients[i] * value[i].toInt() + } + // Round up and normalize + val index = (sum + 0x7f) shr 8 + counts[index]++ + } + return counts +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceLut.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceLut.kt new file mode 100644 index 0000000..3d42739 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceLut.kt @@ -0,0 +1,48 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.LookupTable +import com.google.android.renderscript.Range2d + +/** + * Reference implementation of a LookUpTable operation. + */ +@ExperimentalUnsignedTypes +fun referenceLut( + inputArray: ByteArray, + sizeX: Int, + sizeY: Int, + table: LookupTable, + restriction: Range2d? +): ByteArray { + val input = Vector2dArray(inputArray.asUByteArray(), 4, sizeX, sizeY) + + val output = input.createSameSized() + input.forEach(restriction) { x, y -> + val oldValue = input[x, y] + val newValue = byteArrayOf( + table.red[oldValue[0].toInt()], + table.green[oldValue[1].toInt()], + table.blue[oldValue[2].toInt()], + table.alpha[oldValue[3].toInt()] + ) + output[x, y] = newValue.asUByteArray() + } + return output.values.asByteArray() +} + diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceLut3d.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceLut3d.kt new file mode 100644 index 0000000..529cb77 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceLut3d.kt @@ -0,0 +1,74 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.Range2d +import com.google.android.renderscript.Rgba3dArray + +/** + * Reference implementation of a 3D LookUpTable operation. + */ +@ExperimentalUnsignedTypes +fun referenceLut3d( + inputArray: ByteArray, + sizeX: Int, + sizeY: Int, + cube: Rgba3dArray, + restriction: Range2d? +): ByteArray { + val input = Vector2dArray(inputArray.asUByteArray(), 4, sizeX, sizeY) + val output = input.createSameSized() + input.forEach(restriction) { x, y -> + output[x, y] = lookup(input[x, y], cube) + } + return output.values.asByteArray() +} + +@ExperimentalUnsignedTypes +private fun lookup(input: UByteArray, cube: Rgba3dArray): UByteArray { + // Calculate the two points at opposite edges of the size 1 + // cube that contains our point. + val maxIndex = Int4(cube.sizeX - 1, cube.sizeY - 1, cube.sizeZ - 1, 0) + val baseCoordinate: Float4 = input.toFloat4() * maxIndex.toFloat4() / 255f + val point1: Int4 = baseCoordinate.intFloor() + val point2: Int4 = min(point1 + 1, maxIndex) + val fractionAwayFromPoint1: Float4 = baseCoordinate - point1.toFloat4() + + // Get the RGBA values at each of the four corners of the size 1 cube. + val v000 = cube[point1.x, point1.y, point1.z].toFloat4() + val v100 = cube[point2.x, point1.y, point1.z].toFloat4() + val v010 = cube[point1.x, point2.y, point1.z].toFloat4() + val v110 = cube[point2.x, point2.y, point1.z].toFloat4() + val v001 = cube[point1.x, point1.y, point2.z].toFloat4() + val v101 = cube[point2.x, point1.y, point2.z].toFloat4() + val v011 = cube[point1.x, point2.y, point2.z].toFloat4() + val v111 = cube[point2.x, point2.y, point2.z].toFloat4() + + // Do the linear mixing of these eight values. + val yz00 = mix(v000, v100, fractionAwayFromPoint1.x) + val yz10 = mix(v010, v110, fractionAwayFromPoint1.x) + val yz01 = mix(v001, v101, fractionAwayFromPoint1.x) + val yz11 = mix(v011, v111, fractionAwayFromPoint1.x) + + val z0 = mix(yz00, yz10, fractionAwayFromPoint1.y) + val z1 = mix(yz01, yz11, fractionAwayFromPoint1.y) + + val v = mix(z0, z1, fractionAwayFromPoint1.z) + + // Preserve the alpha of the original value + return ubyteArrayOf(v.x.clampToUByte(), v.y.clampToUByte(), v.z.clampToUByte(), input[3]) +} diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceResize.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceResize.kt new file mode 100644 index 0000000..06d3653 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceResize.kt @@ -0,0 +1,157 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.Range2d +import kotlin.math.floor +import kotlin.math.max + +var trace = false + +/** + * Reference implementation of a Resize operation. + */ +@ExperimentalUnsignedTypes +fun referenceResize(inputArray: ByteArray, + vectorSize: Int, + inSizeX: Int, + inSizeY: Int, + outSizeX: Int, outSizeY: Int, + restriction: Range2d?): ByteArray { + val input = Vector2dArray(inputArray.asUByteArray(), vectorSize, inSizeX, inSizeY) + val scaleX: Float = input.sizeX.toFloat() / outSizeX.toFloat() + val scaleY: Float = input.sizeY.toFloat() / outSizeY.toFloat() + val outArray = UByteArray(outSizeX * outSizeY * paddedSize(input.vectorSize)) + val out = Vector2dArray(outArray, input.vectorSize, outSizeX, outSizeY) + out.forEach (restriction) { x, y -> + if (x == 1827 && y == 46) { + println("Found it") + trace = true + } + val o = bicubicU4(x, y, input, scaleX, scaleY) + out[x, y] = o.clampToUByte() + } + return out.values.asByteArray() +} + +private fun cubicInterpolateF(p0: FloatArray, p1: FloatArray, p2: FloatArray, p3: FloatArray, + x: Float): FloatArray { + return p1 + (p2 - p0 + (p0 * 2f - p1 * 5f + p2 * 4f - p3 + + ((p1 - p2) * 3f + p3 - p0) * x) * x) * x * 0.5f +} + +@ExperimentalUnsignedTypes +private fun bicubicU4(x: Int, y: Int, gIn: Vector2dArray, scaleX: Float, scaleY: Float): FloatArray { + var xf: Float = (x + 0.5f) * scaleX - 0.5f + var yf: Float = (y + 0.5f) * scaleY - 0.5f + + val startX: Int = floor(xf - 1).toInt() + val startY: Int = floor(yf - 1).toInt() + xf -= floor(xf) + yf -= floor(yf) + val maxX: Int = gIn.sizeX - 1 + val maxY: Int = gIn.sizeY - 1 + + val xs0: Int = max(0, startX + 0) + val xs1: Int = max(0, startX + 1) + val xs2: Int = kotlin.math.min(maxX, startX + 2) + val xs3: Int = kotlin.math.min(maxX, startX + 3) + + val ys0: Int = max(0, startY + 0) + val ys1: Int = max(0, startY + 1) + val ys2: Int = kotlin.math.min(maxY, startY + 2) + val ys3: Int = kotlin.math.min(maxY, startY + 3) + + val p00 = gIn[xs0, ys0].toFloatArray() + val p01 = gIn[xs1, ys0].toFloatArray() + val p02 = gIn[xs2, ys0].toFloatArray() + val p03 = gIn[xs3, ys0].toFloatArray() + val p0 = cubicInterpolateF(p00, p01, p02, p03, xf) + + val p10 = gIn[xs0, ys1].toFloatArray() + val p11 = gIn[xs1, ys1].toFloatArray() + val p12 = gIn[xs2, ys1].toFloatArray() + val p13 = gIn[xs3, ys1].toFloatArray() + val p1 = cubicInterpolateF(p10, p11, p12, p13, xf) + + val p20 = gIn[xs0, ys2].toFloatArray() + val p21 = gIn[xs1, ys2].toFloatArray() + val p22 = gIn[xs2, ys2].toFloatArray() + val p23 = gIn[xs3, ys2].toFloatArray() + val p2 = cubicInterpolateF(p20, p21, p22, p23, xf) + + val p30 = gIn[xs0, ys3].toFloatArray() + val p31 = gIn[xs1, ys3].toFloatArray() + val p32 = gIn[xs2, ys3].toFloatArray() + val p33 = gIn[xs3, ys3].toFloatArray() + val p3 = cubicInterpolateF(p30, p31, p32, p33, xf) + + return cubicInterpolateF(p0, p1, p2, p3, yf) +} + + +/* To be used if we implement Floats +private fun bicubic_F4(x: Int, y: Int, gin: ByteArray, sizeX: Int, sizeY: Int, scaleX: Float, scaleY: Float): Float4 { + var xf: Float = (x + 0.5f) * scaleX - 0.5f + var yf: Float = (y + 0.5f) * scaleY - 0.5f + + val startX: Int = floor(xf - 1).toInt() + val startY: Int = floor(yf - 1).toInt() + xf = xf - floor(xf) + yf = yf - floor(yf) + val maxX: Int = sizeX - 1 + val maxY: Int = sizeY - 1 + + val xs0: Int = max(0, startX + 0) + val xs1: Int = max(0, startX + 1) + val xs2: Int = min(maxX, startX + 2) + val xs3: Int = min(maxX, startX + 3) + + val ys0: Int = max(0, startY + 0) + val ys1: Int = max(0, startY + 1) + val ys2: Int = min(maxY, startY + 2) + val ys3: Int = min(maxY, startY + 3) + + val p00: Float4 = rsGetElementAt_Float4(gIn, xs0, ys0) + val p01: Float4 = rsGetElementAt_Float4(gIn, xs1, ys0) + val p02: Float4 = rsGetElementAt_Float4(gIn, xs2, ys0) + val p03: Float4 = rsGetElementAt_Float4(gIn, xs3, ys0) + val p0: Float4 = cubicInterpolate_F4(p00, p01, p02, p03, xf) + + val p10: Float4 = rsGetElementAt_Float4(gIn, xs0, ys1) + val p11: Float4 = rsGetElementAt_Float4(gIn, xs1, ys1) + val p12: Float4 = rsGetElementAt_Float4(gIn, xs2, ys1) + val p13: Float4 = rsGetElementAt_Float4(gIn, xs3, ys1) + val p1: Float4 = cubicInterpolate_F4(p10, p11, p12, p13, xf) + + val p20: Float4 = rsGetElementAt_Float4(gIn, xs0, ys2) + val p21: Float4 = rsGetElementAt_Float4(gIn, xs1, ys2) + val p22: Float4 = rsGetElementAt_Float4(gIn, xs2, ys2) + val p23: Float4 = rsGetElementAt_Float4(gIn, xs3, ys2) + val p2: Float4 = cubicInterpolate_F4(p20, p21, p22, p23, xf) + + val p30: Float4 = rsGetElementAt_Float4(gIn, xs0, ys3) + val p31: Float4 = rsGetElementAt_Float4(gIn, xs1, ys3) + val p32: Float4 = rsGetElementAt_Float4(gIn, xs2, ys3) + val p33: Float4 = rsGetElementAt_Float4(gIn, xs3, ys3) + val p3: Float4 = cubicInterpolate_F4(p30, p31, p32, p33, xf) + + val p: Float4 = cubicInterpolate_F4(p0, p1, p2, p3, yf) + + return p +} +*/ diff --git a/test-app/src/main/java/com/google/android/renderscript_test/ReferenceYuvToRgb.kt b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceYuvToRgb.kt new file mode 100644 index 0000000..aa58516 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/ReferenceYuvToRgb.kt @@ -0,0 +1,112 @@ +/* + * 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.google.android.renderscript_test + +import com.google.android.renderscript.YuvFormat +import java.lang.IllegalArgumentException + +/** + * Reference implementation of a YUV to RGB operation. + */ +@ExperimentalUnsignedTypes +fun referenceYuvToRgb(inputSignedArray: ByteArray, sizeX: Int, sizeY: Int, format: YuvFormat): ByteArray { + require(sizeX % 2 == 0) { "The width of the input should be even."} + val inputArray = inputSignedArray.asUByteArray() + + val outputArray = ByteArray(sizeX * sizeY * 4) + val output = Vector2dArray(outputArray.asUByteArray(), 4, sizeX, sizeY) + + when (format) { + YuvFormat.NV21 -> { + val startY = 0 + val startU = sizeX * sizeY + 1 + val startV = sizeX * sizeY + + for (y in 0 until sizeY) { + for (x in 0 until sizeX) { + val offsetY = y * sizeX + x + val offsetU = ((y shr 1) * sizeX + (x shr 1) * 2) + val offsetV = ((y shr 1) * sizeX + (x shr 1) * 2) + output[x, y] = yuvToRGBA4( + inputArray[startY + offsetY], + inputArray[startU + offsetU], + inputArray[startV + offsetV] + ) + } + } + } + + YuvFormat.YV12 -> { + /* According to https://developer.android.com/reference/kotlin/android/graphics/ImageFormat#yv12, + * strideX and strideUV should be aligned to 16 byte boundaries. If we do this, we + * won't get the same results as RenderScript. + * + * We may want to test & require that sizeX is a multiple of 16/32. + */ + val strideX = roundUpTo16(sizeX) // sizeX // + val strideUV = roundUpTo16(strideX / 2) // strideX / 2 // + val startY = 0 + val startU = strideX * sizeY + val startV = startU + strideUV * sizeY / 2 + + for (y in 0 until sizeY) { + for (x in 0 until sizeX) { + val offsetY = y * sizeX + x + val offsetUV = (y shr 1) * strideUV + (x shr 1) + output[x, y] = yuvToRGBA4( + inputArray[startY + offsetY], + inputArray[startU + offsetUV], + inputArray[startV + offsetUV], + ) + } + } + } + else -> throw IllegalArgumentException("Unknown YUV format $format") + } + + return outputArray +} + +@ExperimentalUnsignedTypes +private fun yuvToRGBA4(y: UByte, u: UByte, v: UByte): UByteArray { + val intY = y.toInt() - 16 + val intU = u.toInt() - 128 + val intV = v.toInt() - 128 + val p = intArrayOf( + intY * 298 + intV * 409 + 128 shr 8, + intY * 298 - intU * 100 - intV * 208 + 128 shr 8, + intY * 298 + intU * 516 + 128 shr 8, + 255 + ) + return UByteArray(4) { p[it].clampToUByte() } +} + +/* To be used if we support Float +private fun yuvToRGBA_f4(y: UByte, u: UByte, v: UByte): UByteArray { + val yuv_U_values = floatArrayOf(0f, -0.392f * 0.003921569f, 2.02f * 0.003921569f, 0f) + val yuv_V_values = floatArrayOf(1.603f * 0.003921569f, -0.815f * 0.003921569f, 0f, 0f) + + var color = FloatArray(4) {y.toFloat() * 0.003921569f} + val fU = FloatArray(4) {u.toFloat() - 128f} + val fV = FloatArray(4) {v.toFloat() - 128f} + + color += fU * yuv_U_values; + color += fV * yuv_V_values; + //color = clamp(color, 0.f, 1.f); + return UByteArray(4) { unitFloatClampedToUByte(color[it]) } +} +*/ diff --git a/test-app/src/main/java/com/google/android/renderscript_test/TimingTracker.kt b/test-app/src/main/java/com/google/android/renderscript_test/TimingTracker.kt new file mode 100644 index 0000000..0368368 --- /dev/null +++ b/test-app/src/main/java/com/google/android/renderscript_test/TimingTracker.kt @@ -0,0 +1,64 @@ +/* + * 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.google.android.renderscript_test + +class TimingTracker( + private val numberOfIterations: Int = 1, + private var numberOfIterationsToIgnore: Int = 0 +) { + init { + require(numberOfIterations > numberOfIterationsToIgnore) + } + private val timings = mutableMapOf<String, IntArray>() + private var currentIteration: Int = 0 + fun nextIteration() { + currentIteration++ + } + fun <T> measure(name: String, workToTime: () -> T): T { + val start = System.nanoTime() + val t = workToTime() + if (currentIteration >= numberOfIterationsToIgnore) { + val end = System.nanoTime() + val deltaInMicroseconds: Int = ((end - start) / 1000).toInt() + val timing = timings.getOrPut(name) { + IntArray(numberOfIterations - numberOfIterationsToIgnore) + } + timing[currentIteration - numberOfIterationsToIgnore] += deltaInMicroseconds + } + return t + } + fun report(): String { + var minimum: Int = Int.MAX_VALUE + for (timing in timings.values) { + val m = timing.minOrNull() + if (m != null && m < minimum) minimum = m + } + + println(timings.map { (name, timing) -> name + ": " + timing.minOrNull() }.joinToString(separator = "\n")) + + var minimums = + timings.map { (name, timing) -> name + ": " + timing.minOrNull() }.joinToString() + var all = + timings.map { (name, timing) -> name + ": " + timing.joinToString() }.joinToString() + var normalized = + timings.map { (name, timing) -> name + ": " + timing.joinToString { "%.2f".format(it.toFloat() / minimum) } } + .joinToString() + + return "Minimums: $minimums\n\nAll: $all\n\nNormalized: $normalized\n" + } +} + |