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