summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Sally Qi <sallyqi@google.com> 2025-02-28 10:07:49 -0800
committer Sally Qi <sallyqi@google.com> 2025-03-24 09:55:51 -0700
commit3e1b42cfecadcdf49439a62473aa73df7d9cf90d (patch)
treebd48c02d1c22606c7864fa88376be3f44693ec46
parentf10d290cc3b572c92159f7ebfc193a1e87b0f413 (diff)
[SilkFX] The very first version of lut demo app
Relnote: Now only support to play with Lut on the image. We provide the pre-defined Lut, and users can check the button to see the results. Bug: 391680040 Test: play with SilkFX Flag: EXEMPT demo app development Change-Id: I0f82126c1fd7cbb474e6c619709de641dfa636b5
-rw-r--r--tests/graphics/SilkFX/AndroidManifest.xml5
-rw-r--r--tests/graphics/SilkFX/res/layout/lut_test.xml54
-rw-r--r--tests/graphics/SilkFX/res/values/style.xml7
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt4
-rw-r--r--tests/graphics/SilkFX/src/com/android/test/silkfx/hdr/LutTestActivity.kt206
5 files changed, 275 insertions, 1 deletions
diff --git a/tests/graphics/SilkFX/AndroidManifest.xml b/tests/graphics/SilkFX/AndroidManifest.xml
index c293589bdbaf..be637567b669 100644
--- a/tests/graphics/SilkFX/AndroidManifest.xml
+++ b/tests/graphics/SilkFX/AndroidManifest.xml
@@ -70,5 +70,10 @@
</intent-filter>
</activity>
+ <activity android:name=".hdr.LutTestActivity"
+ android:theme="@style/Theme.LutTestTheme"
+ android:label="Lut Test Examples"
+ android:exported="true"/>
+
</application>
</manifest>
diff --git a/tests/graphics/SilkFX/res/layout/lut_test.xml b/tests/graphics/SilkFX/res/layout/lut_test.xml
new file mode 100644
index 000000000000..f550f397cfa5
--- /dev/null
+++ b/tests/graphics/SilkFX/res/layout/lut_test.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2025 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.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <SurfaceView
+ android:id="@+id/surfaceView"
+ android:layout_width="match_parent"
+ android:layout_height="300dp"
+ android:layout_weight="1"
+ android:layout_marginTop="16dp" />
+
+ <RadioGroup android:id="@+id/lut_option"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <RadioButton android:id="@+id/lut_1d"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="1D Lut" />
+
+ <RadioButton android:id="@+id/lut_3d"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="3D Lut" />
+
+ <RadioButton android:id="@+id/no_lut"
+ android:layout_width="wrap_content"
+ android:layout_weight="1"
+ android:layout_height="wrap_content"
+ android:text="Original image" />
+ </RadioGroup>
+
+</LinearLayout> \ No newline at end of file
diff --git a/tests/graphics/SilkFX/res/values/style.xml b/tests/graphics/SilkFX/res/values/style.xml
index 75506978024b..d162bd0926d3 100644
--- a/tests/graphics/SilkFX/res/values/style.xml
+++ b/tests/graphics/SilkFX/res/values/style.xml
@@ -24,6 +24,13 @@
<item name="buttonStyle">@style/AppTheme.Button</item>
<item name="colorAccent">#bbffffff</item>
</style>
+ <style name="Theme.LutTestTheme" parent="Theme.AppCompat.Dialog">
+ <item name="android:windowBlurBehindEnabled">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowElevation">0dp</item>
+ <item name="buttonStyle">@style/AppTheme.Button</item>
+ <item name="colorAccent">#bbffffff</item>
+ </style>
<style name="AppTheme.Button" parent="Widget.AppCompat.Button">
<item name="android:textColor">#ffffffff</item>
</style>
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
index ad7cde44bb35..be499b6ce26d 100644
--- a/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/Main.kt
@@ -30,6 +30,7 @@ import com.android.test.silkfx.app.EXTRA_COMMON_CONTROLS
import com.android.test.silkfx.app.EXTRA_LAYOUT
import com.android.test.silkfx.app.EXTRA_TITLE
import com.android.test.silkfx.hdr.GlowActivity
+import com.android.test.silkfx.hdr.LutTestActivity
import com.android.test.silkfx.materials.GlassActivity
import com.android.test.silkfx.materials.BackgroundBlurActivity
import kotlin.reflect.KClass
@@ -57,7 +58,8 @@ private val AllDemos = listOf(
Demo("Gainmap Image", R.layout.gainmap_image),
Demo("Gainmap Decode Test", R.layout.gainmap_decode_test, commonControls = false),
Demo("Gainmap Transform Test", R.layout.gainmap_transform_test,
- commonControls = false)
+ commonControls = false),
+ Demo("Lut Test", LutTestActivity::class)
)),
DemoGroup("Materials", listOf(
Demo("Glass", GlassActivity::class),
diff --git a/tests/graphics/SilkFX/src/com/android/test/silkfx/hdr/LutTestActivity.kt b/tests/graphics/SilkFX/src/com/android/test/silkfx/hdr/LutTestActivity.kt
new file mode 100644
index 000000000000..319f58cbfdcc
--- /dev/null
+++ b/tests/graphics/SilkFX/src/com/android/test/silkfx/hdr/LutTestActivity.kt
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2025 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.android.test.silkfx.hdr
+
+import android.graphics.Bitmap
+import android.graphics.ColorSpace
+import android.graphics.ImageDecoder
+import android.graphics.Matrix
+import android.graphics.HardwareBufferRenderer
+import android.graphics.RenderNode
+import android.hardware.DisplayLuts
+import android.hardware.HardwareBuffer
+import android.hardware.LutProperties
+import android.os.Bundle
+import android.view.SurfaceControl
+import android.view.SurfaceView
+import android.view.SurfaceHolder
+import androidx.appcompat.app.AppCompatActivity
+import android.widget.RadioGroup
+import android.util.Log
+import com.android.test.silkfx.R
+import java.io.IOException
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+
+class LutTestActivity : AppCompatActivity() {
+
+ private lateinit var surfaceView: SurfaceView
+ private var surfaceControl: SurfaceControl? = null
+ private var currentBitmap: Bitmap? = null
+ private var renderNode = RenderNode("LutRenderNode")
+ private var currentLutType: Int = R.id.no_lut // Store current LUT type
+ private val TAG = "LutTestActivity"
+ private val renderExecutor = Executors.newSingleThreadExecutor()
+
+ /** Called when the activity is first created. */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.lut_test)
+
+ surfaceView = findViewById(R.id.surfaceView)
+
+ surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ createChildSurfaceControl()
+ loadImage("gainmaps/sunflower.jpg", holder)
+ currentBitmap?.let {
+ createAndRenderHardwareBuffer(holder, it, getCurrentLut())
+ }
+ }
+
+ override fun surfaceChanged(
+ holder: SurfaceHolder,
+ format: Int,
+ width: Int,
+ height: Int
+ ) {
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ }
+ })
+
+ var lutOption = findViewById<RadioGroup>(R.id.lut_option)
+ // handle RadioGroup selection changes
+ lutOption.setOnCheckedChangeListener(
+ RadioGroup.OnCheckedChangeListener { _, id ->
+ currentLutType = id
+ if (surfaceControl != null) {
+ applyCurrentLut()
+ }
+ }
+ )
+ }
+
+ private fun applyCurrentLut() {
+ when (currentLutType) {
+ R.id.lut_1d -> {
+ currentBitmap?.let {
+ createAndRenderHardwareBuffer(surfaceView.holder, it, get1DLut())
+ }
+ }
+ R.id.lut_3d -> {
+ currentBitmap?.let {
+ createAndRenderHardwareBuffer(surfaceView.holder, it, get3DLut())
+ }
+ }
+ R.id.no_lut -> {
+ currentBitmap?.let {
+ createAndRenderHardwareBuffer(surfaceView.holder, it, null)
+ }
+ }
+ }
+ }
+
+ private fun getCurrentLut(): DisplayLuts {
+ when (currentLutType) {
+ R.id.lut_1d -> return get1DLut()
+ R.id.lut_3d -> return get3DLut()
+ R.id.no_lut -> return DisplayLuts()
+ }
+ return DisplayLuts()
+ }
+
+ private fun get3DLut(): DisplayLuts {
+ var luts = DisplayLuts()
+ val entry = DisplayLuts.Entry(
+ floatArrayOf(0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f,
+ 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f,
+ 1f, 1f, 1f, 1f, 1f, 1f, 1f, 1f),
+ LutProperties.THREE_DIMENSION,
+ LutProperties.SAMPLING_KEY_RGB
+ )
+ luts.set(entry)
+ return luts
+ }
+
+ private fun get1DLut(): DisplayLuts {
+ var luts = DisplayLuts()
+ val entry = DisplayLuts.Entry(
+ floatArrayOf(0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f, 0.1f),
+ LutProperties.ONE_DIMENSION,
+ LutProperties.SAMPLING_KEY_RGB
+ )
+ luts.set(entry)
+ return luts
+ }
+
+ private fun createChildSurfaceControl() {
+ surfaceView.surfaceControl?.let { parentSC ->
+ surfaceControl = SurfaceControl.Builder()
+ .setParent(parentSC)
+ .setBufferSize(surfaceView.width, surfaceView.height)
+ .setName("LutTestSurfaceControl")
+ .setHidden(false)
+ .build()
+ }
+ }
+
+ private fun loadImage(assetPath: String, holder: SurfaceHolder) {
+ try {
+ val source = ImageDecoder.createSource(assets, assetPath)
+ currentBitmap = ImageDecoder.decodeBitmap(source) { decoder, info, source ->
+ decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "Error loading image: ${e.message}")
+ e.printStackTrace()
+ }
+ }
+
+ private fun createAndRenderHardwareBuffer(holder: SurfaceHolder, bitmap: Bitmap, luts: DisplayLuts?) {
+ val imageWidth = bitmap.width
+ val imageHeight = bitmap.height
+
+ val buffer = HardwareBuffer.create(
+ imageWidth,
+ imageHeight,
+ HardwareBuffer.RGBA_8888,
+ 1, // layers
+ HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+ )
+ val renderer = HardwareBufferRenderer(buffer)
+ renderNode.setPosition(0, 0, buffer.width, buffer.height)
+ renderer.setContentRoot(renderNode)
+
+ val canvas = renderNode.beginRecording()
+
+ // calculate the scale to match the screen
+ val surfaceWidth = holder.surfaceFrame.width()
+ val surfaceHeight = holder.surfaceFrame.height()
+
+ val scaleX = surfaceWidth.toFloat() / imageWidth.toFloat()
+ val scaleY = surfaceHeight.toFloat() / imageHeight.toFloat()
+ val scale = minOf(scaleX, scaleY)
+
+ val matrix = Matrix().apply{ postScale(scale, scale) }
+ canvas.drawBitmap(bitmap, matrix, null)
+ renderNode.endRecording()
+
+ val colorSpace = ColorSpace.get(ColorSpace.Named.BT2020_HLG)
+ val latch = CountDownLatch(1)
+ renderer.obtainRenderRequest().setColorSpace(colorSpace).draw(renderExecutor) { renderResult ->
+ surfaceControl?.let {
+ SurfaceControl.Transaction().setBuffer(it, buffer, renderResult.fence).setLuts(it, luts).apply()
+ }
+ latch.countDown()
+ }
+ latch.await() // Wait for the fence to complete.
+ buffer.close()
+ }
+} \ No newline at end of file