Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 39f4b48a authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[SilkFX] The very first version of lut demo app" into main

parents ec460d2b 3e1b42cf
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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>
+54 −0
Original line number Diff line number Diff line
<?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
+7 −0
Original line number Diff line number Diff line
@@ -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>
+3 −1
Original line number Diff line number Diff line
@@ -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),
+206 −0
Original line number Diff line number Diff line
/*
 * 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