Skip to content

Commit ffef9d9

Browse files
authored
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.
1 parent 6f73a28 commit ffef9d9

File tree

4 files changed

+27
-20
lines changed

4 files changed

+27
-20
lines changed

changelog.d/1483.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
WebP images can't be sent as media.

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ object MimeTypes {
3131
const val BadJpg = "image/jpg"
3232
const val Jpeg = "image/jpeg"
3333
const val Gif = "image/gif"
34+
const val WebP = "image/webp"
3435

3536
const val Videos = "video/*"
3637
const val Mp4 = "video/mp4"

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ class AndroidMediaPreProcessor @Inject constructor(
6666
* values may surpass this limit. (i.e.: an image of `480x3000px` would have `inSampleSize=1` and be sent as is).
6767
*/
6868
private const val IMAGE_SCALE_REF_SIZE = 640
69+
70+
private val notCompressibleImageTypes = listOf(MimeTypes.Gif, MimeTypes.WebP)
6971
}
7072

7173
private val contentResolver = context.contentResolver
@@ -78,7 +80,10 @@ class AndroidMediaPreProcessor @Inject constructor(
7880
): Result<MediaUploadInfo> = withContext(coroutineDispatchers.computation) {
7981
runCatching {
8082
val result = when {
81-
mimeType.isMimeTypeImage() -> processImage(uri, mimeType, compressIfPossible && mimeType != MimeTypes.Gif)
83+
mimeType.isMimeTypeImage() -> {
84+
val shouldBeCompressed = compressIfPossible && mimeType !in notCompressibleImageTypes
85+
processImage(uri, mimeType, shouldBeCompressed)
86+
}
8287
mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType, compressIfPossible)
8388
mimeType.isMimeTypeAudio() -> processAudio(uri, mimeType)
8489
else -> processFile(uri, mimeType)
@@ -125,13 +130,11 @@ class AndroidMediaPreProcessor @Inject constructor(
125130
exifInterface?.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED)
126131
} ?: ExifInterface.ORIENTATION_UNDEFINED
127132

128-
val compressionResult = contentResolver.openInputStream(uri).use { input ->
129-
imageCompressor.compressToTmpFile(
130-
inputStream = requireNotNull(input),
131-
resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE),
132-
orientation = orientation,
133-
).getOrThrow()
134-
}
133+
val compressionResult = imageCompressor.compressToTmpFile(
134+
inputStreamProvider = { contentResolver.openInputStream(uri)!! },
135+
resizeMode = ResizeMode.Approximate(IMAGE_SCALE_REF_SIZE, IMAGE_SCALE_REF_SIZE),
136+
orientation = orientation,
137+
).getOrThrow()
135138
val thumbnailResult: ThumbnailResult = thumbnailFactory.createImageThumbnail(compressionResult.file)
136139
val imageInfo = compressionResult.toImageInfo(
137140
mimeType = mimeType,

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

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import io.element.android.libraries.androidutils.file.createTmpFile
2727
import io.element.android.libraries.di.ApplicationContext
2828
import kotlinx.coroutines.Dispatchers
2929
import kotlinx.coroutines.withContext
30-
import java.io.BufferedInputStream
3130
import java.io.File
3231
import java.io.InputStream
3332
import javax.inject.Inject
@@ -42,14 +41,14 @@ class ImageCompressor @Inject constructor(
4241
* @return a [Result] containing the resulting [ImageCompressionResult] with the temporary [File] and some metadata.
4342
*/
4443
suspend fun compressToTmpFile(
45-
inputStream: InputStream,
44+
inputStreamProvider: () -> InputStream,
4645
resizeMode: ResizeMode,
4746
format: Bitmap.CompressFormat = Bitmap.CompressFormat.JPEG,
4847
orientation: Int = ExifInterface.ORIENTATION_UNDEFINED,
4948
desiredQuality: Int = 80,
5049
): Result<ImageCompressionResult> = withContext(Dispatchers.IO) {
5150
runCatching {
52-
val compressedBitmap = compressToBitmap(inputStream, resizeMode, orientation).getOrThrow()
51+
val compressedBitmap = compressToBitmap(inputStreamProvider, resizeMode, orientation).getOrThrow()
5352
// Encode bitmap to the destination temporary file
5453
val tmpFile = context.createTmpFile(extension = "jpeg")
5554
tmpFile.outputStream().use {
@@ -65,17 +64,24 @@ class ImageCompressor @Inject constructor(
6564
}
6665

6766
/**
68-
* Decodes the [inputStream] into a [Bitmap] and applies the needed transformations (rotation, scale) based on [resizeMode] and [orientation].
67+
* Decodes the inputStream from [inputStreamProvider] into a [Bitmap] and applies the needed transformations (rotation, scale)
68+
* based on [resizeMode] and [orientation].
6969
* @return a [Result] containing the resulting [Bitmap].
7070
*/
7171
fun compressToBitmap(
72-
inputStream: InputStream,
72+
inputStreamProvider: () -> InputStream,
7373
resizeMode: ResizeMode,
7474
orientation: Int,
7575
): Result<Bitmap> = runCatching {
76-
BufferedInputStream(inputStream).use { input ->
77-
val options = BitmapFactory.Options()
76+
val options = BitmapFactory.Options()
77+
// Decode bounds
78+
inputStreamProvider().use { input ->
7879
calculateDecodingScale(input, resizeMode, options)
80+
}
81+
// Decode the actual bitmap
82+
inputStreamProvider().use { input ->
83+
// Now read the actual image and rotate it to match its metadata
84+
options.inJustDecodeBounds = false
7985
val decodedBitmap = BitmapFactory.decodeStream(input, null, options)
8086
?: error("Decoding Bitmap from InputStream failed")
8187
val rotatedBitmap = decodedBitmap.rotateToMetadataOrientation(orientation)
@@ -88,7 +94,7 @@ class ImageCompressor @Inject constructor(
8894
}
8995

9096
private fun calculateDecodingScale(
91-
inputStream: BufferedInputStream,
97+
inputStream: InputStream,
9298
resizeMode: ResizeMode,
9399
options: BitmapFactory.Options
94100
) {
@@ -98,14 +104,10 @@ class ImageCompressor @Inject constructor(
98104
is ResizeMode.None -> return
99105
}
100106
// Read bounds only
101-
inputStream.mark(inputStream.available())
102107
options.inJustDecodeBounds = true
103108
BitmapFactory.decodeStream(inputStream, null, options)
104109
// Set sample size based on the outWidth and outHeight
105110
options.inSampleSize = options.calculateInSampleSize(width, height)
106-
// Now read the actual image and rotate it to match its metadata
107-
inputStream.reset()
108-
options.inJustDecodeBounds = false
109111
}
110112
}
111113

0 commit comments

Comments
 (0)