diff --git a/changelog.d/1483.bugfix b/changelog.d/1483.bugfix new file mode 100644 index 0000000000..57f9df1011 --- /dev/null +++ b/changelog.d/1483.bugfix @@ -0,0 +1 @@ +WebP images can't be sent as media. diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt index 0506126568..af2bca157b 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt +++ b/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 Jpeg = "image/jpeg" const val Gif = "image/gif" + const val WebP = "image/webp" const val Videos = "video/*" const val Mp4 = "video/mp4" diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt index 9fc160252b..cd968530f8 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/AndroidMediaPreProcessor.kt +++ b/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). */ private const val IMAGE_SCALE_REF_SIZE = 640 + + private val notCompressibleImageTypes = listOf(MimeTypes.Gif, MimeTypes.WebP) } private val contentResolver = context.contentResolver @@ -78,7 +80,10 @@ class AndroidMediaPreProcessor @Inject constructor( ): Result = withContext(coroutineDispatchers.computation) { runCatching { 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.isMimeTypeAudio() -> processAudio(uri, mimeType) else -> processFile(uri, mimeType) @@ -125,13 +130,11 @@ class AndroidMediaPreProcessor @Inject constructor( exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED) } ?: ExifInterface.ORIENTATION_UNDEFINED - val compressionResult = contentResolver.openInputStream(uri).use { input -> - imageCompressor.compressToTmpFile( - inputStream = requireNotNull(input), - resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE), - orientation = orientation, - ).getOrThrow() - } + val compressionResult = imageCompressor.compressToTmpFile( + inputStreamProvider = { contentResolver.openInputStream(uri)!! }, + resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE), + orientation = orientation, + ).getOrThrow() val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file) val imageInfo = compressionResult.toImageInfo( mimeType = mimeType, diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt index a619a27bd9..d936b3a5bf 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt +++ b/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 kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import java.io.BufferedInputStream import java.io.File import java.io.InputStream 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. */ suspend fun compressToTmpFile( - inputStream: InputStream, + inputStreamProvider: () -> InputStream, resizeMode: ResizeMode, format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG, orientation: Int = ExifInterface.ORIENTATION_UNDEFINED, desiredQuality: Int = 80, ): Result = withContext(Dispatchers.IO) { runCatching { - val compressedBitmap = compressToBitmap(inputStream, resizeMode, orientation).getOrThrow() + val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow() // Encode bitmap to the destination temporary file val tmpFile = context.createTmpFile(extension = "jpeg") 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]. */ fun compressToBitmap( - inputStream: InputStream, + inputStreamProvider: () -> InputStream, resizeMode: ResizeMode, orientation: Int, ): Result = runCatching { - BufferedInputStream(inputStream).use { input -> - val options = BitmapFactory.Options() + val options = BitmapFactory.Options() + // Decode bounds + inputStreamProvider().use { input -> 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) ?: error("Decoding Bitmap from InputStream failed") val rotatedBitmap = decodedBitmap.rotateToMetadataOrientation(orientation) @@ -88,7 +94,7 @@ class ImageCompressor @Inject constructor( } private fun calculateDecodingScale( - inputStream: BufferedInputStream, + inputStream: InputStream, resizeMode: ResizeMode, options: BitmapFactory.Options ) { @@ -98,14 +104,10 @@ class ImageCompressor @Inject constructor( is ResizeMode.None -> return } // Read bounds only - inputStream.mark(inputStream.available()) options.inJustDecodeBounds = true BitmapFactory.decodeStream(inputStream, null, options) // Set sample size based on the outWidth and outHeight options.inSampleSize = options.calculateInSampleSize(width, height) - // Now read the actual image and rotate it to match its metadata - inputStream.reset() - options.inJustDecodeBounds = false } }