1
+ /*
2
+ * Copyright 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
+ * https://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
+ package com.example.platform.graphics.ultrahdr.display
17
+
18
+ import android.app.Activity
19
+ import android.content.Context
20
+ import android.content.ContextWrapper
21
+ import android.content.pm.ActivityInfo
22
+ import android.graphics.Bitmap
23
+ import android.graphics.BitmapFactory
24
+ import android.os.Build
25
+ import android.util.Log
26
+ import android.view.Display
27
+ import android.view.Window
28
+ import android.widget.ImageView
29
+ import androidx.annotation.RequiresApi
30
+ import androidx.compose.foundation.Image
31
+ import androidx.compose.foundation.layout.Arrangement
32
+ import androidx.compose.foundation.layout.Column
33
+ import androidx.compose.foundation.layout.Row
34
+ import androidx.compose.foundation.layout.Spacer
35
+ import androidx.compose.foundation.layout.fillMaxWidth
36
+ import androidx.compose.foundation.layout.padding
37
+ import androidx.compose.foundation.layout.width
38
+ import androidx.compose.material3.Button
39
+ import androidx.compose.material3.Divider
40
+ import androidx.compose.material3.MaterialTheme
41
+ import androidx.compose.material3.Text
42
+ import androidx.compose.runtime.Composable
43
+ import androidx.compose.runtime.DisposableEffect
44
+ import androidx.compose.runtime.LaunchedEffect
45
+ import androidx.compose.runtime.getValue
46
+ import androidx.compose.runtime.mutableStateOf
47
+ import androidx.compose.runtime.remember
48
+ import androidx.compose.runtime.setValue
49
+ import androidx.compose.ui.Alignment
50
+ import androidx.compose.ui.Modifier
51
+ import androidx.compose.ui.graphics.asImageBitmap
52
+ import androidx.compose.ui.platform.LocalContext
53
+ import androidx.compose.ui.platform.LocalView
54
+ import androidx.compose.ui.res.dimensionResource
55
+ import androidx.compose.ui.res.stringResource
56
+ import androidx.compose.ui.unit.dp
57
+ import androidx.compose.ui.viewinterop.AndroidView
58
+ import androidx.compose.ui.window.DialogWindowProvider
59
+ import com.example.platform.graphics.ultrahdr.R
60
+ import com.google.android.catalog.framework.annotations.Sample
61
+ import kotlinx.coroutines.Dispatchers
62
+ import kotlinx.coroutines.withContext
63
+ import java.util.function.Consumer
64
+
65
+ @RequiresApi(34 )
66
+ @Sample(
67
+ name = " Displaying UltraHDR (Compose)" ,
68
+ description = " This sample demonstrates displaying an UltraHDR image in a Compose View and an Android View" ,
69
+ documentation = " https://developer.android.com/guide/topics/media/hdr-image-format" ,
70
+ tags = [" UltraHDR" , " Compose" ],
71
+ )
72
+
73
+ @Composable
74
+ fun DisplayUltraHDRScreen () {
75
+ var bitmap by remember { mutableStateOf<Bitmap ?>(null ) }
76
+
77
+ val context = LocalContext .current
78
+ var colorMode by remember { mutableStateOf<ColorMode >(ColorMode .Default ) }
79
+ val window = findWindow()
80
+ val display = LocalView .current.display
81
+
82
+ // Load asset and bitmap on background thread
83
+ LaunchedEffect (Unit ) {
84
+ window?.let {
85
+ colorMode = getColorMode(it, display)
86
+ }
87
+
88
+ // Same bitmap is used to load image in an image view and image (Compose)
89
+ bitmap = withContext(Dispatchers .IO ) {
90
+ val ultraHdrImage = " gainmaps/night_highrise.jpg"
91
+ context.assets.open(ultraHdrImage).use { inputStream ->
92
+ BitmapFactory .decodeStream(inputStream)
93
+ }
94
+ }
95
+ }
96
+
97
+ val hdrSdrRatioChangeListener = Consumer <Display > { display ->
98
+ if (window == null ) return @Consumer
99
+
100
+ Log .d(TAG , " HDR/SDR Ratio Changed ${display.hdrSdrRatio} " )
101
+
102
+ colorMode = getColorMode(window, display)
103
+ }
104
+
105
+ DisposableEffect (window, display) {
106
+ if (display.isHdrSdrRatioAvailable) {
107
+ display.registerHdrSdrRatioChangedListener(
108
+ { executable -> executable.run () },
109
+ hdrSdrRatioChangeListener,
110
+ )
111
+ }
112
+ // When the effect leaves the Composition, remove the observer
113
+ onDispose {
114
+ display.unregisterHdrSdrRatioChangedListener(hdrSdrRatioChangeListener)
115
+ }
116
+ }
117
+
118
+
119
+ Column (
120
+ modifier = Modifier .fillMaxWidth(),
121
+ verticalArrangement = Arrangement .SpaceEvenly ,
122
+ horizontalAlignment = Alignment .CenterHorizontally ,
123
+ ) {
124
+
125
+ val details = when (colorMode) {
126
+ is ColorMode .Default -> stringResource(R .string.color_mode_sdr)
127
+ is ColorMode .Unknown -> stringResource(R .string.color_mode_unknown)
128
+ is ColorMode .Hdr -> stringResource(
129
+ R .string.color_mode_hdr_with_ratio,
130
+ (colorMode as ColorMode .Hdr ).hdrSdrRatio,
131
+ )
132
+ }
133
+
134
+ Text (stringResource(R .string.color_mode_details, details))
135
+
136
+ // Add SDR/HDR Color mode controls
137
+ Row (
138
+ horizontalArrangement = Arrangement .Center ,
139
+ modifier = Modifier .padding(dimensionResource(R .dimen.ultrahdr_color_mode_current_mode_padding)),
140
+ ) {
141
+ Button (
142
+ modifier = Modifier .weight(1f ),
143
+ onClick = { window?.colorMode = ActivityInfo .COLOR_MODE_DEFAULT },
144
+ ) {
145
+ Text (stringResource(R .string.color_mode_sdr))
146
+ }
147
+ Spacer (Modifier .width(dimensionResource(R .dimen.ultrahdr_color_mode_current_mode_padding)))
148
+ Button (
149
+ modifier = Modifier .weight(1f ),
150
+ onClick = { window?.colorMode = ActivityInfo .COLOR_MODE_HDR },
151
+ ) {
152
+ Text (stringResource(R .string.color_mode_hdr))
153
+ }
154
+ }
155
+
156
+ // Render UltraHDR in a Compose Image
157
+ Text (text = " Image (Compose)" )
158
+
159
+ if (bitmap != null ) {
160
+ Image (
161
+ bitmap = bitmap!! .asImageBitmap(),
162
+ contentDescription = null ,
163
+ modifier = Modifier .weight(1f ),
164
+ )
165
+ }
166
+
167
+ Divider (
168
+ modifier = Modifier
169
+ .fillMaxWidth()
170
+ .padding(vertical = 4 .dp),
171
+ thickness = 1 .dp,
172
+ color = MaterialTheme .colorScheme.primary,
173
+ )
174
+
175
+ // Render UltraHDR in View (ImageView)
176
+ Text (text = " ImageView (Android View)" )
177
+ AndroidView (
178
+ modifier = Modifier .weight(1f ),
179
+ factory = {
180
+ ImageView (it).apply {
181
+ setImageBitmap(bitmap)
182
+ }
183
+ },
184
+ update = {
185
+ it.setImageBitmap(bitmap)
186
+ },
187
+ )
188
+ }
189
+ }
190
+
191
+ private const val TAG = " DisplayUltraHDRScreen"
192
+
193
+ private sealed interface ColorMode {
194
+ object Default : ColorMode
195
+ object Unknown : ColorMode
196
+
197
+ @JvmInline
198
+ value class Hdr (val hdrSdrRatio : Float ) : ColorMode
199
+ }
200
+
201
+ @Composable
202
+ private fun findWindow (): Window ? =
203
+ (LocalView .current.parent as ? DialogWindowProvider )?.window ? : LocalContext .current.findWindow()
204
+
205
+ private tailrec fun Context.findWindow (): Window ? =
206
+ when (this ) {
207
+ is Activity -> window
208
+ is ContextWrapper -> baseContext.findWindow()
209
+ else -> null
210
+ }
211
+
212
+ @RequiresApi(Build .VERSION_CODES .UPSIDE_DOWN_CAKE )
213
+ private fun getColorMode (window : Window , display : Display ) = when (window.colorMode) {
214
+ ActivityInfo .COLOR_MODE_DEFAULT -> ColorMode .Default
215
+ ActivityInfo .COLOR_MODE_HDR -> ColorMode .Hdr (display.hdrSdrRatio)
216
+ else -> ColorMode .Unknown
217
+ }
0 commit comments