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.content.Context
20 import android.graphics.Bitmap
21 import android.graphics.Canvas
22 import android.graphics.ColorMatrixColorFilter
23 import android.graphics.Gainmap
24 import android.graphics.ImageDecoder
25 import android.graphics.Paint
26 import android.util.AttributeSet
27 import android.view.View
28 import android.widget.AdapterView
29 import android.widget.ArrayAdapter
30 import android.widget.Button
31 import android.widget.FrameLayout
32 import android.widget.RadioGroup
33 import android.widget.Spinner
34 import android.widget.TextView
35 import com.android.test.silkfx.R
36 import com.davemorrissey.labs.subscaleview.ImageSource
37 import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
38 
39 class GainmapImage(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
40 
41     private val gainmapImages: Array<String>
42     private var selectedImage = -1
43     private var outputMode = R.id.output_hdr
44     private var bitmap: Bitmap? = null
45     private var gainmap: Gainmap? = null
46     private var gainmapVisualizer: Bitmap? = null
47     private lateinit var imageView: SubsamplingScaleImageView
48     private lateinit var gainmapMetadataEditor: GainmapMetadataEditor
49 
50     init {
51         gainmapImages = context.assets.list("gainmaps")!!
52     }
53 
54     fun setImageSource(source: ImageDecoder.Source) {
55         findViewById<Spinner>(R.id.image_selection)!!.visibility = View.GONE
56         doDecode(source)
57     }
58 
59     override fun onFinishInflate() {
60         super.onFinishInflate()
61 
62         imageView = findViewById(R.id.image)!!
63         gainmapMetadataEditor = GainmapMetadataEditor(this, imageView)
64 
65         findViewById<RadioGroup>(R.id.output_mode)!!.also {
66             it.check(outputMode)
67             it.setOnCheckedChangeListener { _, checkedId ->
68                 val previousMode = outputMode
69                 outputMode = checkedId
70                 if (previousMode == R.id.output_sdr && checkedId == R.id.output_hdr) {
71                     animateToHdr()
72                 } else if (previousMode == R.id.output_hdr && checkedId == R.id.output_sdr) {
73                     animateToSdr()
74                 } else {
75                     updateDisplay()
76                 }
77             }
78         }
79 
80         val spinner = findViewById<Spinner>(R.id.image_selection)!!
81         val adapter = ArrayAdapter(context, android.R.layout.simple_spinner_item, gainmapImages)
82         adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
83         spinner.adapter = adapter
84         spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
85             override fun onItemSelected(
86                     parent: AdapterView<*>?,
87                     view: View?,
88                     position: Int,
89                     id: Long
90             ) {
91                 setImage(position)
92             }
93 
94             override fun onNothingSelected(parent: AdapterView<*>?) {
95             }
96         }
97 
98         findViewById<Button>(R.id.gainmap_metadata)!!.setOnClickListener {
99             gainmapMetadataEditor.openEditor()
100         }
101 
102         setImage(0)
103 
104         imageView.apply {
105             isClickable = true
106             setOnClickListener {
107                 animate().alpha(.5f).withEndAction {
108                     animate().alpha(1f).start()
109                 }.start()
110             }
111         }
112     }
113 
114     private fun setImage(position: Int) {
115         if (selectedImage == position) return
116         selectedImage = position
117         val source = ImageDecoder.createSource(resources.assets,
118                 "gainmaps/${gainmapImages[position]}")
119         doDecode(source)
120     }
121 
122     private fun doDecode(source: ImageDecoder.Source) {
123         gainmap = null
124         bitmap = ImageDecoder.decodeBitmap(source) { decoder, info, source ->
125             decoder.allocator = ImageDecoder.ALLOCATOR_SOFTWARE
126         }
127         if (!bitmap!!.hasGainmap()) {
128             outputMode = R.id.output_sdr
129             findViewById<TextView>(R.id.error_msg)!!.also {
130                 it.visibility = View.VISIBLE
131                 it.text = "Image doesn't have a gainmap, only showing in SDR"
132             }
133             findViewById<RadioGroup>(R.id.output_mode)!!.also {
134                 it.check(R.id.output_sdr)
135                 it.visibility = View.GONE
136             }
137         } else {
138             findViewById<TextView>(R.id.error_msg)!!.visibility = View.GONE
139             findViewById<RadioGroup>(R.id.output_mode)!!.visibility = View.VISIBLE
140 
141             gainmap = bitmap!!.gainmap
142             gainmapMetadataEditor.setGainmap(gainmap)
143             val map = gainmap!!.gainmapContents
144             if (map.config != Bitmap.Config.ALPHA_8) {
145                 gainmapVisualizer = map
146             } else {
147                 gainmapVisualizer = Bitmap.createBitmap(map.width, map.height,
148                         Bitmap.Config.ARGB_8888)
149                 val canvas = Canvas(gainmapVisualizer!!)
150                 val paint = Paint()
151                 paint.colorFilter = ColorMatrixColorFilter(
152                         floatArrayOf(
153                                 0f, 0f, 0f, 1f, 0f,
154                                 0f, 0f, 0f, 1f, 0f,
155                                 0f, 0f, 0f, 1f, 0f,
156                                 0f, 0f, 0f, 0f, 255f
157                         )
158                 )
159                 canvas.drawBitmap(map, 0f, 0f, paint)
160                 canvas.setBitmap(null)
161             }
162         }
163 
164         updateDisplay()
165     }
166 
167     private fun animateToHdr() {
168         if (bitmap == null || gainmap == null) return
169 
170         // TODO: Trigger an animation
171         updateDisplay()
172     }
173 
174     private fun animateToSdr() {
175         if (bitmap == null) return
176 
177         // TODO: Trigger an animation
178         updateDisplay()
179     }
180 
181     private fun updateDisplay() {
182         if (bitmap == null) return
183 
184         imageView.setImage(ImageSource.cachedBitmap(when (outputMode) {
185             R.id.output_hdr -> {
186                 gainmapMetadataEditor.useOriginalMetadata()
187                 bitmap!!.gainmap = gainmap
188                 bitmap!!
189             }
190 
191             R.id.output_hdr_test -> {
192                 gainmapMetadataEditor.useEditMetadata()
193                 bitmap!!.gainmap = gainmap
194                 bitmap!!
195             }
196 
197             R.id.output_sdr -> {
198                 bitmap!!.gainmap = null; bitmap!!
199             }
200 
201             R.id.output_gainmap -> gainmapVisualizer!!
202             else -> throw IllegalStateException()
203         }))
204     }
205 }
206