From 757746697847b39758a3e15f85587dfc82ee78e7 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 10 Jun 2025 20:45:59 +0200 Subject: [PATCH] rescale large thumbnails decoded as is; check thumbnail bitmap size before getting raw bytes --- CHANGELOG.md | 4 +++ .../calls/fetchers/ThumbnailFetcher.kt | 33 +++++++++++++++++++ lib/services/media/media_fetch_service.dart | 2 +- 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e8aa252..a28c86bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ All notable changes to this project will be documented in this file. - Info: show matching dynamic albums +### Fixed + +- crash when decoding some large thumbnails + ## [v1.13.2] - 2025-06-02 ### Changed diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt index 1bfb478de..ebd0245d9 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/ThumbnailFetcher.kt @@ -5,8 +5,10 @@ import android.graphics.Bitmap import android.net.Uri import android.os.Build import android.provider.MediaStore +import android.util.Log import android.util.Size import androidx.annotation.RequiresApi +import androidx.core.graphics.scale import androidx.core.net.toUri import com.bumptech.glide.Glide import com.bumptech.glide.load.DecodeFormat @@ -17,6 +19,7 @@ import deckers.thibault.aves.decoder.AvesAppGlideModule import deckers.thibault.aves.decoder.MultiPageImage import deckers.thibault.aves.utils.BitmapUtils import deckers.thibault.aves.utils.BitmapUtils.applyExifOrientation +import deckers.thibault.aves.utils.LogUtils import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes.SVG import deckers.thibault.aves.utils.MimeTypes.isVideo @@ -25,6 +28,8 @@ import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.UriUtils.tryParseId import io.flutter.plugin.common.MethodChannel +import kotlin.math.min +import kotlin.math.roundToInt class ThumbnailFetcher internal constructor( private val context: Context, @@ -77,6 +82,29 @@ class ThumbnailFetcher internal constructor( } } + if (bitmap != null) { + if (bitmap.width > width && bitmap.height > height) { + val scalingFactor: Double = min(bitmap.width.toDouble() / width, bitmap.height.toDouble() / height) + val dstWidth = (bitmap.width / scalingFactor).roundToInt() + val dstHeight = (bitmap.height / scalingFactor).roundToInt() + Log.d( + LOG_TAG, "rescale thumbnail for mimeType=$mimeType uri=$uri width=$width height=$height" + + ", with bitmap byteCount=${bitmap.byteCount} size=${bitmap.width}x${bitmap.height}" + + ", to target=${dstWidth}x${dstHeight}" + ) + bitmap = bitmap.scale(dstWidth, dstHeight) + } + + if (bitmap.byteCount > BITMAP_SIZE_DANGER_THRESHOLD) { + result.error( + "getThumbnail-large", "thumbnail bitmap dangerously large" + + " for mimeType=$mimeType uri=$uri pageId=$pageId width=$width height=$height" + + ", with bitmap byteCount=${bitmap.byteCount} size=${bitmap.width}x${bitmap.height} config=${bitmap.config?.name}", null + ) + return + } + } + // do not recycle bitmaps fetched from `ContentResolver` or Glide as their lifecycle is unknown val recycle = false val bytes = BitmapUtils.getRawBytes(bitmap, recycle = recycle) @@ -144,4 +172,9 @@ class ThumbnailFetcher internal constructor( Glide.with(context).clear(target) } } + + companion object { + private val LOG_TAG = LogUtils.createTag() + private const val BITMAP_SIZE_DANGER_THRESHOLD = 20 * (1 shl 20) // MB + } } \ No newline at end of file diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart index 057f544aa..3de23d30e 100644 --- a/lib/services/media/media_fetch_service.dart +++ b/lib/services/media/media_fetch_service.dart @@ -247,7 +247,7 @@ class PlatformMediaFetchService implements MediaFetchService { return InteropDecoding.bytesToCodec(bytes); } } on PlatformException catch (e, stack) { - if (_isUnknownVisual(mimeType)) { + if (_isUnknownVisual(mimeType) || e.code == 'getThumbnail-large') { await reportService.recordError(e, stack); } }