• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.test.silkfx.hdr
18 
19 import android.graphics.Gainmap
20 import android.view.Gravity
21 import android.view.LayoutInflater
22 import android.view.View
23 import android.view.ViewGroup
24 import android.widget.Button
25 import android.widget.PopupWindow
26 import android.widget.SeekBar
27 import android.widget.TextView
28 import com.android.test.silkfx.R
29 
30 data class GainmapMetadata(
31         var ratioMin: Float,
32         var ratioMax: Float,
33         var capacityMin: Float,
34         var capacityMax: Float,
35         var gamma: Float,
36         var offsetSdr: Float,
37         var offsetHdr: Float
38 )
39 
40 /**
41  * Note: This can only handle single-channel gainmaps nicely. It will force all 3-channel
42  * metadata to have the same value single value and is not intended to be a robust demonstration
43  * of gainmap metadata editing
44  */
45 class GainmapMetadataEditor(val parent: ViewGroup, val renderView: View) {
46     private lateinit var gainmap: Gainmap
47 
48     private var metadataPopup: PopupWindow? = null
49 
50     private var originalMetadata: GainmapMetadata = GainmapMetadata(
51             1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f)
52     private var currentMetadata: GainmapMetadata = originalMetadata.copy()
53 
54     private val maxProgress = 100.0f
55 
56     private val minRatioMin = .001f
57     private val maxRatioMin = 1.0f
58     private val minRatioMax = 1.0f
59     private val maxRatioMax = 16.0f
60     private val minCapacityMin = 1.0f
61     private val maxCapacityMin = maxRatioMax
62     private val minCapacityMax = 1.001f
63     private val maxCapacityMax = maxRatioMax
64     private val minGamma = 0.1f
65     private val maxGamma = 3.0f
66     // Min and max offsets are 0.0 and 1.0 respectively
67 
setGainmapnull68     fun setGainmap(newGainmap: Gainmap) {
69         gainmap = newGainmap
70         originalMetadata = GainmapMetadata(gainmap.getRatioMin()[0],
71                 gainmap.getRatioMax()[0], gainmap.getMinDisplayRatioForHdrTransition(),
72                 gainmap.getDisplayRatioForFullHdr(), gainmap.getGamma()[0],
73                 gainmap.getEpsilonSdr()[0], gainmap.getEpsilonHdr()[0])
74         currentMetadata = originalMetadata.copy()
75     }
76 
editedGainmapnull77     fun editedGainmap(): Gainmap {
78         applyMetadata(currentMetadata)
79         return gainmap
80     }
81 
closeEditornull82     fun closeEditor() {
83         metadataPopup?.let {
84             it.dismiss()
85             metadataPopup = null
86         }
87     }
88 
openEditornull89     fun openEditor() {
90         if (metadataPopup != null) return
91 
92         val view = LayoutInflater.from(parent.getContext()).inflate(R.layout.gainmap_metadata, null)
93 
94         metadataPopup = PopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT,
95                 ViewGroup.LayoutParams.WRAP_CONTENT)
96         metadataPopup!!.showAtLocation(view, Gravity.CENTER, 0, 0)
97 
98         (view.getParent() as ViewGroup).removeView(view)
99         parent.addView(view)
100 
101         view.requireViewById<Button>(R.id.gainmap_metadata_done).setOnClickListener {
102             closeEditor()
103         }
104 
105         view.requireViewById<Button>(R.id.gainmap_metadata_reset).setOnClickListener {
106             resetGainmapMetadata()
107         }
108 
109         updateMetadataUi()
110 
111         val gainmapMinSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin)
112         val gainmapMaxSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax)
113         val capacityMinSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymin)
114         val capacityMaxSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymax)
115         val gammaSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_gamma)
116         val offsetSdrSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
117         val offsetHdrSeek = view.requireViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
118         arrayOf(gainmapMinSeek, gainmapMaxSeek, capacityMinSeek, capacityMaxSeek, gammaSeek,
119                 offsetSdrSeek, offsetHdrSeek).forEach {
120             it.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener{
121                 override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
122                     if (!fromUser) return
123                     val normalized = progress.toFloat() / maxProgress
124                     when (seekBar) {
125                         gainmapMinSeek -> updateGainmapMin(normalized)
126                         gainmapMaxSeek -> updateGainmapMax(normalized)
127                         capacityMinSeek -> updateCapacityMin(normalized)
128                         capacityMaxSeek -> updateCapacityMax(normalized)
129                         gammaSeek -> updateGamma(normalized)
130                         offsetSdrSeek -> updateOffsetSdr(normalized)
131                         offsetHdrSeek -> updateOffsetHdr(normalized)
132                     }
133                 }
134 
135                 override fun onStartTrackingTouch(seekBar: SeekBar) {}
136                 override fun onStopTrackingTouch(seekBar: SeekBar) {}
137             })
138         }
139     }
140 
updateMetadataUinull141     private fun updateMetadataUi() {
142         val gainmapMinSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmin)
143         val gainmapMaxSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gainmapmax)
144         val capacityMinSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymin)
145         val capacityMaxSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_capacitymax)
146         val gammaSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_gamma)
147         val offsetSdrSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_offsetsdr)
148         val offsetHdrSeek = parent.requireViewById<SeekBar>(R.id.gainmap_metadata_offsethdr)
149 
150         gainmapMinSeek.setProgress(
151                 ((currentMetadata.ratioMin - minRatioMin) / maxRatioMin * maxProgress).toInt())
152         gainmapMaxSeek.setProgress(
153                 ((currentMetadata.ratioMax - minRatioMax) / maxRatioMax * maxProgress).toInt())
154         capacityMinSeek.setProgress(
155                 ((currentMetadata.capacityMin - minCapacityMin) / maxCapacityMin * maxProgress).toInt())
156         capacityMaxSeek.setProgress(
157                 ((currentMetadata.capacityMax - minCapacityMax) / maxCapacityMax * maxProgress).toInt())
158         gammaSeek.setProgress(
159                 ((currentMetadata.gamma - minGamma) / maxGamma * maxProgress).toInt())
160         // Log base 3 via: log_b(x) = log_y(x) / log_y(b)
161         offsetSdrSeek.setProgress(
162                 ((1.0 - Math.log(currentMetadata.offsetSdr.toDouble() / Math.log(3.0)) / -11.0)
163                         .toFloat() * maxProgress).toInt())
164         offsetHdrSeek.setProgress(
165                 ((1.0 - Math.log(currentMetadata.offsetHdr.toDouble() / Math.log(3.0)) / -11.0)
166                         .toFloat() * maxProgress).toInt())
167 
168         parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val).setText(
169                 "%.3f".format(currentMetadata.ratioMin))
170         parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val).setText(
171                 "%.3f".format(currentMetadata.ratioMax))
172         parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymin_val).setText(
173                 "%.3f".format(currentMetadata.capacityMin))
174         parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymax_val).setText(
175                 "%.3f".format(currentMetadata.capacityMax))
176         parent.requireViewById<TextView>(R.id.gainmap_metadata_gamma_val).setText(
177                 "%.3f".format(currentMetadata.gamma))
178         parent.requireViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val).setText(
179                 "%.5f".format(currentMetadata.offsetSdr))
180         parent.requireViewById<TextView>(R.id.gainmap_metadata_offsethdr_val).setText(
181                 "%.5f".format(currentMetadata.offsetHdr))
182     }
183 
resetGainmapMetadatanull184     private fun resetGainmapMetadata() {
185         currentMetadata = originalMetadata.copy()
186         applyMetadata(currentMetadata)
187         updateMetadataUi()
188     }
189 
applyMetadatanull190     private fun applyMetadata(newMetadata: GainmapMetadata) {
191         gainmap.setRatioMin(newMetadata.ratioMin, newMetadata.ratioMin, newMetadata.ratioMin)
192         gainmap.setRatioMax(newMetadata.ratioMax, newMetadata.ratioMax, newMetadata.ratioMax)
193         gainmap.setMinDisplayRatioForHdrTransition(newMetadata.capacityMin)
194         gainmap.setDisplayRatioForFullHdr(newMetadata.capacityMax)
195         gainmap.setGamma(newMetadata.gamma, newMetadata.gamma, newMetadata.gamma)
196         gainmap.setEpsilonSdr(newMetadata.offsetSdr, newMetadata.offsetSdr, newMetadata.offsetSdr)
197         gainmap.setEpsilonHdr(newMetadata.offsetHdr, newMetadata.offsetHdr, newMetadata.offsetHdr)
198         renderView.invalidate()
199     }
200 
updateGainmapMinnull201     private fun updateGainmapMin(normalized: Float) {
202         val newValue = minRatioMin + normalized * (maxRatioMin - minRatioMin)
203         parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmin_val).setText(
204                 "%.3f".format(newValue))
205         currentMetadata.ratioMin = newValue
206         gainmap.setRatioMin(newValue, newValue, newValue)
207         renderView.invalidate()
208     }
209 
updateGainmapMaxnull210     private fun updateGainmapMax(normalized: Float) {
211         val newValue = minRatioMax + normalized * (maxRatioMax - minRatioMax)
212         parent.requireViewById<TextView>(R.id.gainmap_metadata_gainmapmax_val).setText(
213                 "%.3f".format(newValue))
214         currentMetadata.ratioMax = newValue
215         gainmap.setRatioMax(newValue, newValue, newValue)
216         renderView.invalidate()
217     }
218 
updateCapacityMinnull219     private fun updateCapacityMin(normalized: Float) {
220         val newValue = minCapacityMin + normalized * (maxCapacityMin - minCapacityMin)
221         parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymin_val).setText(
222                 "%.3f".format(newValue))
223         currentMetadata.capacityMin = newValue
224         gainmap.setMinDisplayRatioForHdrTransition(newValue)
225         renderView.invalidate()
226     }
227 
updateCapacityMaxnull228     private fun updateCapacityMax(normalized: Float) {
229         val newValue = minCapacityMax + normalized * (maxCapacityMax - minCapacityMax)
230         parent.requireViewById<TextView>(R.id.gainmap_metadata_capacitymax_val).setText(
231                 "%.3f".format(newValue))
232         currentMetadata.capacityMax = newValue
233         gainmap.setDisplayRatioForFullHdr(newValue)
234         renderView.invalidate()
235     }
236 
updateGammanull237     private fun updateGamma(normalized: Float) {
238         val newValue = minGamma + normalized * (maxGamma - minGamma)
239         parent.requireViewById<TextView>(R.id.gainmap_metadata_gamma_val).setText(
240                 "%.3f".format(newValue))
241         currentMetadata.gamma = newValue
242         gainmap.setGamma(newValue, newValue, newValue)
243         renderView.invalidate()
244     }
245 
updateOffsetSdrnull246     private fun updateOffsetSdr(normalized: Float) {
247         var newValue = 0.0f
248         if (normalized > 0.0f ) {
249             newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
250         }
251         parent.requireViewById<TextView>(R.id.gainmap_metadata_offsetsdr_val).setText(
252                 "%.5f".format(newValue))
253         currentMetadata.offsetSdr = newValue
254         gainmap.setEpsilonSdr(newValue, newValue, newValue)
255         renderView.invalidate()
256     }
257 
updateOffsetHdrnull258     private fun updateOffsetHdr(normalized: Float) {
259         var newValue = 0.0f
260         if (normalized > 0.0f ) {
261             newValue = Math.pow(3.0, (1.0 - normalized.toDouble()) * -11.0).toFloat()
262         }
263         parent.requireViewById<TextView>(R.id.gainmap_metadata_offsethdr_val).setText(
264                 "%.5f".format(newValue))
265         currentMetadata.offsetHdr = newValue
266         gainmap.setEpsilonHdr(newValue, newValue, newValue)
267         renderView.invalidate()
268     }
269 }
270