Skip to content

Commit 7e396e9

Browse files
Merge branch 'master' into github-action-unit-tests
2 parents f7bee2c + 614664b commit 7e396e9

File tree

1 file changed

+41
-1
lines changed

1 file changed

+41
-1
lines changed

ground/src/main/java/com/google/android/ground/ui/datacollection/tasks/photo/PhotoTaskViewModel.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515
*/
1616
package com.google.android.ground.ui.datacollection.tasks.photo
1717

18+
import android.content.Context
1819
import android.content.res.Resources
20+
import android.graphics.Bitmap
21+
import android.graphics.Matrix
22+
import android.media.ExifInterface
1923
import android.net.Uri
2024
import androidx.lifecycle.LiveData
2125
import androidx.lifecycle.asLiveData
@@ -25,14 +29,17 @@ import com.google.android.ground.persistence.remote.firebase.FirebaseStorageMana
2529
import com.google.android.ground.repository.UserMediaRepository
2630
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskViewModel
2731
import com.google.android.ground.ui.util.BitmapUtil
32+
import dagger.hilt.android.qualifiers.ApplicationContext
2833
import java.io.IOException
34+
import java.lang.UnsupportedOperationException
2935
import javax.inject.Inject
3036
import kotlinx.coroutines.flow.map
3137
import timber.log.Timber
3238

3339
class PhotoTaskViewModel
3440
@Inject
3541
constructor(
42+
@ApplicationContext private val context: Context,
3643
private val userMediaRepository: UserMediaRepository,
3744
private val bitmapUtil: BitmapUtil,
3845
resources: Resources,
@@ -50,6 +57,15 @@ constructor(
5057
taskTaskData.map { userMediaRepository.getDownloadUrl(it?.getDetailsText()) }.asLiveData()
5158
val isPhotoPresent: LiveData<Boolean> = taskTaskData.map { it.isNotNullOrEmpty() }.asLiveData()
5259

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+
5369
/**
5470
* Saves photo data stored on an on-device URI in Ground-associated storage and prepares it for
5571
* inclusion in a data collection submission.
@@ -59,7 +75,9 @@ constructor(
5975
requireNotNull(currentTask) { "Photo captured but no task waiting for the result" }
6076

6177
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)
6381
val file = userMediaRepository.savePhoto(bitmap, currentTask)
6482
userMediaRepository.addImageToGallery(file.absolutePath, file.name)
6583
val remoteFilename = FirebaseStorageManager.getRemoteMediaPath(surveyId, file.absolutePath)
@@ -68,4 +86,26 @@ constructor(
6886
Timber.e(e, "Error getting photo selected from storage")
6987
}
7088
}
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+
}
71111
}

0 commit comments

Comments
 (0)