desktop: don't decode animation frames that cannot be seen

With media blur on, a blurred image is only revealed while the mouse is over
it, so every frame was decoded, uploaded and then blurred away again for
nobody - and the blur is a render effect re-run per frame. Frames now decode
only while the image can be seen, which also stops motion showing through a
blur that is there to hide it.

Passing the blur state to the view is why the shared signature changes; coil
drives its own animation on Android, so there is nothing to pause there.
This commit is contained in:
Narasimha-sc
2026-08-11 17:01:09 +00:00
parent cdb901a926
commit 1b6b885798
3 changed files with 7 additions and 2 deletions
@@ -3,6 +3,7 @@ package chat.simplex.common.views.chat.item
import android.os.Build.VERSION.SDK_INT
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.State
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
@@ -24,6 +25,7 @@ actual fun SimpleAndAnimatedImageView(
file: CIFile?,
imageProvider: () -> ImageGalleryProvider,
smallView: Boolean,
blurred: State<Boolean>, // coil drives the animation itself here, so there is nothing to pause
ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit
) {
val context = LocalContext.current
@@ -210,7 +210,7 @@ fun CIImageView(
val loaded = res.value
if (loaded != null && file != null) {
val (imageBitmap, data, _) = loaded
SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, smallView, @Composable { painter, onClick -> ImageView(painter, image, file.fileSource, onClick) })
SimpleAndAnimatedImageView(data, imageBitmap, file, imageProvider, smallView, blurred, @Composable { painter, onClick -> ImageView(painter, image, file.fileSource, onClick) })
} else {
imageView(previewBitmap, onClick = {
if (file != null) {
@@ -281,5 +281,6 @@ expect fun SimpleAndAnimatedImageView(
file: CIFile?,
imageProvider: () -> ImageGalleryProvider,
smallView: Boolean,
blurred: State<Boolean>,
ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit
)
@@ -1,6 +1,7 @@
package chat.simplex.common.views.chat.item
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
@@ -15,12 +16,13 @@ actual fun SimpleAndAnimatedImageView(
file: CIFile?,
imageProvider: () -> ImageGalleryProvider,
smallView: Boolean,
blurred: State<Boolean>,
ImageView: @Composable (painter: Painter, onClick: () -> Unit) -> Unit
) {
// The small view is the chat list preview: a 36dp box that the desktop layout keeps on screen the whole
// time. Decoding an animation at its own resolution to fill it would hold a raster and spend a frame of
// work per listed chat, without pause, so it keeps the still image as it did before.
val frame = if (smallView) null else rememberAnimatedImage(data, imageBitmap)
val frame = if (smallView) null else rememberAnimatedImage(data, imageBitmap, blurred)
ImageView(BitmapPainter(frame?.value ?: imageBitmap)) {
if (getLoadedFilePath(file) != null) {
ModalManager.fullscreen.showCustomModal(animated = false) { close ->