Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ object Deps {

const val volley = "com.android.volley:volley:1.2.1"

const val avifAndroid = "org.aomedia.avif.android:avif:1.0.1.262e11d"

object AndroidX {
const val androidxAnnotation = "androidx.annotation:annotation:1.1.0"
const val core = "androidx.core:core:1.3.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package com.facebook.imageformat

import com.facebook.common.webp.WebpSupportStatus
import com.facebook.imageformat.ImageFormat.FormatChecker
import java.nio.charset.Charset

/** Default image format checker that is able to determine all [DefaultImageFormats]. */
class DefaultImageFormatChecker : FormatChecker {
Expand Down Expand Up @@ -61,6 +62,9 @@ class DefaultImageFormatChecker : FormatChecker {
if (isHeifHeader(headerBytes, headerSize)) {
return DefaultImageFormats.HEIF
}
if (isAvifHeader(headerBytes)) {
return DefaultImageFormats.AVIF
}
return if (isDngHeader(headerBytes, headerSize)) {
DefaultImageFormats.DNG
} else {
Expand Down Expand Up @@ -266,5 +270,17 @@ class DefaultImageFormatChecker : FormatChecker {
headerSize >= DNG_HEADER_LENGTH &&
(ImageFormatCheckerUtils.startsWithPattern(imageHeaderBytes, DNG_HEADER_II) ||
ImageFormatCheckerUtils.startsWithPattern(imageHeaderBytes, DNG_HEADER_MM))

/**
* Checks if [imageHeaderBytes] contains 'avif'.
*
* This check may not be sufficient, though it works for most AVIF images.
* Details on AVIF can be found [here](https://aomediacodec.github.io/av1-avif/).
* */
private fun isAvifHeader(imageHeaderBytes: ByteArray): Boolean {
return imageHeaderBytes
.toString(Charset.forName("UTF-8"))
.contains("avif")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ object DefaultImageFormats {
@JvmField val WEBP_ANIMATED: ImageFormat = ImageFormat("WEBP_ANIMATED", "webp")
@JvmField val HEIF: ImageFormat = ImageFormat("HEIF", "heif")
@JvmField val DNG: ImageFormat = ImageFormat("DNG", "dng")
@JvmField val AVIF: ImageFormat = ImageFormat("AVIF", "avif")

/**
* Check if the given image format is a WebP image format (static or animated).
Expand All @@ -34,6 +35,9 @@ object DefaultImageFormats {
return isStaticWebpFormat(imageFormat) || imageFormat === WEBP_ANIMATED
}

@JvmStatic
fun isAvifFormat(imageFormat: ImageFormat) = imageFormat === AVIF

/**
* Check if the given image format is static WebP (not animated).
*
Expand Down Expand Up @@ -67,5 +71,6 @@ object DefaultImageFormats {
WEBP_EXTENDED_WITH_ALPHA,
WEBP_ANIMATED,
HEIF,
AVIF,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.facebook.imagepipeline.decoder.factory

import com.facebook.imagepipeline.decoder.ImageDecoder

interface AvifDecoderFactory {
val avifDecoder: ImageDecoder?
}
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,8 @@ private void internalParseMetaData() {
final Pair<Integer, Integer> dimensions;
if (DefaultImageFormats.isWebpFormat(imageFormat)) {
dimensions = readWebPImageSize();
} else if (DefaultImageFormats.isAvifFormat(imageFormat)) {
dimensions = null;
} else {
dimensions = readImageMetaData().getDimensions();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.facebook.imageutils

import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.InputStream
import java.nio.ByteBuffer
import java.util.concurrent.atomic.AtomicReference

object ByteBufferUtil {
private const val BUFFER_SIZE = 16384 // 16KiB
private val BUFFER_REF = AtomicReference<ByteArray>()

@Throws(IOException::class)
fun fromStream(stream: InputStream): ByteBuffer {
val outStream = ByteArrayOutputStream(BUFFER_SIZE)

var buffer = BUFFER_REF.getAndSet(null)
if (buffer == null) {
buffer = ByteArray(BUFFER_SIZE)
}

var n: Int
while (stream.read(buffer).also { n = it } >= 0) {
outStream.write(buffer, 0, n)
}

BUFFER_REF.set(buffer)

val bytes = outStream.toByteArray()

return rewind(ByteBuffer.allocateDirect(bytes.size).put(bytes))
}

private fun rewind(buffer: ByteBuffer) = buffer.position(0) as ByteBuffer
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.facebook.imagepipeline.cache.CacheKeyFactory
import com.facebook.imagepipeline.cache.MemoryCache
import com.facebook.imagepipeline.decoder.ImageDecoder
import com.facebook.imagepipeline.decoder.ProgressiveJpegConfig
import com.facebook.imagepipeline.decoder.factory.AvifDecoderFactory
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.platform.PlatformDecoderOptions
import com.facebook.imageutils.BitmapUtil
Expand All @@ -41,6 +42,7 @@ class ImagePipelineExperiments private constructor(builder: Builder) {
val webpErrorLogger: WebpErrorLogger?
val isDecodeCancellationEnabled: Boolean
val webpBitmapFactory: WebpBitmapFactory?
val avifDecoderFactory: AvifDecoderFactory?
val useDownsamplingRatioForResizing: Boolean
val useBitmapPrepareToDraw: Boolean
val useBalancedAnimationStrategy: Boolean
Expand Down Expand Up @@ -81,6 +83,7 @@ class ImagePipelineExperiments private constructor(builder: Builder) {
@JvmField var webpErrorLogger: WebpErrorLogger? = null
@JvmField var decodeCancellationEnabled = false
@JvmField var webpBitmapFactory: WebpBitmapFactory? = null
@JvmField var avifDecoderFactory: AvifDecoderFactory? = null
@JvmField var useDownsamplingRatioForResizing = false
@JvmField var useBitmapPrepareToDraw = false
@JvmField var useBalancedAnimationStrategy = false
Expand Down Expand Up @@ -401,6 +404,7 @@ class ImagePipelineExperiments private constructor(builder: Builder) {
webpErrorLogger = builder.webpErrorLogger
isDecodeCancellationEnabled = builder.decodeCancellationEnabled
webpBitmapFactory = builder.webpBitmapFactory
avifDecoderFactory = builder.avifDecoderFactory
useDownsamplingRatioForResizing = builder.useDownsamplingRatioForResizing
useBitmapPrepareToDraw = builder.useBitmapPrepareToDraw
useBalancedAnimationStrategy = builder.useBalancedAnimationStrategy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
import com.facebook.imagepipeline.cache.MemoryCache;
import com.facebook.imagepipeline.decoder.DefaultImageDecoder;
import com.facebook.imagepipeline.decoder.ImageDecoder;
import com.facebook.imagepipeline.decoder.factory.AvifDecoderFactory;
import com.facebook.imagepipeline.decoder.factory.provider.AvifDecoderFactoryProvider;
import com.facebook.imagepipeline.drawable.DrawableFactory;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.platform.PlatformDecoder;
Expand Down Expand Up @@ -250,13 +252,22 @@ private ImageDecoder getImageDecoder() {
webPDecoder = animatedFactory.getWebPDecoder();
}

final AvifDecoderFactory avifDecoderFactory = getAvifDecoderFactory();

ImageDecoder avifDecoder = null;

if (avifDecoderFactory != null) {
avifDecoder = avifDecoderFactory.getAvifDecoder();
}

if (mConfig.getImageDecoderConfig() == null) {
mImageDecoder = new DefaultImageDecoder(gifDecoder, webPDecoder, getPlatformDecoder());
mImageDecoder = new DefaultImageDecoder(gifDecoder, webPDecoder, avifDecoder, getPlatformDecoder());
} else {
mImageDecoder =
new DefaultImageDecoder(
gifDecoder,
webPDecoder,
avifDecoder,
getPlatformDecoder(),
mConfig.getImageDecoderConfig().getCustomImageDecoders());
// Add custom image formats if needed
Expand All @@ -269,6 +280,11 @@ private ImageDecoder getImageDecoder() {
return mImageDecoder;
}

@Nullable
private AvifDecoderFactory getAvifDecoderFactory() {
return AvifDecoderFactoryProvider.loadIfExists(mConfig.getPoolFactory().getBitmapPool());
}

public BufferedDiskCache getMainBufferedDiskCache() {
if (mMainBufferedDiskCache == null) {
mMainBufferedDiskCache =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ public class DefaultImageDecoder implements ImageDecoder {

private final @Nullable ImageDecoder mAnimatedGifDecoder;
private final @Nullable ImageDecoder mAnimatedWebPDecoder;
private final @Nullable ImageDecoder mAvifDecoder;
private final PlatformDecoder mPlatformDecoder;
private final Supplier<Boolean> mEnableEncodedImageColorSpaceUsage;

Expand All @@ -76,6 +77,8 @@ public class DefaultImageDecoder implements ImageDecoder {
return decodeGif(encodedImage, length, qualityInfo, options);
} else if (imageFormat == DefaultImageFormats.WEBP_ANIMATED) {
return decodeAnimatedWebp(encodedImage, length, qualityInfo, options);
} else if (imageFormat == DefaultImageFormats.AVIF) {
return decodeAvif(encodedImage, length, qualityInfo, options);
} else if (imageFormat == ImageFormat.UNKNOWN) {
throw new DecodeException("unknown image format", encodedImage);
}
Expand All @@ -88,17 +91,20 @@ public class DefaultImageDecoder implements ImageDecoder {
public DefaultImageDecoder(
@Nullable final ImageDecoder animatedGifDecoder,
@Nullable final ImageDecoder animatedWebPDecoder,
@Nullable final ImageDecoder avifDecoder,
final PlatformDecoder platformDecoder) {
this(animatedGifDecoder, animatedWebPDecoder, platformDecoder, null);
this(animatedGifDecoder, animatedWebPDecoder, avifDecoder, platformDecoder, null);
}

public DefaultImageDecoder(
@Nullable final ImageDecoder animatedGifDecoder,
@Nullable final ImageDecoder animatedWebPDecoder,
@Nullable final ImageDecoder avifDecoder,
final PlatformDecoder platformDecoder,
@Nullable Map<ImageFormat, ImageDecoder> customDecoders) {
mAnimatedGifDecoder = animatedGifDecoder;
mAnimatedWebPDecoder = animatedWebPDecoder;
mAvifDecoder = avifDecoder;
mPlatformDecoder = platformDecoder;
mCustomDecoders = customDecoders;
mEnableEncodedImageColorSpaceUsage = Suppliers.BOOLEAN_FALSE;
Expand All @@ -107,11 +113,13 @@ public DefaultImageDecoder(
public DefaultImageDecoder(
@Nullable final ImageDecoder animatedGifDecoder,
@Nullable final ImageDecoder animatedWebPDecoder,
@Nullable final ImageDecoder avifDecoder,
final PlatformDecoder platformDecoder,
@Nullable Map<ImageFormat, ImageDecoder> customDecoders,
final Supplier<Boolean> enableEncodedImageColorSpaceUsage) {
mAnimatedGifDecoder = animatedGifDecoder;
mAnimatedWebPDecoder = animatedWebPDecoder;
mAvifDecoder = avifDecoder;
mPlatformDecoder = platformDecoder;
mCustomDecoders = customDecoders;
mEnableEncodedImageColorSpaceUsage = enableEncodedImageColorSpaceUsage;
Expand Down Expand Up @@ -264,4 +272,15 @@ public CloseableStaticBitmap decodeJpeg(
}
return decodeStaticImage(encodedImage, options);
}

private @Nullable CloseableImage decodeAvif(
final EncodedImage encodedImage,
final int length,
final QualityInfo qualityInfo,
final ImageDecodeOptions options) {
if (mAvifDecoder != null) {
return mAvifDecoder.decode(encodedImage, length, qualityInfo, options);
}
return decodeStaticImage(encodedImage, options);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.facebook.imagepipeline.decoder.factory.provider

import com.facebook.imagepipeline.decoder.factory.AvifDecoderFactory
import com.facebook.imagepipeline.memory.BitmapPool

object AvifDecoderFactoryProvider {

private var checked = false

private var avifDecoderFactory: AvifDecoderFactory? = null

@JvmStatic
fun loadIfExists(bitmapPool: BitmapPool): AvifDecoderFactory? {
if (checked) return avifDecoderFactory

try {
avifDecoderFactory = Class
.forName("com.facebook.avifsupport.AvifDecoderFactoryImpl")
.getConstructor(BitmapPool::class.java)
.newInstance(bitmapPool) as? AvifDecoderFactory
} catch (_: Throwable) {
// Not available
}

checked = true
return avifDecoderFactory
}
}
1 change: 1 addition & 0 deletions samples/showcase/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ dependencies {
implementation project(':animated-gif')
implementation project(':animated-gif-lite')
implementation project(':animated-webp')
implementation project(':static-avif')
implementation project(':static-webp')
implementation project(':native-filters')
implementation project(':native-imagetranscoder')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package com.facebook.fresco.samples.showcase

import com.facebook.fresco.samples.showcase.drawee.*
import com.facebook.fresco.samples.showcase.drawee.transition.DraweeTransitionFragment
import com.facebook.fresco.samples.showcase.imageformat.avif.ImageFormatAvifFragment
import com.facebook.fresco.samples.showcase.imageformat.color.ImageFormatColorFragment
import com.facebook.fresco.samples.showcase.imageformat.datauri.ImageFormatDataUriFragment
import com.facebook.fresco.samples.showcase.imageformat.gif.ImageFormatGifFragment
Expand Down Expand Up @@ -69,6 +70,7 @@ object ExampleDatabase {
ExampleItem("Color") { ImageFormatColorFragment() },
ExampleItem("GIF") { ImageFormatGifFragment() },
ExampleItem("WebP") { ImageFormatWebpFragment() },
ExampleItem("AVIF") { ImageFormatAvifFragment() },
ExampleItem("SVG") { ImageFormatSvgFragment() },
ExampleItem("Keyframes") { ImageFormatKeyframesFragment() },
ExampleItem("Decoder Override") { ImageFormatOverrideExample() },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.facebook.fresco.samples.showcase.imageformat.avif

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.fresco.samples.showcase.BaseShowcaseFragment
import com.facebook.fresco.samples.showcase.R

class ImageFormatAvifFragment : BaseShowcaseFragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_format_avif, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val draweeAvifStatic = view.findViewById<SimpleDraweeView>(R.id.drawee_view_avif_static)
draweeAvifStatic.setImageURI(sampleUris().createAvifStaticUri())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ class ImageUriProvider constructor(context: Context) {
fun createWebpStaticUri(): Uri =
applyOverrideSettings(SAMPLE_URI_WEBP_STATIC, UriModification.NONE)

fun createAvifStaticUri(): Uri =
applyOverrideSettings(SAMPLE_URI_AVIF_STATIC, UriModification.NONE)

fun createWebpTranslucentUri(): Uri =
applyOverrideSettings(SAMPLE_URI_WEBP_TRANSLUCENT, UriModification.NONE)

Expand Down Expand Up @@ -282,6 +285,8 @@ class ImageUriProvider constructor(context: Context) {

private val SAMPLE_URI_WEBP_STATIC = "https://www.gstatic.com/webp/gallery/2.webp"

private const val SAMPLE_URI_AVIF_STATIC = "https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.12bpc.yuv422.avif"

private val SAMPLE_URI_WEBP_TRANSLUCENT = "https://www.gstatic.com/webp/gallery3/5_webp_ll.webp"

private val SAMPLE_URI_WEBP_ANIMATED = "https://www.gstatic.com/webp/animated/1.webp"
Expand Down
30 changes: 30 additions & 0 deletions samples/showcase/src/main/res/layout/fragment_format_avif.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
>

<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/drawee_view_avif_static"
android:layout_width="@dimen/drawee_width_medium"
android:layout_height="@dimen/drawee_height_medium"
android:layout_marginTop="@dimen/margin_medium"
/>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_medium"
android:text="@string/format_avif_static_help"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
</LinearLayout>
</ScrollView>
Loading