From 8297482c248a04a2e7e2be549cfce7d98a0428cf Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Thu, 17 Oct 2024 17:03:08 -0700 Subject: [PATCH] Add support for loading vector drawables Reviewed By: oprisnik Differential Revision: D64209263 --- .../core/ImagePipelineExperiments.kt | 8 +++++ .../core/ImagePipelineFactory.java | 30 +++++++++++++++++-- .../decoder/DefaultImageDecoder.java | 29 ++++++++++++++++-- .../impl/DefaultFrescoVitoProvider.kt | 15 ++++++---- .../impl/kotlin/KFrescoVitoProvider.kt | 18 +++++++++-- 5 files changed, 86 insertions(+), 14 deletions(-) diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt index 9365e7c2b6..7ad9120ab9 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineExperiments.kt @@ -74,6 +74,7 @@ class ImagePipelineExperiments private constructor(builder: Builder) { val animationRenderFpsLimit: Int val prefetchShortcutEnabled: Boolean val platformDecoderOptions: PlatformDecoderOptions + val isBinaryXmlEnabled: Boolean class Builder(private val configBuilder: ImagePipelineConfig.Builder) { @JvmField var shouldUseDecodingBufferHelper = false @@ -127,6 +128,8 @@ class ImagePipelineExperiments private constructor(builder: Builder) { @JvmField var platformDecoderOptions = PlatformDecoderOptions() + @JvmField var isBinaryXmlEnabled = false + private fun asBuilder(block: () -> Unit): Builder { block() return this @@ -324,6 +327,10 @@ class ImagePipelineExperiments private constructor(builder: Builder) { this.platformDecoderOptions = platformDecoderOptions } + fun setBinaryXmlEnabled(binaryXmlEnabled: Boolean) = asBuilder { + isBinaryXmlEnabled = binaryXmlEnabled + } + fun build(): ImagePipelineExperiments = ImagePipelineExperiments(this) } @@ -442,6 +449,7 @@ class ImagePipelineExperiments private constructor(builder: Builder) { cancelDecodeOnCacheMiss = builder.cancelDecodeOnCacheMiss prefetchShortcutEnabled = builder.prefetchShortcutEnabled platformDecoderOptions = builder.platformDecoderOptions + isBinaryXmlEnabled = builder.isBinaryXmlEnabled } companion object { diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java index 8bb207afab..3aa31cc4b6 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/core/ImagePipelineFactory.java @@ -41,6 +41,8 @@ import com.facebook.imagepipeline.transcoder.ImageTranscoderFactory; import com.facebook.imagepipeline.transcoder.MultiImageTranscoderFactory; import com.facebook.imagepipeline.transcoder.SimpleImageTranscoderFactory; +import com.facebook.imagepipeline.xml.XmlDrawableFactory; +import com.facebook.imagepipeline.xml.XmlFormatDecoder; import com.facebook.infer.annotation.Nullsafe; import javax.annotation.Nullable; import javax.annotation.concurrent.NotThreadSafe; @@ -246,19 +248,23 @@ private ImageDecoder getImageDecoder() { webPDecoder = animatedFactory.getWebPDecoder(); } + ImageDecoder xmlDecoder = this.getXmlImageDecoder(); + if (mConfig.getImageDecoderConfig() == null) { - mImageDecoder = new DefaultImageDecoder(gifDecoder, webPDecoder, getPlatformDecoder()); + mImageDecoder = + new DefaultImageDecoder(gifDecoder, webPDecoder, xmlDecoder, getPlatformDecoder()); } else { mImageDecoder = new DefaultImageDecoder( gifDecoder, webPDecoder, + xmlDecoder, getPlatformDecoder(), mConfig.getImageDecoderConfig().getCustomImageDecoders()); // Add custom image formats if needed ImageFormatChecker.getInstance() - .setCustomImageFormatCheckers( - mConfig.getImageDecoderConfig().getCustomImageFormats()); + .setCustomImageFormatCheckers(mConfig.getImageDecoderConfig().getCustomImageFormats()) + .setBinaryXmlEnabled(mConfig.getExperiments().isBinaryXmlEnabled()); } } } @@ -418,4 +424,22 @@ public String reportData() { } return b.toString(); } + + @Nullable + public ImageDecoder getXmlImageDecoder() { + if (mConfig.getExperiments().isBinaryXmlEnabled()) { + return new XmlFormatDecoder(mConfig.getContext().getApplicationContext().getResources()); + } else { + return null; + } + } + + @Nullable + public DrawableFactory getXmlDrawableFactory() { + if (mConfig.getExperiments().isBinaryXmlEnabled()) { + return new XmlDrawableFactory(); + } else { + return null; + } + } } diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/decoder/DefaultImageDecoder.java b/imagepipeline/src/main/java/com/facebook/imagepipeline/decoder/DefaultImageDecoder.java index a87013b1d8..fbdda0c4db 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/decoder/DefaultImageDecoder.java +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/decoder/DefaultImageDecoder.java @@ -51,6 +51,7 @@ public class DefaultImageDecoder implements ImageDecoder { private final @Nullable ImageDecoder mAnimatedGifDecoder; private final @Nullable ImageDecoder mAnimatedWebPDecoder; + private final @Nullable ImageDecoder mXmlDecoder; private final PlatformDecoder mPlatformDecoder; private final Supplier mEnableEncodedImageColorSpaceUsage; @@ -77,7 +78,7 @@ public class DefaultImageDecoder implements ImageDecoder { } else if (imageFormat == DefaultImageFormats.WEBP_ANIMATED) { return decodeAnimatedWebp(encodedImage, length, qualityInfo, options); } else if (imageFormat == DefaultImageFormats.BINARY_XML) { - throw new DecodeException("unsupported image format", encodedImage); + return decodeXml(encodedImage, length, qualityInfo, options); } else if (imageFormat == ImageFormat.UNKNOWN) { throw new DecodeException("unknown image format", encodedImage); } @@ -90,17 +91,20 @@ public class DefaultImageDecoder implements ImageDecoder { public DefaultImageDecoder( @Nullable final ImageDecoder animatedGifDecoder, @Nullable final ImageDecoder animatedWebPDecoder, + @Nullable final ImageDecoder xmlDecoder, final PlatformDecoder platformDecoder) { - this(animatedGifDecoder, animatedWebPDecoder, platformDecoder, null); + this(animatedGifDecoder, animatedWebPDecoder, xmlDecoder, platformDecoder, null); } public DefaultImageDecoder( @Nullable final ImageDecoder animatedGifDecoder, @Nullable final ImageDecoder animatedWebPDecoder, + @Nullable final ImageDecoder xmlDecoder, final PlatformDecoder platformDecoder, @Nullable Map customDecoders) { mAnimatedGifDecoder = animatedGifDecoder; mAnimatedWebPDecoder = animatedWebPDecoder; + mXmlDecoder = xmlDecoder; mPlatformDecoder = platformDecoder; mCustomDecoders = customDecoders; mEnableEncodedImageColorSpaceUsage = Suppliers.BOOLEAN_FALSE; @@ -109,11 +113,13 @@ public DefaultImageDecoder( public DefaultImageDecoder( @Nullable final ImageDecoder animatedGifDecoder, @Nullable final ImageDecoder animatedWebPDecoder, + @Nullable final ImageDecoder xmlDecoder, final PlatformDecoder platformDecoder, @Nullable Map customDecoders, final Supplier enableEncodedImageColorSpaceUsage) { mAnimatedGifDecoder = animatedGifDecoder; mAnimatedWebPDecoder = animatedWebPDecoder; + mXmlDecoder = xmlDecoder; mPlatformDecoder = platformDecoder; mCustomDecoders = customDecoders; mEnableEncodedImageColorSpaceUsage = enableEncodedImageColorSpaceUsage; @@ -266,4 +272,23 @@ public CloseableStaticBitmap decodeJpeg( } return decodeStaticImage(encodedImage, options); } + + /** + * Decodes a binary xml resource. + * + * @param encodedImage input image (encoded bytes plus meta data) + * @param length amount of currently available data in bytes + * @param qualityInfo quality info for the image + * @return a CloseableStaticBitmap + */ + private @Nullable CloseableImage decodeXml( + final EncodedImage encodedImage, + final int length, + final QualityInfo qualityInfo, + final ImageDecodeOptions options) { + if (mXmlDecoder != null) { + return mXmlDecoder.decode(encodedImage, length, qualityInfo, options); + } + return null; + } } diff --git a/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/DefaultFrescoVitoProvider.kt b/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/DefaultFrescoVitoProvider.kt index 3e259451d2..2ec68734be 100644 --- a/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/DefaultFrescoVitoProvider.kt +++ b/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/DefaultFrescoVitoProvider.kt @@ -81,13 +81,16 @@ class DefaultFrescoVitoProvider( companion object { private fun createDefaultDrawableFactory(): ImageOptionsDrawableFactory { val animatedDrawableFactory = - ImagePipelineFactory.getInstance().getAnimatedDrawableFactory(null) + ImagePipelineFactory.getInstance() + .getAnimatedDrawableFactory(null) + ?.let(DrawableFactoryWrapper::wrap) val bitmapFactory = BitmapDrawableFactory() - return if (animatedDrawableFactory == null) { - bitmapFactory - } else { - ArrayVitoDrawableFactory( - bitmapFactory, DrawableFactoryWrapper.wrap(animatedDrawableFactory)) + val xmlFactory = + ImagePipelineFactory.getInstance().xmlDrawableFactory?.let(DrawableFactoryWrapper::wrap) + val factories = listOfNotNull(bitmapFactory, animatedDrawableFactory, xmlFactory) + return when (factories.size) { + 1 -> factories[0] + else -> ArrayVitoDrawableFactory(*factories.toTypedArray()) } } } diff --git a/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/kotlin/KFrescoVitoProvider.kt b/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/kotlin/KFrescoVitoProvider.kt index 39e1fa47f6..621c048a97 100644 --- a/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/kotlin/KFrescoVitoProvider.kt +++ b/vito/provider/src/main/java/com/facebook/fresco/vito/provider/impl/kotlin/KFrescoVitoProvider.kt @@ -17,6 +17,7 @@ import com.facebook.fresco.vito.core.impl.DebugOverlayHandler import com.facebook.fresco.vito.core.impl.FrescoVitoPrefetcherImpl import com.facebook.fresco.vito.core.impl.KFrescoController import com.facebook.fresco.vito.core.impl.VitoImagePipelineImpl +import com.facebook.fresco.vito.drawable.ArrayVitoDrawableFactory import com.facebook.fresco.vito.draweesupport.DrawableFactoryWrapper import com.facebook.fresco.vito.options.ImageOptionsDrawableFactory import com.facebook.fresco.vito.provider.impl.NoOpCallerContextVerifier @@ -62,8 +63,19 @@ class KFrescoVitoProvider( override fun getConfig(): FrescoVitoConfig = vitoConfig private fun getFactory(): ImageOptionsDrawableFactory? { - return ImagePipelineFactory.getInstance() - .getAnimatedDrawableFactory(null) - ?.let(DrawableFactoryWrapper::wrap) + val animatedDrawableFactory = + ImagePipelineFactory.getInstance() + .getAnimatedDrawableFactory(null) + ?.let(DrawableFactoryWrapper::wrap) + val xmlFactory = + ImagePipelineFactory.getInstance() + .getXmlDrawableFactory() + ?.let(DrawableFactoryWrapper::wrap) + val factories = listOfNotNull(animatedDrawableFactory, xmlFactory) + return when (factories.size) { + 0 -> null + 1 -> factories[0] + else -> ArrayVitoDrawableFactory(*factories.toTypedArray()) + } } }