From 62654be8ba337afa24e235d23902129f787e87d8 Mon Sep 17 00:00:00 2001 From: Vignesh Venkatasubramanian Date: Thu, 16 Feb 2023 09:37:49 -0800 Subject: [PATCH] Add AVIF support to AnimatedImageDecoder on Android 12+ Animated AVIF images have been supported on the Android platform since Android 12 via the ImageDecoder API. Enable support for AVIF on Android 12 (Build Code S) or higher devices. PiperOrigin-RevId: 510167795 --- .../glide/LoadAnimatedImageResourceTest.java | 99 ++++++++++++++++++ .../com/bumptech/glide/test/ResourceIds.java | 2 + .../src/main/res/raw/dl_world_anim_avif.avif | Bin 0 -> 2335 bytes .../src/main/res/raw/dl_world_anim_webp.webp | Bin 0 -> 2592 bytes .../drawable/AnimatedImageDecoder.java | 5 +- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 instrumentation/src/androidTest/java/com/bumptech/glide/LoadAnimatedImageResourceTest.java create mode 100644 instrumentation/src/main/res/raw/dl_world_anim_avif.avif create mode 100644 instrumentation/src/main/res/raw/dl_world_anim_webp.webp diff --git a/instrumentation/src/androidTest/java/com/bumptech/glide/LoadAnimatedImageResourceTest.java b/instrumentation/src/androidTest/java/com/bumptech/glide/LoadAnimatedImageResourceTest.java new file mode 100644 index 0000000000..691b0b9777 --- /dev/null +++ b/instrumentation/src/androidTest/java/com/bumptech/glide/LoadAnimatedImageResourceTest.java @@ -0,0 +1,99 @@ +package com.bumptech.glide; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.drawable.AnimatedImageDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Build; +import androidx.test.core.app.ApplicationProvider; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.bumptech.glide.test.GlideApp; +import com.bumptech.glide.test.ResourceIds; +import com.bumptech.glide.testutil.ConcurrencyHelper; +import com.bumptech.glide.testutil.TearDownGlide; +import java.io.IOException; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; + +/** + * Tests that Glide is able to load animated images (WebP and AVIF) stored in resources and loaded + * as {@link android.graphics.drawable.AnimatedImageDrawable}s when the underlying Android platform + * supports it. + */ +@RunWith(AndroidJUnit4.class) +public class LoadAnimatedImageResourceTest { + @Rule public final TearDownGlide tearDownGlide = new TearDownGlide(); + private final ConcurrencyHelper concurrency = new ConcurrencyHelper(); + + private Context context; + + private static final boolean IS_ANIMATED_WEBP_SUPPORTED = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; + private static final boolean IS_ANIMATED_AVIF_SUPPORTED = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; + + @Before + public void setUp() throws IOException { + MockitoAnnotations.initMocks(this); + context = ApplicationProvider.getApplicationContext(); + } + + @Test + public void loadAnimatedImageResourceId_fromInt_decodesAnimatedImageDrawable_Webp() { + assumeTrue(IS_ANIMATED_WEBP_SUPPORTED); + Drawable frame = + concurrency.get(Glide.with(context).load(ResourceIds.raw.animated_webp).submit()); + + assertThat(frame).isNotNull(); + assertThat(frame).isInstanceOf(AnimatedImageDrawable.class); + } + + @Test + public void loadAnimatedImageResourceId_fromInt_decodesAnimatedImageDrawable_Avif() { + assumeTrue(IS_ANIMATED_AVIF_SUPPORTED); + Drawable frame = + concurrency.get(Glide.with(context).load(ResourceIds.raw.animated_avif).submit()); + + assertThat(frame).isNotNull(); + assertThat(frame).isInstanceOf(AnimatedImageDrawable.class); + } + + @Test + public void loadAnimatedImageUri_fromId_decodesAnimatedImageDrawable_Webp() { + assumeTrue(IS_ANIMATED_WEBP_SUPPORTED); + Uri uri = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(context.getPackageName()) + .path(String.valueOf(ResourceIds.raw.animated_webp)) + .build(); + + Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit()); + + assertThat(frame).isNotNull(); + assertThat(frame).isInstanceOf(AnimatedImageDrawable.class); + } + + @Test + public void loadAnimatedImageUri_fromId_decodesAnimatedImageDrawable_Avif() { + assumeTrue(IS_ANIMATED_AVIF_SUPPORTED); + Uri uri = + new Uri.Builder() + .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) + .authority(context.getPackageName()) + .path(String.valueOf(ResourceIds.raw.animated_avif)) + .build(); + + Drawable frame = concurrency.get(GlideApp.with(context).load(uri).submit()); + + assertThat(frame).isNotNull(); + assertThat(frame).isInstanceOf(AnimatedImageDrawable.class); + } +} diff --git a/instrumentation/src/androidTest/java/com/bumptech/glide/test/ResourceIds.java b/instrumentation/src/androidTest/java/com/bumptech/glide/test/ResourceIds.java index b4025ab0fa..86b266da86 100644 --- a/instrumentation/src/androidTest/java/com/bumptech/glide/test/ResourceIds.java +++ b/instrumentation/src/androidTest/java/com/bumptech/glide/test/ResourceIds.java @@ -25,6 +25,8 @@ public interface raw { int opaque_interlaced_gif = getResourceId("raw", "opaque_interlaced_gif"); int webkit_logo_p3 = getResourceId("raw", "webkit_logo_p3"); int video = getResourceId("raw", "video"); + int animated_webp = getResourceId("raw", "dl_world_anim_webp"); + int animated_avif = getResourceId("raw", "dl_world_anim_avif"); } public interface drawable { diff --git a/instrumentation/src/main/res/raw/dl_world_anim_avif.avif b/instrumentation/src/main/res/raw/dl_world_anim_avif.avif new file mode 100644 index 0000000000000000000000000000000000000000..22c428a383aaa305d3ac90236cd9e4aec7720458 GIT binary patch literal 2335 zcmb7Ddpy+X8h>ZZxU{kp<4)O%9n~;1xfhK_$`W0KurcPsFLOIHV8 zL^@O?R&KlQ(n%$fQv>I5d-LOu>ba$ zG76O;1g%=L39S^}#-H+E z4n#ll>3xHnW3xiFP{Ho}CS37`G(IglXS1opL|inNNw6p3J2 z?4_6yTg+K%ilt)IBg8xt$bnppkwhB8gLbk+DtUiBXpV2tc7Y=*ALd*_1h7GNKoeC| zFnS(L8Oz$`W6Id+8v4L-v*m@GF z;y6(cB`CvFLv13gQSsKGjpC@n`X!3z@Bs$-ir5AiI}Gjj0&&G7xd#EXB|@Rjibp`2q3fhD8U}@NP`t(Jx3BBxbf%T{X1NtxG%f4L%-srUHDCXJx-qkL*m2%|N?ILMF*Mv< zmc6=6TPc>X7~kW7r34S9n_D+%nx}s)b$Vlb;I>vkO>+9!%l=gw6U2V2`Lef*iyiES zZCWvYkFUqleNLTS8@^$)T}FE0_gY(%gwb7&+K;MxJ<{AS`PMw_C?&L6?RG!#-P9#3 zsW_tJnU=H8MBXLonyU}vv(-;ae0v>#GM)D8-s}9d*#2?Q+fCH->kOxj?*{jC_+A?Z zGS!5H(qPhTw@uCkhWM1$zDDi~;nd_zql$9TTpQiIt3{pO*r~DKs^Dx)PHhTGgM-i$Ql7isKYY~4=I4w9w#SpLJ)b8P+W ze#Wa^HynMQ?~?P#_j(e0b9NBGFIzIx!Un958^%{Sr)M-@`}U0YCB1=PS(D6WS%!bk zkrS9J7oBczZOFqFMb}d=Go|_qQ9q27_3)ES`tTM0qltCyUPHqxto}JbX-xYq^;*ce zqlb((EgXEvsdHjoObAananO3YLXLNyh?DuR^qAUam1uA!E2RV*OS$EcafV%s6DP7& z`@VX%QO(8O?<`-YLfou=$wW0P*Q3H{yQlUxzC$p#sYLU`(t%H;c$zM^7QMz@#eLHyi#AafK=^+BXOMCb8t|#|5 zdG4sg8tU}R?`ylRlaskypTuZA_h^|3tgI^v)N&8?znShul& z-;v&%QahbjG1!iCk6!QZKPoL=H0-=JRPoDa8Q#tlJ*nG+qv*arkyq5t z-toX$5#)um|^R`_JO*9z^H%bR!jn^nnbt2I=9}BXR z3%0cWeD!8&Yt>Jf++`1PmcJZU586Et6Ik@!>2k)&j2pGlJC7dUduy;D)kLQ`w=zCP zbUjqpCcF_d7s)9Lp>>X2*ptwgQ$sm#wIIeMAML6a(490H>qX=JsuAX|$c8UMQfaOv z>}VVSM?9OF;$8l-x){*G&4T4-gO_8zOvF!`R-USM$Uj!dShF=#i@IW_uAuNxySR1Z zIBDcU_r8c+l5XPRuX^QOqck0gQVRg_uK7TfxSa?94rI?GM)?MqztcDrWf!$LjH|qf zpxLe|xJ(3D9&1a~-Y~zEzgzRrhY>!`8Rid4dduOVryr;}fcb5n> zURNZ|+4_`y@%d2X{=63*#Rk5Ns%-T^y9RB|n!CD!q*NR|Np?(3BT`hs3JOL!AHZc4 z6`VXnp;=NsJF$WWAgZU?LbT8$0xu1_6#eq6IxkN*zhgE;d6hQZ$uE9B@Iyha*F8bp EA2YcA*Z=?k literal 0 HcmV?d00001 diff --git a/instrumentation/src/main/res/raw/dl_world_anim_webp.webp b/instrumentation/src/main/res/raw/dl_world_anim_webp.webp new file mode 100644 index 0000000000000000000000000000000000000000..a9152404ddd200f6624ccbf9fff3f1f15645f6ff GIT binary patch literal 2592 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 O>mp&i7VR}c;`tXax2Mnm literal 0 HcmV?d00001 diff --git a/library/src/main/java/com/bumptech/glide/load/resource/drawable/AnimatedImageDecoder.java b/library/src/main/java/com/bumptech/glide/load/resource/drawable/AnimatedImageDecoder.java index e09e4d9f84..c78b1d4f76 100644 --- a/library/src/main/java/com/bumptech/glide/load/resource/drawable/AnimatedImageDecoder.java +++ b/library/src/main/java/com/bumptech/glide/load/resource/drawable/AnimatedImageDecoder.java @@ -27,7 +27,7 @@ /** * Allows decoding animated images using {@link ImageDecoder}. * - *

Supported formats: WebP on Android P+. + *

Supported formats: WebP on Android P+. AVIF on Android 12/S+. */ @RequiresApi(Build.VERSION_CODES.P) public final class AnimatedImageDecoder { @@ -61,7 +61,8 @@ boolean handles(InputStream is) throws IOException { } private boolean isHandled(ImageType imageType) { - return imageType == ImageType.ANIMATED_WEBP; + return imageType == ImageType.ANIMATED_WEBP + || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && imageType == ImageType.ANIMATED_AVIF); } @Synthetic