Fix PNG animations.

Adds an `InputStreamFactory` APNG decoder so images animate correctly when loaded via the new `DecryptableUriStreamLoader`.
This commit is contained in:
jeffrey-signal
2025-08-12 09:56:46 -04:00
committed by GitHub
parent ee657cb075
commit 709ff90d35
2 changed files with 49 additions and 2 deletions

View File

@@ -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<ByteBuffer, APNGDecoder>
) : ResourceDecoder<InputStreamFactory, APNGDecoder> {
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<APNGDecoder>? {
val data = StreamUtil.readFully(source.create())
val byteBuffer = ByteBuffer.wrap(data)
return byteBufferDecoder.decode(byteBuffer, width, height, options)
}
}

View File

@@ -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));