15
15
*/
16
16
package com.google.android.ground.ui.datacollection.tasks.photo
17
17
18
+ import android.content.Context
18
19
import android.content.res.Resources
20
+ import android.graphics.Bitmap
21
+ import android.graphics.Matrix
22
+ import android.media.ExifInterface
19
23
import android.net.Uri
20
24
import androidx.lifecycle.LiveData
21
25
import androidx.lifecycle.asLiveData
@@ -25,14 +29,17 @@ import com.google.android.ground.persistence.remote.firebase.FirebaseStorageMana
25
29
import com.google.android.ground.repository.UserMediaRepository
26
30
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskViewModel
27
31
import com.google.android.ground.ui.util.BitmapUtil
32
+ import dagger.hilt.android.qualifiers.ApplicationContext
28
33
import java.io.IOException
34
+ import java.lang.UnsupportedOperationException
29
35
import javax.inject.Inject
30
36
import kotlinx.coroutines.flow.map
31
37
import timber.log.Timber
32
38
33
39
class PhotoTaskViewModel
34
40
@Inject
35
41
constructor (
42
+ @ApplicationContext private val context: Context ,
36
43
private val userMediaRepository: UserMediaRepository ,
37
44
private val bitmapUtil: BitmapUtil ,
38
45
resources: Resources ,
@@ -50,6 +57,15 @@ constructor(
50
57
taskTaskData.map { userMediaRepository.getDownloadUrl(it?.getDetailsText()) }.asLiveData()
51
58
val isPhotoPresent: LiveData <Boolean > = taskTaskData.map { it.isNotNullOrEmpty() }.asLiveData()
52
59
60
+ private fun rotateBitmap (bitmap : Bitmap , rotateDegrees : Float ): Bitmap {
61
+ val matrix = Matrix ()
62
+ // Rotate iff rotation is non-zero.
63
+ if (rotateDegrees != 0f ) {
64
+ matrix.postRotate(rotateDegrees)
65
+ }
66
+ return Bitmap .createBitmap(bitmap, 0 , 0 , bitmap.getWidth(), bitmap.getHeight(), matrix, true )
67
+ }
68
+
53
69
/* *
54
70
* Saves photo data stored on an on-device URI in Ground-associated storage and prepares it for
55
71
* inclusion in a data collection submission.
@@ -59,7 +75,9 @@ constructor(
59
75
requireNotNull(currentTask) { " Photo captured but no task waiting for the result" }
60
76
61
77
try {
62
- val bitmap = bitmapUtil.fromUri(uri)
78
+ val orientation = getOrientationFromExif(uri)
79
+ val rotateDegrees = getRotationDegrees(orientation)
80
+ val bitmap = rotateBitmap(bitmapUtil.fromUri(uri), rotateDegrees)
63
81
val file = userMediaRepository.savePhoto(bitmap, currentTask)
64
82
userMediaRepository.addImageToGallery(file.absolutePath, file.name)
65
83
val remoteFilename = FirebaseStorageManager .getRemoteMediaPath(surveyId, file.absolutePath)
@@ -68,4 +86,26 @@ constructor(
68
86
Timber .e(e, " Error getting photo selected from storage" )
69
87
}
70
88
}
89
+
90
+ /* *
91
+ * Returns the number of degrees a photo should be rotated based on the value of its orientation
92
+ * EXIF tag.
93
+ */
94
+ private fun getRotationDegrees (orientation : Int ): Float =
95
+ when (orientation) {
96
+ ExifInterface .ORIENTATION_NORMAL -> 0f
97
+ ExifInterface .ORIENTATION_ROTATE_90 -> 90f
98
+ ExifInterface .ORIENTATION_ROTATE_180 -> 180f
99
+ ExifInterface .ORIENTATION_ROTATE_270 -> 270f
100
+ else -> throw UnsupportedOperationException (" Unsupported photo orientation $orientation " )
101
+ }
102
+
103
+ /* * Returns the EXIF orientation attribute of the JPEG image at the specified URI. */
104
+ private fun getOrientationFromExif (uri : Uri ): Int {
105
+ val inputStream =
106
+ context.contentResolver.openInputStream(uri)
107
+ ? : throw IOException (" Content resolver returned null for $uri " )
108
+ val exif = ExifInterface (inputStream)
109
+ return exif.getAttributeInt(ExifInterface .TAG_ORIENTATION , ExifInterface .ORIENTATION_NORMAL )
110
+ }
71
111
}
0 commit comments