Fix portrait image metadata when uploading without media optimization (#6362)
* fix(media): preserve image orientation metadata without optimization * style: linting
This commit is contained in:
parent
d38f483cd1
commit
523ede744a
2 changed files with 67 additions and 12 deletions
|
|
@ -195,18 +195,16 @@ class AndroidMediaPreProcessor(
|
||||||
file = file,
|
file = file,
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
)
|
)
|
||||||
val imageInfo = contentResolver.openInputStream(uri).use { input ->
|
val (width, height) = extractOrientedImageDimensions(file)
|
||||||
val bitmap = BitmapFactory.decodeStream(input, null, null)!!
|
val imageInfo = ImageInfo(
|
||||||
ImageInfo(
|
width = width,
|
||||||
width = bitmap.width.toLong(),
|
height = height,
|
||||||
height = bitmap.height.toLong(),
|
|
||||||
mimetype = mimeType,
|
mimetype = mimeType,
|
||||||
size = file.length(),
|
size = file.length(),
|
||||||
thumbnailInfo = thumbnailResult?.info,
|
thumbnailInfo = thumbnailResult?.info,
|
||||||
thumbnailSource = null,
|
thumbnailSource = null,
|
||||||
blurhash = thumbnailResult?.blurhash,
|
blurhash = thumbnailResult?.blurhash,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
removeSensitiveImageMetadata(file)
|
removeSensitiveImageMetadata(file)
|
||||||
return MediaUploadInfo.Image(
|
return MediaUploadInfo.Image(
|
||||||
file = file,
|
file = file,
|
||||||
|
|
@ -354,6 +352,23 @@ class AndroidMediaPreProcessor(
|
||||||
return contentResolver.openInputStream(uri)?.use { createTmpFileWithInput(it) }
|
return contentResolver.openInputStream(uri)?.use { createTmpFileWithInput(it) }
|
||||||
?: error("Could not copy the contents of $uri to a temporary file")
|
?: error("Could not copy the contents of $uri to a temporary file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun extractOrientedImageDimensions(file: File): Pair<Long, Long> {
|
||||||
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
|
BitmapFactory.decodeFile(file.path, options)
|
||||||
|
|
||||||
|
val rawWidth = options.outWidth.toLong()
|
||||||
|
val rawHeight = options.outHeight.toLong()
|
||||||
|
val orientation = tryOrNull {
|
||||||
|
ExifInterface(file).getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
|
||||||
|
} ?: ExifInterface.ORIENTATION_UNDEFINED
|
||||||
|
|
||||||
|
return orientedImageDimensions(
|
||||||
|
rawWidth = rawWidth,
|
||||||
|
rawHeight = rawHeight,
|
||||||
|
orientation = orientation,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ImageCompressionResult.toImageInfo(mimeType: String, thumbnailResult: ThumbnailResult?) = ImageInfo(
|
private fun ImageCompressionResult.toImageInfo(mimeType: String, thumbnailResult: ThumbnailResult?) = ImageInfo(
|
||||||
|
|
@ -371,3 +386,18 @@ private fun MediaMetadataRetriever.extractDuration(): Duration {
|
||||||
val durationInMs = extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
|
val durationInMs = extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L
|
||||||
return durationInMs.milliseconds
|
return durationInMs.milliseconds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun orientedImageDimensions(rawWidth: Long, rawHeight: Long, orientation: Int): Pair<Long, Long> {
|
||||||
|
return if (orientation.rotatesRightAngle()) {
|
||||||
|
rawHeight to rawWidth
|
||||||
|
} else {
|
||||||
|
rawWidth to rawHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Int.rotatesRightAngle(): Boolean {
|
||||||
|
return this == ExifInterface.ORIENTATION_ROTATE_90 ||
|
||||||
|
this == ExifInterface.ORIENTATION_ROTATE_270 ||
|
||||||
|
this == ExifInterface.ORIENTATION_TRANSPOSE ||
|
||||||
|
this == ExifInterface.ORIENTATION_TRANSVERSE
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
|
||||||
|
|
@ -42,6 +43,30 @@ import kotlin.time.Duration
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class AndroidMediaPreProcessorTest {
|
class AndroidMediaPreProcessorTest {
|
||||||
|
@Test
|
||||||
|
fun `orientedImageDimensions swaps width and height for 90 degree exif orientation`() {
|
||||||
|
val (width, height) = orientedImageDimensions(
|
||||||
|
rawWidth = 4032,
|
||||||
|
rawHeight = 2268,
|
||||||
|
orientation = ExifInterface.ORIENTATION_ROTATE_90,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(width).isEqualTo(2268)
|
||||||
|
assertThat(height).isEqualTo(4032)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `orientedImageDimensions keeps width and height for upright exif orientation`() {
|
||||||
|
val (width, height) = orientedImageDimensions(
|
||||||
|
rawWidth = 4032,
|
||||||
|
rawHeight = 2268,
|
||||||
|
orientation = ExifInterface.ORIENTATION_NORMAL,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(width).isEqualTo(4032)
|
||||||
|
assertThat(height).isEqualTo(2268)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun TestScope.process(
|
private suspend fun TestScope.process(
|
||||||
asset: Asset,
|
asset: Asset,
|
||||||
mediaOptimizationConfig: MediaOptimizationConfig,
|
mediaOptimizationConfig: MediaOptimizationConfig,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue