Skip to content

Commit ccb3852

Browse files
Merge pull request #88 from android/ultrahdr_edit
[Sample] [Graphics] UltraHDR Image Edit Operations
2 parents 53167de + 1a78c5b commit ccb3852

File tree

5 files changed

+338
-1
lines changed

5 files changed

+338
-1
lines changed

samples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ Demonstrates how to implement data access auditing for your app to identify
2626
This sample demonstrates displaying an UltraHDR image.
2727
- [Displaying UltraHDR (Glide)](graphics/ultrahdr/src/main/java/com/example/platform/graphics/ultrahdr/display/DisplayingUltraHDRUsingGlide.kt):
2828
This sample demonstrates using the Glide image loading library to detect the
29+
- [Editing UltraHDR](graphics/ultrahdr/src/main/java/com/example/platform/graphics/ultrahdr/edit/EditingUltraHDR.kt):
30+
This sample demonstrates editing an UltraHDR image. Spatial edit operations like crop, rotate, scale are supported.
2931
- [Downloadable Fonts](user-interface/text/src/main/java/com/example/platform/ui/text/DownloadableFonts.kt):
3032
Download fonts instead of bundling them in the app resources.
3133
- [Drag and Drop](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDrop.kt):
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
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.edit
17+
18+
import android.graphics.Bitmap
19+
import android.graphics.BitmapFactory
20+
import android.graphics.Canvas
21+
import android.graphics.ColorMatrixColorFilter
22+
import android.graphics.Matrix
23+
import android.graphics.Paint
24+
import android.os.Build
25+
import android.os.Bundle
26+
import android.view.LayoutInflater
27+
import android.view.View
28+
import android.view.ViewGroup
29+
import android.widget.RadioButton
30+
import androidx.annotation.RequiresApi
31+
import androidx.fragment.app.Fragment
32+
import androidx.lifecycle.lifecycleScope
33+
import com.example.platform.graphics.ultrahdr.databinding.EditingUltrahdrBinding
34+
import com.google.android.catalog.framework.annotations.Sample
35+
import kotlinx.coroutines.Dispatchers
36+
import kotlinx.coroutines.launch
37+
import kotlinx.coroutines.withContext
38+
import kotlin.math.roundToInt
39+
40+
41+
@Sample(
42+
name = "Editing UltraHDR",
43+
description = "This sample demonstrates editing an UltraHDR image and the resulting gainmap as well. Spatial edit operations like crop, rotate, scale are supported",
44+
documentation = "https://developer.android.com/guide/topics/media/hdr-image-format",
45+
tags = ["UltraHDR"],
46+
)
47+
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
48+
class EditingUltraHDR : Fragment() {
49+
50+
51+
/**
52+
* Android ViewBinding.
53+
*/
54+
private var _binding: EditingUltrahdrBinding? = null
55+
private val binding get() = _binding!!
56+
57+
override fun onCreateView(
58+
inflater: LayoutInflater,
59+
container: ViewGroup?,
60+
savedInstanceState: Bundle?,
61+
): View {
62+
_binding = EditingUltrahdrBinding.inflate(inflater, container, false)
63+
return binding.root
64+
}
65+
66+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
67+
super.onViewCreated(view, savedInstanceState)
68+
69+
// The ColorModeControls Class contain the necessary function to change the activities
70+
// ColorMode to HDR, which allows and UltraHDRs images gain map to be used to enhance the
71+
// image.
72+
binding.colorModeControls.setWindow(requireActivity().window)
73+
74+
// Setup editing options and on listeners
75+
initializedImagesOrGainmap()
76+
}
77+
78+
private fun initializedImagesOrGainmap() {
79+
displayOriginalUltraHDRImage()
80+
81+
//Set default editing for type image and operation rotate
82+
var editingType: Int = ULTRA_HDR_DISPLAY_EDITED_IMAGE
83+
var editOperation = ULTRA_HDR_IMAGE_ROTATE
84+
85+
//Select which type to edit : Image or Gainmap (for visualization purposes only)
86+
binding.imageOptionVisualization.setOnCheckedChangeListener { group, i ->
87+
val selected = group.findViewById<RadioButton>(i)
88+
editingType = group.indexOfChild(selected)
89+
}
90+
91+
//Select the edit operation : crop, rotate, scale
92+
binding.imageOptionsEditingGroup.setOnCheckedChangeListener { group, i ->
93+
val selected = group.findViewById<RadioButton>(i)
94+
editOperation = group.indexOfChild(selected)
95+
editSelectedUltraHDRImage(editOperation, editingType)
96+
}
97+
98+
//Initialize refresh button
99+
binding.refreshUltrahdrEditedImage.setOnClickListener {
100+
editSelectedUltraHDRImage(editOperation,editingType)
101+
}
102+
103+
}
104+
/**
105+
* Updated the currently displayed UltraHDR ORIGINAL image.
106+
*/
107+
private fun displayOriginalUltraHDRImage() {
108+
109+
val stream = context?.assets?.open(ULTRA_HDR_IMAGE_TRAIN_STATION)
110+
val bitmap = BitmapFactory.decodeStream(stream)
111+
binding.imageContainer.setImageBitmap(bitmap)
112+
}
113+
114+
/**
115+
* Edit currently displayed UltraHDR image
116+
*/
117+
private fun editSelectedUltraHDRImage(editOperation: Int, editingType: Int) =
118+
lifecycleScope.launch(Dispatchers.IO) {
119+
//Performing edit operations on a background thread
120+
121+
val stream = context?.assets?.open(ULTRA_HDR_IMAGE_TRAIN_STATION)
122+
val bitmap = BitmapFactory.decodeStream(stream)
123+
124+
//first display original image, then perform any edit operations
125+
displayOriginalUltraHDRImage()
126+
127+
//Perform Edit operation
128+
lateinit var editedBitmap: Bitmap
129+
130+
when (editOperation) {
131+
ULTRA_HDR_IMAGE_CROP -> editedBitmap = bitmap.crop()
132+
ULTRA_HDR_IMAGE_ROTATE -> editedBitmap = bitmap.rotate()
133+
ULTRA_HDR_IMAGE_SCALE -> editedBitmap = bitmap.scale()
134+
}
135+
136+
//Display edited image or gainmap, run on main thread to update the UI
137+
withContext(Dispatchers.Main) {
138+
when (editingType) {
139+
ULTRA_HDR_DISPLAY_EDITED_IMAGE -> binding.imageContainer.setImageBitmap(editedBitmap)
140+
ULTRA_HDR_DISPLAY_EDITED_GAINMAP -> binding.imageContainer.setImageBitmap(
141+
visualizeEditedGainmap(editedBitmap),
142+
)
143+
}
144+
//Release memory
145+
bitmap.recycle()
146+
}
147+
}
148+
149+
private fun Bitmap.rotate(): Bitmap {
150+
151+
val matrix = Matrix().apply { postRotate(ULTRA_HDR_IMAGE_ROTATE_DEGREE) }
152+
return Bitmap.createBitmap(this, 0, 0, width, height, matrix, true)
153+
}
154+
155+
private fun Bitmap.scale(): Bitmap {
156+
157+
//For simplicity : Resize the image to X% of existing dimensions
158+
return Bitmap.createScaledBitmap(
159+
this, (this.width * ULTRA_HDR_IMAGE_SCALE_RATIO).roundToInt(),
160+
(this.height * ULTRA_HDR_IMAGE_SCALE_RATIO).roundToInt(), true,
161+
)
162+
}
163+
164+
private fun Bitmap.crop(): Bitmap {
165+
166+
//For simplicity : Crop to square shape
167+
val dimension: Int = this.width.coerceAtMost(this.height)
168+
return Bitmap.createBitmap(this, 0, 0, dimension, dimension)
169+
}
170+
171+
private suspend fun visualizeEditedGainmap(bitmap: Bitmap): Bitmap =
172+
withContext(Dispatchers.IO) {
173+
bitmap.gainmap?.let {
174+
val contents = it.gainmapContents
175+
if (contents.config != Bitmap.Config.ALPHA_8) return@withContext contents
176+
177+
178+
val visual = Bitmap.createBitmap(
179+
contents.width, contents.height,
180+
Bitmap.Config.ARGB_8888,
181+
)
182+
183+
val canvas = Canvas(visual)
184+
val paint = Paint()
185+
paint.colorFilter = ColorMatrixColorFilter(
186+
floatArrayOf(
187+
0f, 0f, 0f, 1f, 0f,
188+
0f, 0f, 0f, 1f, 0f,
189+
0f, 0f, 0f, 1f, 0f,
190+
0f, 0f, 0f, 0f, 255f,
191+
),
192+
)
193+
canvas.drawBitmap(contents, 0f, 0f, paint)
194+
canvas.setBitmap(null)
195+
return@withContext visual
196+
}
197+
return@withContext bitmap
198+
}
199+
200+
override fun onDetach() {
201+
super.onDetach()
202+
binding.colorModeControls.detach()
203+
}
204+
205+
companion object {
206+
/**
207+
* Sample UltraHDR images paths
208+
*/
209+
private const val ULTRA_HDR_IMAGE_TRAIN_STATION = "gainmaps/train_station_night.jpg"
210+
211+
/**
212+
* Sample UltraHDR images operations
213+
*/
214+
private const val ULTRA_HDR_IMAGE_CROP = 0
215+
private const val ULTRA_HDR_IMAGE_ROTATE = 1
216+
private const val ULTRA_HDR_IMAGE_SCALE = 2
217+
218+
/**
219+
* Visualization types for editing
220+
*/
221+
private const val ULTRA_HDR_DISPLAY_EDITED_IMAGE = 0
222+
private const val ULTRA_HDR_DISPLAY_EDITED_GAINMAP = 1
223+
224+
private const val ULTRA_HDR_IMAGE_ROTATE_DEGREE = 180F
225+
private const val ULTRA_HDR_IMAGE_SCALE_RATIO = 0.2
226+
}
227+
228+
}
229+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
xml version="1.0" encoding="utf-8"?>
16+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
17+
android:layout_width="match_parent"
18+
android:layout_height="wrap_content"
19+
xmlns:tools="http://schemas.android.com/tools"
20+
android:orientation="vertical">
21+
22+
<com.example.platform.graphics.ultrahdr.common.ColorModeControls
23+
android:id="@+id/color_mode_controls"
24+
android:layout_width="match_parent"
25+
android:layout_height="wrap_content" />
26+
27+
<Button
28+
android:id="@+id/refresh_ultrahdr_edited_image"
29+
android:layout_width="wrap_content"
30+
android:layout_height="wrap_content"
31+
android:layout_marginStart="@dimen/ultrahdr_color_mode_current_mode_padding"
32+
android:layout_marginEnd="@dimen/ultrahdr_color_mode_current_mode_padding"
33+
android:text="@string/refresh_ultrahdr_edited_image"
34+
/>
35+
36+
<RadioGroup
37+
android:id="@+id/image_option_visualization"
38+
android:layout_width="match_parent"
39+
android:layout_height="wrap_content"
40+
android:layout_marginTop="@dimen/ultrahdr_color_mode_current_mode_padding"
41+
android:orientation="horizontal">
42+
43+
<RadioButton
44+
android:id="@+id/option_edited_ultrahdr"
45+
android:layout_width="0dp"
46+
android:layout_height="wrap_content"
47+
android:layout_weight="1"
48+
android:checked="false"
49+
android:text="@string/displaying_edited_ultrahdr" />
50+
51+
<RadioButton
52+
android:id="@+id/option_edited_gainmap"
53+
android:layout_width="0dp"
54+
android:layout_height="wrap_content"
55+
android:layout_weight="1"
56+
android:checked="false"
57+
android:text="@string/displaying_edited_gainmap" />
58+
RadioGroup>
59+
60+
<RadioGroup
61+
android:id="@+id/image_options_editing_group"
62+
android:layout_width="match_parent"
63+
android:layout_height="wrap_content"
64+
android:layout_marginTop="@dimen/ultrahdr_color_mode_current_mode_padding"
65+
android:orientation="horizontal">
66+
67+
<RadioButton
68+
android:id="@+id/option_crop"
69+
android:layout_width="0dp"
70+
android:layout_height="wrap_content"
71+
android:layout_weight="1"
72+
android:text="@string/displaying_ultrahdr_option_title_crop" />
73+
74+
<RadioButton
75+
android:id="@+id/option_rotate"
76+
android:layout_width="0dp"
77+
android:layout_height="wrap_content"
78+
android:layout_weight="1"
79+
android:text="@string/displaying_ultrahdr_option_title_rotate" />
80+
81+
<RadioButton
82+
android:id="@+id/option_scale"
83+
android:layout_width="0dp"
84+
android:layout_height="wrap_content"
85+
android:layout_weight="1"
86+
android:text="@string/displaying_ultrahdr_option_title_scale" />
87+
RadioGroup>
88+
89+
<ImageView
90+
android:id="@+id/image_container"
91+
android:layout_width="wrap_content"
92+
android:layout_height="wrap_content"
93+
android:contentDescription="@null" />
94+
95+
LinearLayout>

samples/graphics/ultrahdr/src/main/res/values/dimens.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@
1515
-->
1616

1717
<resources>
18-
<dimen name="ultrahdr_color_mode_current_mode_padding">16dpdimen>
18+
<dimen name="ultrahdr_color_mode_current_mode_padding">8dpdimen>
1919
resources>

samples/graphics/ultrahdr/src/main/res/values/strings.xml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,20 @@
2727
<string name="visualizing_ultrahdr_gainmap_mode_title_ultrahdr">UltraHDRstring>
2828
<string name="visualizing_ultrahdr_gainmap_mode_title_no_ultrahdr">Not UltraHDRstring>
2929

30+
31+
<string name="displaying_ultrahdr_option_title_crop">Cropstring>
32+
<string name="displaying_ultrahdr_option_title_rotate">Rotatestring>
33+
<string name="displaying_ultrahdr_option_title_scale">Scalestring>
34+
<string name="displaying_edited_ultrahdr">Edit UltraHDR Imagestring>
35+
<string name="displaying_edited_gainmap">Edit Gainmapstring>
36+
37+
3038
3139
<string name="color_mode_sdr">SDRstring>
3240
<string name="color_mode_hdr">HDRstring>
3341
<string name="color_mode_hdr_with_ratio">HDR | SDR/HDR Ratio = %fstring>
3442
<string name="color_mode_unknown">Unknownstring>
43+
44+
<string name="refresh_ultrahdr_edited_image">Refreshstring>
45+
3546
resources>

0 commit comments

Comments
 (0)