Add new APNG renderer, just for internal users for now.

This commit is contained in:
Greyson Parrelli
2024-02-02 10:08:08 -05:00
committed by Cody Henthorne
parent 34d87cf6e1
commit c3f9e5d972
151 changed files with 2425 additions and 13 deletions

View File

@@ -16,11 +16,12 @@ import com.bumptech.glide.load.resource.gif.ByteBufferGifDecoder;
import com.bumptech.glide.load.resource.gif.GifDrawable;
import com.bumptech.glide.load.resource.gif.StreamGifDecoder;
import org.signal.apng.ApngDecoder;
import org.signal.blurhash.BlurHash;
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
import org.signal.glide.blurhash.BlurHashModelLoader;
import org.signal.glide.blurhash.BlurHashResourceDecoder;
import org.signal.glide.common.io.InputStreamFactory;
import org.signal.glide.load.resource.apng.decode.APNGDecoder;
import org.thoughtcrime.securesms.badges.load.BadgeLoader;
import org.thoughtcrime.securesms.badges.load.GiftBadgeModel;
import org.thoughtcrime.securesms.badges.models.Badge;
@@ -29,9 +30,13 @@ import org.thoughtcrime.securesms.contacts.avatars.ContactPhotoLoader;
import org.thoughtcrime.securesms.crypto.AttachmentSecret;
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
import org.thoughtcrime.securesms.giph.model.ChunkedImageUrl;
import org.thoughtcrime.securesms.glide.cache.ApngDrawableTranscoder;
import org.thoughtcrime.securesms.glide.cache.ApngFrameDrawableTranscoder;
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamFactoryResourceDecoder;
import org.thoughtcrime.securesms.glide.cache.ApngInputStreamResourceDecoder;
import org.thoughtcrime.securesms.glide.cache.ByteBufferApngDecoder;
import org.thoughtcrime.securesms.glide.cache.EncryptedApngCacheEncoder;
import org.thoughtcrime.securesms.glide.cache.EncryptedApngResourceEncoder;
import org.thoughtcrime.securesms.glide.cache.EncryptedBitmapResourceEncoder;
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheDecoder;
import org.thoughtcrime.securesms.glide.cache.EncryptedCacheEncoder;
@@ -46,6 +51,7 @@ import org.signal.glide.decryptableuri.DecryptableUri;
import org.signal.glide.decryptableuri.DecryptableUriStreamLoader;
import org.thoughtcrime.securesms.mms.RegisterGlideComponents;
import org.thoughtcrime.securesms.mms.SignalGlideModule;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.stickers.StickerRemoteUri;
import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader;
import org.thoughtcrime.securesms.stories.StoryTextPostModel;
@@ -85,16 +91,25 @@ public class SignalGlideComponents implements RegisterGlideComponents {
registry.prepend(Bitmap.class, new EncryptedBitmapResourceEncoder(secret));
registry.prepend(BitmapDrawable.class, new BitmapDrawableEncoder(glide.getBitmapPool(), encryptedBitmapResourceEncoder));
ByteBufferApngDecoder byteBufferApngDecoder = new ByteBufferApngDecoder();
StreamApngDecoder streamApngDecoder = new StreamApngDecoder(byteBufferApngDecoder);
StreamFactoryApngDecoder streamFactoryApngDecoder = new StreamFactoryApngDecoder(byteBufferApngDecoder, glide, registry);
registry.prepend(InputStream.class, APNGDecoder.class, streamApngDecoder);
registry.prepend(InputStreamFactory.class, APNGDecoder.class, streamFactoryApngDecoder);
registry.prepend(ByteBuffer.class, APNGDecoder.class, byteBufferApngDecoder);
registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));
registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, streamApngDecoder));
registry.register(APNGDecoder.class, Drawable.class, new ApngFrameDrawableTranscoder());
if (SignalStore.labs().getNewApngRenderer()) {
registry.prepend(InputStream.class, ApngDecoder.class, new ApngInputStreamResourceDecoder());
registry.prepend(InputStreamFactory.class, ApngDecoder.class, new ApngInputStreamFactoryResourceDecoder());
registry.prepend(ApngDecoder.class, new EncryptedApngResourceEncoder(secret));
registry.prepend(File.class, ApngDecoder.class, new EncryptedCacheDecoder<>(secret, new ApngInputStreamResourceDecoder()));
registry.register(ApngDecoder.class, Drawable.class, new ApngDrawableTranscoder());
} else {
ByteBufferApngDecoder byteBufferApngDecoder = new ByteBufferApngDecoder();
StreamApngDecoder streamApngDecoder = new StreamApngDecoder(byteBufferApngDecoder);
StreamFactoryApngDecoder streamFactoryApngDecoder = new StreamFactoryApngDecoder(byteBufferApngDecoder, glide, registry);
registry.prepend(InputStream.class, APNGDecoder.class, streamApngDecoder);
registry.prepend(InputStreamFactory.class, APNGDecoder.class, streamFactoryApngDecoder);
registry.prepend(ByteBuffer.class, APNGDecoder.class, byteBufferApngDecoder);
registry.prepend(APNGDecoder.class, new EncryptedApngCacheEncoder(secret));
registry.prepend(File.class, APNGDecoder.class, new EncryptedCacheDecoder<>(secret, streamApngDecoder));
registry.register(APNGDecoder.class, Drawable.class, new ApngFrameDrawableTranscoder());
}
registry.prepend(BlurHash.class, Bitmap.class, new BlurHashResourceDecoder());
registry.prepend(StoryTextPostModel.class, Bitmap.class, new StoryTextPostModel.Decoder());

View File

@@ -0,0 +1,28 @@
package org.thoughtcrime.securesms.glide.cache
import android.graphics.drawable.Drawable
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.engine.Resource
import com.bumptech.glide.load.resource.drawable.DrawableResource
import com.bumptech.glide.load.resource.transcode.ResourceTranscoder
import org.signal.apng.ApngDecoder
import org.signal.apng.ApngDrawable
class ApngDrawableTranscoder : ResourceTranscoder<ApngDecoder, Drawable> {
override fun transcode(toTranscode: Resource<ApngDecoder>, options: Options): Resource<Drawable> {
val decoder = toTranscode.get()
val drawable = ApngDrawable(decoder).apply {
loopForever = true
}
return object : DrawableResource<Drawable>(drawable) {
override fun getResourceClass(): Class<Drawable> = Drawable::class.java
override fun getSize(): Int = 0
override fun recycle() {
(get() as ApngDrawable).recycle()
}
}
}
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright 2024 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.apng.ApngDecoder
import org.signal.core.util.readFully
import org.signal.glide.apng.ApngOptions
import org.signal.glide.common.io.InputStreamFactory
import java.io.ByteArrayInputStream
import java.io.IOException
class ApngInputStreamFactoryResourceDecoder : ResourceDecoder<InputStreamFactory, ApngDecoder> {
override fun handles(source: InputStreamFactory, options: Options): Boolean {
return if (options.get(ApngOptions.ANIMATE) == true) {
ApngDecoder.isApng(source.create())
} else {
false
}
}
@Throws(IOException::class)
override fun decode(source: InputStreamFactory, width: Int, height: Int, options: Options): Resource<ApngDecoder>? {
val data: ByteArray = source.create().readFully()
val decoder = ApngDecoder(ByteArrayInputStream(data))
return ApngResource(decoder, data.size)
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2024 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.apng.ApngDecoder
import org.signal.core.util.readFully
import org.signal.core.util.stream.LimitedInputStream
import org.signal.glide.apng.ApngOptions
import java.io.ByteArrayInputStream
import java.io.IOException
import java.io.InputStream
class ApngInputStreamResourceDecoder : ResourceDecoder<InputStream, ApngDecoder> {
companion object {
/** Set to match [com.bumptech.glide.load.data.InputStreamRewinder]'s read limit */
private const val READ_LIMIT: Long = 5 * 1024 * 1024
}
override fun handles(source: InputStream, options: Options): Boolean {
return if (options.get(ApngOptions.ANIMATE)!!) {
ApngDecoder.isApng(LimitedInputStream(source, READ_LIMIT))
} else {
false
}
}
@Throws(IOException::class)
override fun decode(source: InputStream, width: Int, height: Int, options: Options): Resource<ApngDecoder>? {
val data: ByteArray = source.readFully()
val decoder = ApngDecoder(ByteArrayInputStream(data))
return ApngResource(decoder, data.size)
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright 2024 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.glide.cache
import com.bumptech.glide.load.engine.Resource
import org.signal.apng.ApngDecoder
class ApngResource(private val decoder: ApngDecoder, private val size: Int) : Resource<ApngDecoder> {
override fun getResourceClass(): Class<ApngDecoder> = ApngDecoder::class.java
override fun get(): ApngDecoder = decoder
override fun getSize(): Int = size
override fun recycle() {
decoder.inputStream.close()
}
}

View File

@@ -0,0 +1,36 @@
package org.thoughtcrime.securesms.glide.cache
import com.bumptech.glide.load.EncodeStrategy
import com.bumptech.glide.load.Options
import com.bumptech.glide.load.ResourceEncoder
import com.bumptech.glide.load.engine.Resource
import org.signal.apng.ApngDecoder
import org.signal.core.util.logging.Log.tag
import org.signal.core.util.logging.Log.w
import java.io.File
import java.io.IOException
internal class EncryptedApngResourceEncoder(private val secret: ByteArray) : EncryptedCoder(), ResourceEncoder<ApngDecoder> {
override fun getEncodeStrategy(options: Options): EncodeStrategy {
return EncodeStrategy.SOURCE
}
override fun encode(data: Resource<ApngDecoder>, file: File, options: Options): Boolean {
try {
val input = data.get().inputStream
val output = createEncryptedOutputStream(secret, file)
input.reset()
input.copyTo(output)
return true
} catch (e: IOException) {
w(TAG, e)
}
return false
}
companion object {
private val TAG = tag(EncryptedApngResourceEncoder::class.java)
}
}