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