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