Browse Source

Fix: WebP images can't be sent as media. (#1501)

* Fix: WebP images can't be sent as media.

* Place the `BitmapFactory.Options` mode change and comment where it belongs.
pull/1505/head
Jorge Martin Espinosa 12 months ago committed by GitHub
parent
commit
ffef9d936a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      changelog.d/1483.bugfix
  2. 1
      libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt
  3. 19
      libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt
  4. 26
      libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt

1
changelog.d/1483.bugfix

@ -0,0 +1 @@
WebP images can't be sent as media.

1
libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt

@ -31,6 +31,7 @@ object MimeTypes {
const val BadJpg = "image/jpg" const val BadJpg = "image/jpg"
const val Jpeg = "image/jpeg" const val Jpeg = "image/jpeg"
const val Gif = "image/gif" const val Gif = "image/gif"
const val WebP = "image/webp"
const val Videos = "video/*" const val Videos = "video/*"
const val Mp4 = "video/mp4" const val Mp4 = "video/mp4"

19
libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt

@ -66,6 +66,8 @@ class AndroidMediaPreProcessor @Inject constructor(
* values may surpass this limit. (i.e.: an image of `480x3000px` would have `inSampleSize=1` and be sent as is). * values may surpass this limit. (i.e.: an image of `480x3000px` would have `inSampleSize=1` and be sent as is).
*/ */
private const val IMAGE_SCALE_REF_SIZE = 640 private const val IMAGE_SCALE_REF_SIZE = 640
private val notCompressibleImageTypes = listOf(MimeTypes.Gif, MimeTypes.WebP)
} }
private val contentResolver = context.contentResolver private val contentResolver = context.contentResolver
@ -78,7 +80,10 @@ class AndroidMediaPreProcessor @Inject constructor(
): Result<MediaUploadInfo> = withContext(coroutineDispatchers.computation) { ): Result<MediaUploadInfo> = withContext(coroutineDispatchers.computation) {
runCatching { runCatching {
val result = when { val result = when {
mimeType.isMimeTypeImage() -> processImage(uri, mimeType, compressIfPossible && mimeType != MimeTypes.Gif) mimeType.isMimeTypeImage() -> {
val shouldBeCompressed = compressIfPossible && mimeType !in notCompressibleImageTypes
processImage(uri, mimeType, shouldBeCompressed)
}
mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, compressIfPossible) mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, compressIfPossible)
mimeType.isMimeTypeAudio() -> processAudio(uri, mimeType) mimeType.isMimeTypeAudio() -> processAudio(uri, mimeType)
else -> processFile(uri, mimeType) else -> processFile(uri, mimeType)
@ -125,13 +130,11 @@ class AndroidMediaPreProcessor @Inject constructor(
exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
} ?: ExifInterface.ORIENTATION_UNDEFINED } ?: ExifInterface.ORIENTATION_UNDEFINED
val compressionResult = contentResolver.openInputStream(uri).use { input -> val compressionResult = imageCompressor.compressToTmpFile(
imageCompressor.compressToTmpFile( inputStreamProvider = { contentResolver.openInputStream(uri)!! },
inputStream = requireNotNull(input), resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE),
resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE), orientation = orientation,
orientation = orientation, ).getOrThrow()
).getOrThrow()
}
val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file) val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file)
val imageInfo = compressionResult.toImageInfo( val imageInfo = compressionResult.toImageInfo(
mimeType = mimeType, mimeType = mimeType,

26
libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt

@ -27,7 +27,6 @@ import io.element.android.libraries.androidutils.file.createTmpFile
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.BufferedInputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import javax.inject.Inject import javax.inject.Inject
@ -42,14 +41,14 @@ class ImageCompressor @Inject constructor(
* @return a [Result] containing the resulting [ImageCompressionResult] with the temporary [File] and some metadata. * @return a [Result] containing the resulting [ImageCompressionResult] with the temporary [File] and some metadata.
*/ */
suspend fun compressToTmpFile( suspend fun compressToTmpFile(
inputStream: InputStream, inputStreamProvider: () -> InputStream,
resizeMode: ResizeMode, resizeMode: ResizeMode,
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED, orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
desiredQuality: Int = 80, desiredQuality: Int = 80,
): Result<ImageCompressionResult> = withContext(Dispatchers.IO) { ): Result<ImageCompressionResult> = withContext(Dispatchers.IO) {
runCatching { runCatching {
val compressedBitmap = compressToBitmap(inputStream, resizeMode, orientation).getOrThrow() val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()
// Encode bitmap to the destination temporary file // Encode bitmap to the destination temporary file
val tmpFile = context.createTmpFile(extension = "jpeg") val tmpFile = context.createTmpFile(extension = "jpeg")
tmpFile.outputStream().use { tmpFile.outputStream().use {
@ -65,17 +64,24 @@ class ImageCompressor @Inject constructor(
} }
/** /**
* Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode] and [orientation]. * Decodes the inputStream from [inputStreamProvider] into a [Bitmap] and applies the needed transformations (rotation, scale)
* based on [resizeMode] and [orientation].
* @return a [Result] containing the resulting [Bitmap]. * @return a [Result] containing the resulting [Bitmap].
*/ */
fun compressToBitmap( fun compressToBitmap(
inputStream: InputStream, inputStreamProvider: () -> InputStream,
resizeMode: ResizeMode, resizeMode: ResizeMode,
orientation: Int, orientation: Int,
): Result<Bitmap> = runCatching { ): Result<Bitmap> = runCatching {
BufferedInputStream(inputStream).use { input -> val options = BitmapFactory.Options()
val options = BitmapFactory.Options() // Decode bounds
inputStreamProvider().use { input ->
calculateDecodingScale(input, resizeMode, options) calculateDecodingScale(input, resizeMode, options)
}
// Decode the actual bitmap
inputStreamProvider().use { input ->
// Now read the actual image and rotate it to match its metadata
options.inJustDecodeBounds = false
val decodedBitmap = BitmapFactory.decodeStream(input, null, options) val decodedBitmap = BitmapFactory.decodeStream(input, null, options)
?: error("Decoding Bitmap from InputStream failed") ?: error("Decoding Bitmap from InputStream failed")
val rotatedBitmap = decodedBitmap.rotateToMetadataOrientation(orientation) val rotatedBitmap = decodedBitmap.rotateToMetadataOrientation(orientation)
@ -88,7 +94,7 @@ class ImageCompressor @Inject constructor(
} }
private fun calculateDecodingScale( private fun calculateDecodingScale(
inputStream: BufferedInputStream, inputStream: InputStream,
resizeMode: ResizeMode, resizeMode: ResizeMode,
options: BitmapFactory.Options options: BitmapFactory.Options
) { ) {
@ -98,14 +104,10 @@ class ImageCompressor @Inject constructor(
is ResizeMode.None -> return is ResizeMode.None -> return
} }
// Read bounds only // Read bounds only
inputStream.mark(inputStream.available())
options.inJustDecodeBounds = true options.inJustDecodeBounds = true
BitmapFactory.decodeStream(inputStream, null, options) BitmapFactory.decodeStream(inputStream, null, options)
// Set sample size based on the outWidth and outHeight // Set sample size based on the outWidth and outHeight
options.inSampleSize = options.calculateInSampleSize(width, height) options.inSampleSize = options.calculateInSampleSize(width, height)
// Now read the actual image and rotate it to match its metadata
inputStream.reset()
options.inJustDecodeBounds = false
} }
} }

Loading…
Cancel
Save