Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support progressive animated webp #2353

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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 @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public int getFrameCount() {

@Override
public int getLoopCount() {
return mAnimatedImage.getLoopCount();
return mAnimatedImage.isPartial() ? 1 : mAnimatedImage.getLoopCount();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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
Expand All @@ -212,7 +225,7 @@ public void draw(Canvas canvas) {
}

// Log potential dropped frames
if (!frameDrawn) {
if (frameNumberToDraw != -1 && !frameDrawn) {
onFrameDropped();
}

Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,5 +92,10 @@ public AnimatedDrawableFrameInfo getFrameInfo(int frameNumber) {
AnimatedDrawableFrameInfo.BlendOperation.BLEND_WITH_PREVIOUS,
mFrames[frameNumber].getDisposalMode());
}

@Override
public boolean isPartial() {
return false;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@
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;
import com.facebook.imagepipeline.image.EncodedImage;
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;
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,9 @@ protected Drawable createDrawable(CloseableReference<CloseableImage> image) {
return drawable;
}

drawable = mDefaultDrawableFactory.createDrawable(closeableImage);
drawable = mDefaultDrawableFactory.needPreviousDrawable(mDrawable, closeableImage)
? mDefaultDrawableFactory.createDrawable(mDrawable, closeableImage)
: mDefaultDrawableFactory.createDrawable(closeableImage);
if (drawable != null) {
return drawable;
}
Expand All @@ -288,7 +290,9 @@ protected Drawable createDrawable(CloseableReference<CloseableImage> 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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public static <INFO> InternalForwardingListener<INFO> createInternal(
private @Nullable String mContentDescription;
private @Nullable DataSource<T> mDataSource;
private @Nullable T mFetchedImage;
private @Nullable Drawable mDrawable;
protected @Nullable Drawable mDrawable;
private boolean mJustConstructed = true;

public AbstractDraweeController(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,24 @@ public abstract class AbstractDraweeControllerBuilder <
INFO>
implements SimpleDraweeControllerBuilder {

private static class ProgressiveAnimationsListener extends BaseControllerListener<Object> {
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<Object> sAutoPlayAnimationsListener =
new BaseControllerListener<Object>() {
@Override
Expand Down Expand Up @@ -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. */
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Loading