diff options
author | 2025-02-28 10:07:49 -0800 | |
---|---|---|
committer | 2025-03-24 09:55:51 -0700 | |
commit | 3e1b42cfecadcdf49439a62473aa73df7d9cf90d (patch) | |
tree | bd48c02d1c22606c7864fa88376be3f44693ec46 | |
parent | f10d290cc3b572c92159f7ebfc193a1e87b0f413 (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
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 |