diff --git a/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamFactoryDecoder.kt b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamFactoryDecoder.kt new file mode 100644 index 0000000000..ec766d51e3 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/glide/cache/ApngStreamFactoryDecoder.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2025 Signal Messenger, LLC + * SPDX-License-Identifier: AGPL-3.0-only + */ + +package org.thoughtcrime.securesms.glide.cache + +import com.bumptech.glide.load.Options +import com.bumptech.glide.load.ResourceDecoder +import com.bumptech.glide.load.engine.Resource +import org.signal.core.util.StreamUtil +import org.signal.glide.apng.decode.APNGDecoder +import org.signal.glide.apng.decode.APNGParser +import org.signal.glide.common.io.StreamReader +import org.thoughtcrime.securesms.glide.GlideStreamConfig +import org.thoughtcrime.securesms.mms.InputStreamFactory +import java.nio.ByteBuffer + +/** + * A variant of [ApngStreamCacheDecoder] that decodes animated PNGs from [org.thoughtcrime.securesms.mms.InputStreamFactory] sources. + */ +class ApngStreamFactoryDecoder( + private val byteBufferDecoder: ResourceDecoder +) : ResourceDecoder { + + override fun handles(source: InputStreamFactory, options: Options): Boolean { + return if (options.get(ApngOptions.ANIMATE) == true) { + return APNGParser.isAPNG(LimitedReader(StreamReader(source.create()), GlideStreamConfig.markReadLimitBytes)) + } else { + false + } + } + + override fun decode( + source: InputStreamFactory, + width: Int, + height: Int, + options: Options + ): Resource? { + val data = StreamUtil.readFully(source.create()) + val byteBuffer = ByteBuffer.wrap(data) + return byteBufferDecoder.decode(byteBuffer, width, height, options) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideComponents.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideComponents.java index eef7d5f9c2..67ae6a4efe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideComponents.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideComponents.java @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.glide.OkHttpUrlLoader; import org.thoughtcrime.securesms.glide.cache.ApngBufferCacheDecoder; import org.thoughtcrime.securesms.glide.cache.ApngFrameDrawableTranscoder; import org.thoughtcrime.securesms.glide.cache.ApngStreamCacheDecoder; +import org.thoughtcrime.securesms.glide.cache.ApngStreamFactoryDecoder; import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheEncoder; import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder; import org.thoughtcrime.securesms.glide.cache.EncryptedCacheDecoder; @@ -78,10 +79,12 @@ public class SignalGlideComponents implements RegisterGlideComponents { registry.prepend(Bitmap.class, new EncryptedBitmapResourceEncoder(secret)); registry.prepend(BitmapDrawable.class, new BitmapDrawableEncoder(glide.getBitmapPool(), encryptedBitmapResourceEncoder)); - ApngBufferCacheDecoder apngBufferCacheDecoder = new ApngBufferCacheDecoder(); - ApngStreamCacheDecoder apngStreamCacheDecoder = new ApngStreamCacheDecoder(apngBufferCacheDecoder); + ApngBufferCacheDecoder apngBufferCacheDecoder = new ApngBufferCacheDecoder(); + ApngStreamCacheDecoder apngStreamCacheDecoder = new ApngStreamCacheDecoder(apngBufferCacheDecoder); + ApngStreamFactoryDecoder apngStreamFactoryDecoder = new ApngStreamFactoryDecoder(apngBufferCacheDecoder); registry.prepend(InputStream.class, APNGDecoder.class, apngStreamCacheDecoder); + registry.prepend(InputStreamFactory.class, APNGDecoder.class, apngStreamFactoryDecoder); registry.prepend(ByteBuffer.class, APNGDecoder.class, apngBufferCacheDecoder); registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret)); registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, apngStreamCacheDecoder));