diff --git a/animated-base/src/main/java/com/facebook/fresco/animation/factory/ExperimentalBitmapAnimationDrawableFactory.java b/animated-base/src/main/java/com/facebook/fresco/animation/factory/ExperimentalBitmapAnimationDrawableFactory.java index 5bcac36502..b3f0b4bb16 100644 --- a/animated-base/src/main/java/com/facebook/fresco/animation/factory/ExperimentalBitmapAnimationDrawableFactory.java +++ b/animated-base/src/main/java/com/facebook/fresco/animation/factory/ExperimentalBitmapAnimationDrawableFactory.java @@ -9,6 +9,7 @@ import android.graphics.Bitmap; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.net.Uri; import com.facebook.cache.common.CacheKey; import com.facebook.common.internal.Supplier; @@ -35,17 +36,20 @@ import com.facebook.imagepipeline.animated.impl.AnimatedFrameCache; import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory; import com.facebook.imagepipeline.cache.CountingMemoryCache; -import com.facebook.imagepipeline.drawable.DrawableFactory; +import com.facebook.imagepipeline.drawable.BaseDrawableFactory; import com.facebook.imagepipeline.image.CloseableAnimatedImage; import com.facebook.imagepipeline.image.CloseableImage; + import java.util.concurrent.ExecutorService; import java.util.concurrent.ScheduledExecutorService; +import javax.annotation.Nullable; + /** * Animation factory for {@link AnimatedDrawable2}. * */ -public class ExperimentalBitmapAnimationDrawableFactory implements DrawableFactory { +public class ExperimentalBitmapAnimationDrawableFactory extends BaseDrawableFactory { public static final int CACHING_STRATEGY_NO_CACHE = 0; public static final int CACHING_STRATEGY_FRESCO_CACHE = 1; @@ -92,6 +96,22 @@ public AnimatedDrawable2 createDrawable(CloseableImage image) { ((CloseableAnimatedImage) image).getImageResult())); } + @Nullable + @Override + public Drawable createDrawable(Drawable previousDrawable, CloseableImage image) { + if (previousDrawable instanceof AnimatedDrawable2) { + ((AnimatedDrawable2) previousDrawable).updateProgressiveAnimation( + createAnimationBackend(((CloseableAnimatedImage) image).getImageResult())); + return previousDrawable; + } + return null; + } + + @Override + public boolean needPreviousDrawable(Drawable previousDrawable, CloseableImage image) { + return previousDrawable instanceof AnimatedDrawable2; + } + private AnimationBackend createAnimationBackend(AnimatedImageResult animatedImageResult) { AnimatedDrawableBackend animatedDrawableBackend = createAnimatedDrawableBackend(animatedImageResult); diff --git a/animated-base/src/main/java/com/facebook/imagepipeline/animated/base/AnimatedImage.java b/animated-base/src/main/java/com/facebook/imagepipeline/animated/base/AnimatedImage.java index 17f03dfce8..4090645910 100644 --- a/animated-base/src/main/java/com/facebook/imagepipeline/animated/base/AnimatedImage.java +++ b/animated-base/src/main/java/com/facebook/imagepipeline/animated/base/AnimatedImage.java @@ -97,4 +97,13 @@ public interface AnimatedImage { * @return the frame info */ AnimatedDrawableFrameInfo getFrameInfo(int frameNumber); + + /** + * Return whether the image is partial. This may be due to a cancellation or failure while the + * file was being downloaded or because only part of the image was requested or only part of image + * has been downloaded for the time being. + * + * @return whether the image is partial + */ + boolean isPartial(); } diff --git a/animated-base/src/main/java/com/facebook/imagepipeline/animated/impl/AnimatedDrawableBackendImpl.java b/animated-base/src/main/java/com/facebook/imagepipeline/animated/impl/AnimatedDrawableBackendImpl.java index 2b7f7107e4..cce068cb97 100644 --- a/animated-base/src/main/java/com/facebook/imagepipeline/animated/impl/AnimatedDrawableBackendImpl.java +++ b/animated-base/src/main/java/com/facebook/imagepipeline/animated/impl/AnimatedDrawableBackendImpl.java @@ -91,7 +91,7 @@ public int getFrameCount() { @Override public int getLoopCount() { - return mAnimatedImage.getLoopCount(); + return mAnimatedImage.isPartial() ? 1 : mAnimatedImage.getLoopCount(); } @Override diff --git a/animated-drawable/src/main/java/com/facebook/fresco/animation/drawable/AnimatedDrawable2.java b/animated-drawable/src/main/java/com/facebook/fresco/animation/drawable/AnimatedDrawable2.java index 688621af87..c41f9d0d16 100644 --- a/animated-drawable/src/main/java/com/facebook/fresco/animation/drawable/AnimatedDrawable2.java +++ b/animated-drawable/src/main/java/com/facebook/fresco/animation/drawable/AnimatedDrawable2.java @@ -66,6 +66,7 @@ void onDraw( private long mLastFrameAnimationTimeMs; private long mExpectedRenderTimeMs; private int mLastDrawnFrameNumber; + private int mProgressiveLastFrameNumber = -1; private long mFrameSchedulingDelayMs = DEFAULT_FRAME_SCHEDULING_DELAY_MS; private long mFrameSchedulingOffsetMs = DEFAULT_FRAME_SCHEDULING_OFFSET_MS; @@ -129,16 +130,27 @@ public int getIntrinsicHeight() { */ @Override public void start() { - if (mIsRunning || mAnimationBackend == null || mAnimationBackend.getFrameCount() <= 1) { + boolean progressiveStart = mProgressiveLastFrameNumber >= 0; + boolean frameCountValid = + mAnimationBackend.getFrameCount() <= (progressiveStart ? mProgressiveLastFrameNumber : 1); + if (mIsRunning || mAnimationBackend == null || frameCountValid) { return; } mIsRunning = true; - mStartTimeMs = now(); + if (progressiveStart) { + mLastFrameAnimationTimeMs = + mFrameScheduler.getTargetRenderTimeMs(mProgressiveLastFrameNumber); + mLastDrawnFrameNumber = mProgressiveLastFrameNumber; + mStartTimeMs = now() - mLastFrameAnimationTimeMs; + } else { + mLastFrameAnimationTimeMs = -1; + mLastDrawnFrameNumber = -1; + mStartTimeMs = now(); + } mExpectedRenderTimeMs = mStartTimeMs; - mLastFrameAnimationTimeMs = -1; - mLastDrawnFrameNumber = -1; invalidateSelf(); mAnimationListener.onAnimationStart(this); + mProgressiveLastFrameNumber = -1; } /** @@ -190,20 +202,21 @@ public void draw(Canvas canvas) { int frameNumberToDraw = mFrameScheduler.getFrameNumberToRender( animationTimeMs, mLastFrameAnimationTimeMs); - // Check if the animation is finished and draw last frame if so if (frameNumberToDraw == FrameScheduler.FRAME_NUMBER_DONE) { frameNumberToDraw = mAnimationBackend.getFrameCount() - 1; mAnimationListener.onAnimationStop(this); mIsRunning = false; } else if (frameNumberToDraw == 0) { - if (mLastDrawnFrameNumber != -1 && actualRenderTimeStartMs >= mExpectedRenderTimeMs) { + if (isInfiniteAnimation() && mLastDrawnFrameNumber != -1 + && actualRenderTimeStartMs >= mExpectedRenderTimeMs) { mAnimationListener.onAnimationRepeat(this); } } // Draw the frame - boolean frameDrawn = mAnimationBackend.drawFrame(this, canvas, frameNumberToDraw); + boolean frameDrawn = + frameNumberToDraw != -1 && mAnimationBackend.drawFrame(this, canvas, frameNumberToDraw); if (frameDrawn) { // Notify listeners that we draw a new frame and // that the animation might be repeated @@ -212,7 +225,7 @@ public void draw(Canvas canvas) { } // Log potential dropped frames - if (!frameDrawn) { + if (frameNumberToDraw != -1 && !frameDrawn) { onFrameDropped(); } @@ -485,4 +498,15 @@ public void dropCaches() { mAnimationBackend.clear(); } } + + /** + * In order to support progressive animation. When more data is downloaded, the newly generated + * AnimationBackend will be set and the last frame number will be recorded. + * + * @param animationBackend the newly generated AnimationBackend + */ + public void updateProgressiveAnimation(@Nullable AnimationBackend animationBackend) { + mProgressiveLastFrameNumber = mLastDrawnFrameNumber; + setAnimationBackend(animationBackend); + } } diff --git a/animated-drawable/src/main/java/com/facebook/fresco/animation/frame/DropFramesFrameScheduler.java b/animated-drawable/src/main/java/com/facebook/fresco/animation/frame/DropFramesFrameScheduler.java index 5c409bbd1c..15f2f5d4de 100644 --- a/animated-drawable/src/main/java/com/facebook/fresco/animation/frame/DropFramesFrameScheduler.java +++ b/animated-drawable/src/main/java/com/facebook/fresco/animation/frame/DropFramesFrameScheduler.java @@ -27,6 +27,10 @@ public DropFramesFrameScheduler(AnimationInformation animationInformation) { @Override public int getFrameNumberToRender(long animationTimeMs, long lastFrameTimeMs) { if (!isInfiniteAnimation()) { + long loopDurationMs = getLoopDurationMs(); + if (loopDurationMs == 0) { + return FRAME_NUMBER_DONE; + } long loopCount = animationTimeMs / getLoopDurationMs(); if (loopCount >= mAnimationInformation.getLoopCount()) { return FRAME_NUMBER_DONE; diff --git a/animated-gif-lite/src/main/java/com/facebook/animated/giflite/draw/MovieAnimatedImage.java b/animated-gif-lite/src/main/java/com/facebook/animated/giflite/draw/MovieAnimatedImage.java index 38fe57ea4d..6883ef5c55 100644 --- a/animated-gif-lite/src/main/java/com/facebook/animated/giflite/draw/MovieAnimatedImage.java +++ b/animated-gif-lite/src/main/java/com/facebook/animated/giflite/draw/MovieAnimatedImage.java @@ -92,5 +92,10 @@ public AnimatedDrawableFrameInfo getFrameInfo(int frameNumber) { AnimatedDrawableFrameInfo.BlendOperation.BLEND_WITH_PREVIOUS, mFrames[frameNumber].getDisposalMode()); } + + @Override + public boolean isPartial() { + return false; + } } diff --git a/animated-gif/src/main/java/com/facebook/animated/gif/GifImage.java b/animated-gif/src/main/java/com/facebook/animated/gif/GifImage.java index cf81d1606b..4887e94464 100644 --- a/animated-gif/src/main/java/com/facebook/animated/gif/GifImage.java +++ b/animated-gif/src/main/java/com/facebook/animated/gif/GifImage.java @@ -188,6 +188,11 @@ public AnimatedDrawableFrameInfo getFrameInfo(int frameNumber) { } } + @Override + public boolean isPartial() { + return false; + } + private static AnimatedDrawableFrameInfo.DisposalMethod fromGifDisposalMethod(int disposalMode) { if (disposalMode == 0 /* DISPOSAL_UNSPECIFIED */) { return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_DO_NOT; diff --git a/animated-webp/src/main/java/com/facebook/animated/webp/WebPImage.java b/animated-webp/src/main/java/com/facebook/animated/webp/WebPImage.java index 3d264d60d0..6185093aca 100644 --- a/animated-webp/src/main/java/com/facebook/animated/webp/WebPImage.java +++ b/animated-webp/src/main/java/com/facebook/animated/webp/WebPImage.java @@ -169,6 +169,11 @@ public AnimatedDrawableFrameInfo getFrameInfo(int frameNumber) { } } + @Override + public boolean isPartial() { + return nativeGetPartial(); + } + private static native WebPImage nativeCreateFromDirectByteBuffer(ByteBuffer buffer); private static native WebPImage nativeCreateFromNativeMemory(long nativePtr, int sizeInBytes); private native int nativeGetWidth(); @@ -179,6 +184,7 @@ public AnimatedDrawableFrameInfo getFrameInfo(int frameNumber) { private native int nativeGetLoopCount(); private native WebPFrame nativeGetFrame(int frameNumber); private native int nativeGetSizeInBytes(); + private native boolean nativeGetPartial(); private native void nativeDispose(); private native void nativeFinalize(); } diff --git a/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/DefaultDrawableFactory.java b/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/DefaultDrawableFactory.java index b9124fcf52..126bb35a2c 100644 --- a/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/DefaultDrawableFactory.java +++ b/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/DefaultDrawableFactory.java @@ -12,6 +12,7 @@ import android.graphics.drawable.Drawable; import android.media.ExifInterface; import com.facebook.drawee.drawable.OrientedDrawable; +import com.facebook.imagepipeline.drawable.BaseDrawableFactory; import com.facebook.imagepipeline.drawable.DrawableFactory; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.CloseableStaticBitmap; @@ -19,7 +20,7 @@ import com.facebook.imagepipeline.systrace.FrescoSystrace; import javax.annotation.Nullable; -public class DefaultDrawableFactory implements DrawableFactory { +public class DefaultDrawableFactory extends BaseDrawableFactory { private final Resources mResources; private final @Nullable DrawableFactory mAnimatedDrawableFactory; @@ -68,6 +69,21 @@ public Drawable createDrawable(CloseableImage closeableImage) { } } + @Nullable + @Override + public Drawable createDrawable(Drawable previousDrawable, CloseableImage image) { + return mAnimatedDrawableFactory.createDrawable(previousDrawable, image); + } + + @Override + public boolean needPreviousDrawable(Drawable previousDrawable, CloseableImage image) { + if (mAnimatedDrawableFactory != null && mAnimatedDrawableFactory.supportsImageType(image)) { + return mAnimatedDrawableFactory.needPreviousDrawable(previousDrawable, image); + } else { + return false; + } + } + /* Returns true if there is anything to rotate using the rotation angle */ private static boolean hasTransformableRotationAngle( CloseableStaticBitmap closeableStaticBitmap) { diff --git a/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/PipelineDraweeController.java b/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/PipelineDraweeController.java index 28da1071d0..1fe0859e84 100644 --- a/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/PipelineDraweeController.java +++ b/drawee-backends/drawee-pipeline/src/main/java/com/facebook/drawee/backends/pipeline/PipelineDraweeController.java @@ -269,7 +269,9 @@ protected Drawable createDrawable(CloseableReference image) { return drawable; } - drawable = mDefaultDrawableFactory.createDrawable(closeableImage); + drawable = mDefaultDrawableFactory.needPreviousDrawable(mDrawable, closeableImage) + ? mDefaultDrawableFactory.createDrawable(mDrawable, closeableImage) + : mDefaultDrawableFactory.createDrawable(closeableImage); if (drawable != null) { return drawable; } @@ -288,7 +290,9 @@ protected Drawable createDrawable(CloseableReference image) { } for (DrawableFactory factory : drawableFactories) { if (factory.supportsImageType(closeableImage)) { - Drawable drawable = factory.createDrawable(closeableImage); + Drawable drawable = factory.needPreviousDrawable(mDrawable, closeableImage) + ? factory.createDrawable(mDrawable, closeableImage) + : factory.createDrawable(closeableImage); if (drawable != null) { return drawable; } diff --git a/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeController.java b/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeController.java index f38519bfe8..e7e6d37ab1 100644 --- a/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeController.java +++ b/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeController.java @@ -96,7 +96,7 @@ public static InternalForwardingListener createInternal( private @Nullable String mContentDescription; private @Nullable DataSource mDataSource; private @Nullable T mFetchedImage; - private @Nullable Drawable mDrawable; + protected @Nullable Drawable mDrawable; private boolean mJustConstructed = true; public AbstractDraweeController( diff --git a/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeControllerBuilder.java b/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeControllerBuilder.java index e9a35da73a..d68ac05a77 100644 --- a/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeControllerBuilder.java +++ b/drawee/src/main/java/com/facebook/drawee/controller/AbstractDraweeControllerBuilder.java @@ -37,6 +37,24 @@ public abstract class AbstractDraweeControllerBuilder < INFO> implements SimpleDraweeControllerBuilder { + private static class ProgressiveAnimationsListener extends BaseControllerListener { + private AbstractDraweeController mAbstractDraweeController; + + void setAbstractDraweeController(AbstractDraweeController abstractDraweeController) { + mAbstractDraweeController = abstractDraweeController; + } + + @Override + public void onIntermediateImageSet(String id, @Nullable Object imageInfo) { + if (mAbstractDraweeController != null && mAbstractDraweeController.getAnimatable() != null) { + mAbstractDraweeController.getAnimatable().start(); + } + } + } + + private static final ProgressiveAnimationsListener sProgressiveAnimationsListener + = new ProgressiveAnimationsListener(); + private static final ControllerListener sAutoPlayAnimationsListener = new BaseControllerListener() { @Override @@ -423,6 +441,8 @@ protected void maybeAttachListeners(AbstractDraweeController controller) { if (mAutoPlayAnimations) { controller.addControllerListener(sAutoPlayAnimationsListener); } + sProgressiveAnimationsListener.setAbstractDraweeController(controller); + controller.addControllerListener(sProgressiveAnimationsListener); } /** Installs a retry manager (if specified) to the given controller. */ diff --git a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/BaseDrawableFactory.java b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/BaseDrawableFactory.java new file mode 100644 index 0000000000..8980fbc3c8 --- /dev/null +++ b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/BaseDrawableFactory.java @@ -0,0 +1,32 @@ +package com.facebook.imagepipeline.drawable; + +import android.graphics.drawable.Drawable; + +import com.facebook.imagepipeline.image.CloseableImage; + +import javax.annotation.Nullable; + +public class BaseDrawableFactory implements DrawableFactory { + + @Override + public boolean supportsImageType(CloseableImage image) { + return false; + } + + @Nullable + @Override + public Drawable createDrawable(CloseableImage image) { + return null; + } + + @Nullable + @Override + public Drawable createDrawable(Drawable previousDrawable, CloseableImage image) { + return null; + } + + @Override + public boolean needPreviousDrawable(Drawable previousDrawable, CloseableImage image) { + return false; + } +} diff --git a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/DrawableFactory.java b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/DrawableFactory.java index 0591c2dbe9..e4c25750ce 100644 --- a/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/DrawableFactory.java +++ b/imagepipeline-base/src/main/java/com/facebook/imagepipeline/drawable/DrawableFactory.java @@ -33,4 +33,26 @@ public interface DrawableFactory { */ @Nullable Drawable createDrawable(CloseableImage image); + + /** + * Create or update a drawable for the given image and the previous drawable. + * It is guaranteed that this method is only called if + * {@link #needPreviousDrawable(Drawable, CloseableImage)} returned true. + * + * @param previousDrawable the previous drawable + * @param image the image to create the drawable for + * @return the Drawable for the image and previous drawable or null if an error occurred + */ + @Nullable + Drawable createDrawable(Drawable previousDrawable, CloseableImage image); + + /** + * Returns true if the factory need previous drawable to create or update a Drawable for the given + * image. + * + * @param previousDrawable the previous drawable + * @param image the image to check + * @return true if previous drawable is needed + */ + boolean needPreviousDrawable(Drawable previousDrawable, CloseableImage image); } diff --git a/imagepipeline/src/main/java/com/facebook/imagepipeline/producers/DecodeProducer.java b/imagepipeline/src/main/java/com/facebook/imagepipeline/producers/DecodeProducer.java index fe48736eb4..cb766f9542 100644 --- a/imagepipeline/src/main/java/com/facebook/imagepipeline/producers/DecodeProducer.java +++ b/imagepipeline/src/main/java/com/facebook/imagepipeline/producers/DecodeProducer.java @@ -237,8 +237,10 @@ protected boolean updateDecodeJob(EncodedImage ref, @Status int status) { /** Performs the decode synchronously. */ private void doDecode(EncodedImage encodedImage, @Status int status) { - // do not run for partial results of anything except JPEG - if (encodedImage.getImageFormat() != DefaultImageFormats.JPEG && isNotLast(status)) { + // do not run for partial results of anything except JPEG and WEBP_ANIMATED + if (encodedImage.getImageFormat() != DefaultImageFormats.JPEG + && encodedImage.getImageFormat() != DefaultImageFormats.WEBP_ANIMATED + && isNotLast(status)) { return; } @@ -272,7 +274,7 @@ private void doDecode(EncodedImage encodedImage, @Status int status) { : getIntermediateImageEndOffset(encodedImage); QualityInfo quality = isLastAndComplete || isPlaceholder ? ImmutableQualityInfo.FULL_QUALITY - : getQualityInfo(); + : getQualityInfo(encodedImage); mProducerListener.onProducerStart(mProducerContext.getId(), PRODUCER_NAME); CloseableImage image = null; @@ -423,7 +425,7 @@ private void handleCancellation() { protected abstract int getIntermediateImageEndOffset(EncodedImage encodedImage); - protected abstract QualityInfo getQualityInfo(); + protected abstract QualityInfo getQualityInfo(EncodedImage encodedImage); } private class LocalImagesProgressiveDecoder extends ProgressiveDecoder { @@ -450,7 +452,7 @@ protected int getIntermediateImageEndOffset(EncodedImage encodedImage) { } @Override - protected QualityInfo getQualityInfo() { + protected QualityInfo getQualityInfo(EncodedImage encodedImag) { return ImmutableQualityInfo.of(0, false, false); } } @@ -502,12 +504,20 @@ protected synchronized boolean updateDecodeJob(EncodedImage encodedImage, @Statu @Override protected int getIntermediateImageEndOffset(EncodedImage encodedImage) { - return mProgressiveJpegParser.getBestScanEndOffset(); + if (encodedImage.getImageFormat() == DefaultImageFormats.JPEG) { + return mProgressiveJpegParser.getBestScanEndOffset(); + } else { + return encodedImage.getSize(); + } } @Override - protected QualityInfo getQualityInfo() { - return mProgressiveJpegConfig.getQualityInfo(mProgressiveJpegParser.getBestScanNumber()); + protected QualityInfo getQualityInfo(EncodedImage encodedImage) { + if (encodedImage.getImageFormat() == DefaultImageFormats.JPEG) { + return mProgressiveJpegConfig.getQualityInfo(mProgressiveJpegParser.getBestScanNumber()); + } else { + return ImmutableQualityInfo.FULL_QUALITY; + } } } } diff --git a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java index 29921132fc..41e8764802 100644 --- a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java +++ b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/color/ColorImageExample.java @@ -20,6 +20,7 @@ import com.facebook.imageformat.ImageFormatCheckerUtils; import com.facebook.imagepipeline.common.ImageDecodeOptions; import com.facebook.imagepipeline.decoder.ImageDecoder; +import com.facebook.imagepipeline.drawable.BaseDrawableFactory; import com.facebook.imagepipeline.drawable.DrawableFactory; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.EncodedImage; @@ -178,7 +179,7 @@ public CloseableImage decode( * Color drawable factory that is able to render a {@link CloseableColorImage} by creating * a new {@link ColorDrawable} for the given color. */ - public static class ColorDrawableFactory implements DrawableFactory { + public static class ColorDrawableFactory extends BaseDrawableFactory { @Override public boolean supportsImageType(CloseableImage image) { diff --git a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/keyframes/KeyframesDecoderExample.java b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/keyframes/KeyframesDecoderExample.java index 3d7eb915f8..39908051ec 100644 --- a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/keyframes/KeyframesDecoderExample.java +++ b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/keyframes/KeyframesDecoderExample.java @@ -18,6 +18,7 @@ import com.facebook.imageformat.ImageFormatCheckerUtils; import com.facebook.imagepipeline.common.ImageDecodeOptions; import com.facebook.imagepipeline.decoder.ImageDecoder; +import com.facebook.imagepipeline.drawable.BaseDrawableFactory; import com.facebook.imagepipeline.drawable.DrawableFactory; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.EncodedImage; @@ -149,7 +150,7 @@ public boolean isStateful() { } } - private static class KeyframesDrawableFactory implements DrawableFactory { + private static class KeyframesDrawableFactory extends BaseDrawableFactory { @Override public boolean supportsImageType(CloseableImage image) { diff --git a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java index 68cfdc3852..660bba25ba 100644 --- a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java +++ b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/svg/SvgDecoderExample.java @@ -20,6 +20,7 @@ import com.facebook.imageformat.ImageFormatCheckerUtils; import com.facebook.imagepipeline.common.ImageDecodeOptions; import com.facebook.imagepipeline.decoder.ImageDecoder; +import com.facebook.imagepipeline.drawable.BaseDrawableFactory; import com.facebook.imagepipeline.drawable.DrawableFactory; import com.facebook.imagepipeline.image.CloseableImage; import com.facebook.imagepipeline.image.EncodedImage; @@ -131,7 +132,7 @@ public CloseableImage decode( /** * SVG drawable factory that creates {@link PictureDrawable}s for SVG images. */ - public static class SvgDrawableFactory implements DrawableFactory { + public static class SvgDrawableFactory extends BaseDrawableFactory { @Override public boolean supportsImageType(CloseableImage image) { diff --git a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/webp/ImageFormatWebpFragment.java b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/webp/ImageFormatWebpFragment.java index f4dccd028b..7f2dfac9ad 100644 --- a/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/webp/ImageFormatWebpFragment.java +++ b/samples/showcase/src/main/java/com/facebook/fresco/samples/showcase/imageformat/webp/ImageFormatWebpFragment.java @@ -25,6 +25,8 @@ import com.facebook.fresco.samples.showcase.BaseShowcaseFragment; import com.facebook.fresco.samples.showcase.R; import com.facebook.fresco.samples.showcase.misc.CheckerBoardDrawable; +import com.facebook.imagepipeline.request.ImageRequest; +import com.facebook.imagepipeline.request.ImageRequestBuilder; /** * This fragment displays different WebP images. @@ -65,13 +67,30 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { }); final SimpleDraweeView draweeWebpAnimated = view.findViewById(R.id.drawee_view_webp_animated); + ImageRequest request = ImageRequestBuilder.newBuilderWithSource(sampleUris().createWebpAnimatedUri()) + .setProgressiveRenderingEnabled(false).build(); draweeWebpAnimated.setController( Fresco.newDraweeControllerBuilder() .setAutoPlayAnimations(true) .setOldController(draweeWebpAnimated.getController()) - .setUri(sampleUris().createWebpAnimatedUri()) + .setImageRequest(request) .build()); + final SwitchCompat switchProgressive = view.findViewById(R.id.switch_progressive_animator); + switchProgressive.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ImageRequest request = ImageRequestBuilder.newBuilderWithSource(sampleUris().createWebpAnimatedUri()) + .setProgressiveRenderingEnabled(isChecked).build(); + draweeWebpAnimated.setController( + Fresco.newDraweeControllerBuilder() + .setAutoPlayAnimations(true) + .setOldController(draweeWebpAnimated.getController()) + .setImageRequest(request) + .build()); + } + }); + final TextView supportStatusTextView = view.findViewById(R.id.text_webp_support_status); final StringBuilder sb = new StringBuilder(); sb.append("WebpSupportStatus.sIsSimpleWebpSupported = ") diff --git a/samples/showcase/src/main/res/layout/fragment_format_webp.xml b/samples/showcase/src/main/res/layout/fragment_format_webp.xml index b5b52f2028..e1ec9ba3f3 100644 --- a/samples/showcase/src/main/res/layout/fragment_format_webp.xml +++ b/samples/showcase/src/main/res/layout/fragment_format_webp.xml @@ -71,6 +71,14 @@ android:textAppearance="?android:attr/textAppearanceSmall" /> + + Image Format Checker board background + Checker progressive animator Portrait aspect ratio diff --git a/static-webp/src/main/jni/static-webp/webp.cpp b/static-webp/src/main/jni/static-webp/webp.cpp index 3f93498b4c..efcd4e0455 100644 --- a/static-webp/src/main/jni/static-webp/webp.cpp +++ b/static-webp/src/main/jni/static-webp/webp.cpp @@ -86,6 +86,8 @@ struct WebPImageNativeContext { /** Reference counter. Instance is deleted when it goes from 1 to 0 */ size_t refCount; + bool partial; + #if EXTRA_LOGGING ~WebPImageNativeContext() { __android_log_write(ANDROID_LOG_DEBUG, WEBP_IMAGE_LOG_TAG, "WebPImageNativeContext destructor"); @@ -180,9 +182,10 @@ jobject WebPImage_nativeCreateFromByteVector(JNIEnv* pEnv, std::vector& webPData.bytes = vBuffer.data(); webPData.size = vBuffer.size(); + WebPDemuxState state; // Create the WebPDemuxer auto spDemuxer = std::unique_ptr { - WebPDemux(&webPData), + WebPDemuxPartial(&webPData, &state), WebPDemuxDelete }; if (!spDemuxer) { @@ -192,7 +195,7 @@ jobject WebPImage_nativeCreateFromByteVector(JNIEnv* pEnv, std::vector& //FBLOGW("unable to get demuxer"); return 0; } - + spNativeContext->partial = state != WEBP_DEMUX_DONE; spNativeContext->pixelWidth = WebPDemuxGetI(spDemuxer.get(), WEBP_FF_CANVAS_WIDTH); spNativeContext->pixelHeight = WebPDemuxGetI(spDemuxer.get(), WEBP_FF_CANVAS_HEIGHT); spNativeContext->numFrames = WebPDemuxGetI(spDemuxer.get(), WEBP_FF_FRAME_COUNT); @@ -546,6 +549,20 @@ jint WebPImage_nativeGetSizeInBytes(JNIEnv* pEnv, jobject thiz) { return spNativeContext->spDemuxer->getBufferSize(); } +/** + * Return whether the image is partial. + * + * @return whether the image is partial + */ +jboolean WebPImage_nativeGetPartial(JNIEnv* pEnv, jobject thiz) { + auto spNativeContext = getWebPImageNativeContext(pEnv, thiz); + if (!spNativeContext) { + throwIllegalStateException(pEnv, "Already disposed"); + return 0; + } + return spNativeContext->partial; +} + /** * Disposes the WebImage, freeing native resources. */ @@ -812,6 +829,9 @@ static JNINativeMethod sWebPImageMethods[] = { { "nativeGetSizeInBytes", "()I", (void*)WebPImage_nativeGetSizeInBytes }, + { "nativeGetPartial", + "()Z", + (void*)WebPImage_nativeGetPartial }, { "nativeDispose", "()V", (void*)WebImage_nativeDispose },