mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-29 06:06:52 +08:00
feat(android): support AnimatedImageDrawable draw gif
This commit is contained in:
committed by
hippy-actions[bot]
parent
a0117449ea
commit
3ce06f2c84
+39
-39
@@ -55,6 +55,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.Px;
|
||||
|
||||
import com.tencent.renderer.component.image.ImageDataSupplier;
|
||||
|
||||
public class ContentDrawable extends Drawable {
|
||||
|
||||
private int mTintColor;
|
||||
@@ -67,9 +69,7 @@ public class ContentDrawable extends Drawable {
|
||||
@Nullable
|
||||
private Paint mPaint;
|
||||
@Nullable
|
||||
private Bitmap mContentBitmap;
|
||||
@Nullable
|
||||
private Movie mGifMovie;
|
||||
private ImageDataSupplier mImageHolder;
|
||||
@Nullable
|
||||
private GifMovieState mGifMovieState;
|
||||
@Nullable
|
||||
@@ -88,6 +88,8 @@ public class ContentDrawable extends Drawable {
|
||||
mBackgroundHolder = holder;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
protected void onBoundsChange(Rect bounds) {
|
||||
super.onBoundsChange(bounds);
|
||||
@@ -119,17 +121,11 @@ public class ContentDrawable extends Drawable {
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
mContentBitmap = null;
|
||||
mGifMovie = null;
|
||||
mGifMovieState = null;
|
||||
}
|
||||
|
||||
public void setContentBitmap(@Nullable Bitmap bitmap) {
|
||||
mContentBitmap = bitmap;
|
||||
}
|
||||
|
||||
public void setGifMovie(@Nullable Movie gifMovie) {
|
||||
mGifMovie = gifMovie;
|
||||
public void setImageData(@NonNull ImageDataSupplier imageHolder) {
|
||||
mImageHolder = imageHolder;
|
||||
}
|
||||
|
||||
private void updateContentRegionIfNeeded() {
|
||||
@@ -144,7 +140,7 @@ public class ContentDrawable extends Drawable {
|
||||
|
||||
@Override
|
||||
public void draw(@NonNull Canvas canvas) {
|
||||
if (getBounds().width() == 0 || getBounds().height() == 0) {
|
||||
if (getBounds().width() == 0 || getBounds().height() == 0 || mImageHolder == null) {
|
||||
return;
|
||||
}
|
||||
updateContentRegionIfNeeded();
|
||||
@@ -156,7 +152,11 @@ public class ContentDrawable extends Drawable {
|
||||
} else {
|
||||
canvas.clipRect(mContentRegion);
|
||||
}
|
||||
if (mContentBitmap != null && !mContentBitmap.isRecycled()) {
|
||||
if (mImageHolder.getDrawable() != null) {
|
||||
mImageHolder.getDrawable().draw(canvas);
|
||||
} else if (mImageHolder.isAnimated()) {
|
||||
drawGif(canvas, mImageHolder.getGifMovie());
|
||||
} else {
|
||||
if (mPaint == null) {
|
||||
mPaint = new Paint();
|
||||
} else {
|
||||
@@ -166,18 +166,18 @@ public class ContentDrawable extends Drawable {
|
||||
if (mTintColor != Color.TRANSPARENT) {
|
||||
mPaint.setColorFilter(new PorterDuffColorFilter(mTintColor, mTintColorBlendMode));
|
||||
}
|
||||
drawBitmap(canvas);
|
||||
} else if (mGifMovie != null) {
|
||||
drawGif(canvas);
|
||||
Bitmap bitmap = mImageHolder.getBitmap();
|
||||
if (bitmap != null) {
|
||||
drawBitmap(canvas, bitmap);
|
||||
}
|
||||
}
|
||||
canvas.restore();
|
||||
}
|
||||
|
||||
private void updateBitmapMatrix() {
|
||||
assert mContentBitmap != null;
|
||||
private void updateBitmapMatrix(@NonNull Bitmap bitmap) {
|
||||
final RectF dst = new RectF(mContentRegion);
|
||||
final float bitmapWidth = mContentBitmap.getWidth();
|
||||
final float bitmapHeight = mContentBitmap.getHeight();
|
||||
final float bitmapWidth = bitmap.getWidth();
|
||||
final float bitmapHeight = bitmap.getHeight();
|
||||
final float width = dst.width();
|
||||
final float height = dst.height();
|
||||
final float xScale = width / bitmapWidth;
|
||||
@@ -219,30 +219,31 @@ public class ContentDrawable extends Drawable {
|
||||
dst.left += mImagePositionX;
|
||||
dst.right += mImagePositionX;
|
||||
mBitmapMatrix.setRectToRect(
|
||||
new RectF(0, 0, mContentBitmap.getWidth(), mContentBitmap.getHeight()),
|
||||
new RectF(0, 0, bitmap.getWidth(), bitmap.getHeight()),
|
||||
dst, Matrix.ScaleToFit.FILL);
|
||||
}
|
||||
|
||||
private void drawBitmap(@NonNull Canvas canvas) {
|
||||
assert mContentBitmap != null;
|
||||
private void drawBitmap(@NonNull Canvas canvas, @NonNull Bitmap bitmap) {
|
||||
assert mPaint != null;
|
||||
updateBitmapMatrix();
|
||||
updateBitmapMatrix(bitmap);
|
||||
if (mScaleType == ScaleType.REPEAT) {
|
||||
BitmapShader bitmapShader = new BitmapShader(mContentBitmap, Shader.TileMode.REPEAT,
|
||||
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.REPEAT,
|
||||
Shader.TileMode.REPEAT);
|
||||
mPaint.setShader(bitmapShader);
|
||||
}
|
||||
mPaint.setFilterBitmap(true);
|
||||
canvas.drawBitmap(mContentBitmap, mBitmapMatrix, mPaint);
|
||||
canvas.drawBitmap(bitmap, mBitmapMatrix, mPaint);
|
||||
}
|
||||
|
||||
private void drawGif(@NonNull Canvas canvas) {
|
||||
private void drawGif(@NonNull Canvas canvas, @Nullable Movie movie) {
|
||||
if (movie == null) {
|
||||
return;
|
||||
}
|
||||
if (mGifMovieState == null) {
|
||||
mGifMovieState = new GifMovieState();
|
||||
}
|
||||
mGifMovieState.update(mContentRegion);
|
||||
assert mGifMovie != null;
|
||||
int duration = mGifMovie.duration();
|
||||
mGifMovieState.update(mContentRegion, movie);
|
||||
int duration = movie.duration();
|
||||
if (duration == 0) {
|
||||
duration = 1000;
|
||||
}
|
||||
@@ -256,10 +257,10 @@ public class ContentDrawable extends Drawable {
|
||||
mGifMovieState.lastPlayTime = now;
|
||||
int progress =
|
||||
mGifMovieState.progress > Integer.MAX_VALUE ? 0 : (int) mGifMovieState.progress;
|
||||
mGifMovie.setTime(progress);
|
||||
movie.setTime(progress);
|
||||
canvas.save();
|
||||
canvas.scale(mGifMovieState.scaleX, mGifMovieState.scaleY);
|
||||
mGifMovie.draw(canvas, mGifMovieState.startX, mGifMovieState.startY + 1.0f);
|
||||
movie.draw(canvas, mGifMovieState.startX, mGifMovieState.startY + 1.0f);
|
||||
canvas.restore();
|
||||
scheduleSelf(new Runnable() {
|
||||
@Override
|
||||
@@ -342,7 +343,7 @@ public class ContentDrawable extends Drawable {
|
||||
public long lastPlayTime = -1;
|
||||
private boolean updateRequired = true;
|
||||
|
||||
public void update(@NonNull RectF dst) {
|
||||
public void update(@NonNull RectF dst, @NonNull Movie movie) {
|
||||
if (!updateRequired) {
|
||||
return;
|
||||
}
|
||||
@@ -350,12 +351,11 @@ public class ContentDrawable extends Drawable {
|
||||
startY = 0;
|
||||
scaleX = 1;
|
||||
scaleY = 1;
|
||||
assert mGifMovie != null;
|
||||
if (mGifMovie.width() <= 0 || mGifMovie.height() <= 0) {
|
||||
if (movie.width() <= 0 || movie.height() <= 0) {
|
||||
return;
|
||||
}
|
||||
scaleX = dst.width() / mGifMovie.width();
|
||||
scaleY = dst.height() / mGifMovie.height();
|
||||
scaleX = dst.width() / movie.width();
|
||||
scaleY = dst.height() / movie.height();
|
||||
switch (mScaleType) {
|
||||
case CENTER:
|
||||
// fall through
|
||||
@@ -381,8 +381,8 @@ public class ContentDrawable extends Drawable {
|
||||
break;
|
||||
}
|
||||
if (mScaleType != ScaleType.ORIGIN) {
|
||||
startX = (dst.width() / scaleX - mGifMovie.width()) / 2.0f;
|
||||
startY = (dst.height() / scaleY - mGifMovie.height()) / 2.0f;
|
||||
startX = (dst.width() / scaleX - movie.width()) / 2.0f;
|
||||
startY = (dst.height() / scaleY - movie.height()) / 2.0f;
|
||||
}
|
||||
updateRequired = false;
|
||||
}
|
||||
|
||||
+32
-18
@@ -22,6 +22,8 @@ import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_LOAD_PROGRESS;
|
||||
import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_LOAD_START;
|
||||
import static com.tencent.renderer.utils.EventUtils.EVENT_IMAGE_ON_LOAD;
|
||||
|
||||
import android.graphics.drawable.AnimatedImageDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
@@ -51,9 +53,9 @@ public class ImageComponent extends Component {
|
||||
@Nullable
|
||||
private String mBundlePath;
|
||||
@Nullable
|
||||
private ImageDataSupplier mImageData;
|
||||
private ImageDataSupplier mImageHolder;
|
||||
@Nullable
|
||||
private ImageDataSupplier mDefaultImageData;
|
||||
private ImageDataSupplier mDefaultImageHolder;
|
||||
private ImageFetchState mImageFetchState = ImageFetchState.UNLOAD;
|
||||
private ImageFetchState mDefaultImageFetchState = ImageFetchState.UNLOAD;
|
||||
|
||||
@@ -90,11 +92,11 @@ public class ImageComponent extends Component {
|
||||
}
|
||||
|
||||
private void fetchImageIfNeeded() {
|
||||
if ((mDefaultImageData == null || !mDefaultImageData.checkImageData())
|
||||
if ((mDefaultImageHolder == null || !mDefaultImageHolder.checkImageData())
|
||||
&& mDefaultImageFetchState == ImageFetchState.UNLOAD) {
|
||||
fetchImageWithUrl(mDefaultUri, ImageSourceType.DEFAULT);
|
||||
}
|
||||
if ((mImageData == null || !mImageData.checkImageData())
|
||||
if ((mImageHolder == null || !mImageHolder.checkImageData())
|
||||
&& mImageFetchState == ImageFetchState.UNLOAD) {
|
||||
fetchImageWithUrl(mUri, ImageSourceType.SRC);
|
||||
}
|
||||
@@ -116,15 +118,15 @@ public class ImageComponent extends Component {
|
||||
@Override
|
||||
public void clear() {
|
||||
super.clear();
|
||||
if (mImageData != null) {
|
||||
mImageData.detached();
|
||||
mImageData.clear();
|
||||
mImageData = null;
|
||||
if (mImageHolder != null) {
|
||||
mImageHolder.detached();
|
||||
mImageHolder.clear();
|
||||
mImageHolder = null;
|
||||
}
|
||||
if (mDefaultImageData != null) {
|
||||
mDefaultImageData.detached();
|
||||
mDefaultImageData.clear();
|
||||
mDefaultImageData = null;
|
||||
if (mDefaultImageHolder != null) {
|
||||
mDefaultImageHolder.detached();
|
||||
mDefaultImageHolder.clear();
|
||||
mDefaultImageHolder = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,16 +176,28 @@ public class ImageComponent extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
private void setImageData(@NonNull ImageDataSupplier imageHolder) {
|
||||
mImageHolder = imageHolder;
|
||||
Drawable drawable = imageHolder.getDrawable();
|
||||
if (drawable != null) {
|
||||
drawable.setCallback(this);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
if (drawable instanceof AnimatedImageDrawable) {
|
||||
((AnimatedImageDrawable) drawable).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
ensureContentDrawable().setImageData(imageHolder);
|
||||
}
|
||||
|
||||
private void onFetchImageSuccess(@NonNull String uri, ImageSourceType sourceType,
|
||||
@NonNull ImageDataSupplier imageData, boolean loadFromCache) {
|
||||
if (sourceType == ImageSourceType.SRC) {
|
||||
if (!uri.equals(mUri)) {
|
||||
return;
|
||||
}
|
||||
mImageData = imageData;
|
||||
mImageFetchState = ImageFetchState.LOADED;
|
||||
ensureContentDrawable().setContentBitmap(imageData.getBitmap());
|
||||
ensureContentDrawable().setGifMovie(imageData.getGifMovie());
|
||||
setImageData(imageData);
|
||||
if (mHostRef.get() != null && !loadFromCache) {
|
||||
// send onLoad event
|
||||
EventUtils.sendComponentEvent(mHostRef.get(), EVENT_IMAGE_ON_LOAD, null);
|
||||
@@ -200,10 +214,10 @@ public class ImageComponent extends Component {
|
||||
if (!uri.equals(mDefaultUri)) {
|
||||
return;
|
||||
}
|
||||
mDefaultImageData = imageData;
|
||||
mDefaultImageHolder = imageData;
|
||||
mDefaultImageFetchState = ImageFetchState.LOADED;
|
||||
if (mImageData == null || !mImageData.checkImageData()) {
|
||||
ensureContentDrawable().setContentBitmap(imageData.getBitmap());
|
||||
if (mImageHolder == null || !mImageHolder.checkImageData()) {
|
||||
setImageData(imageData);
|
||||
}
|
||||
}
|
||||
imageData.attached();
|
||||
|
||||
+66
-30
@@ -19,10 +19,16 @@ package com.tencent.renderer.component.image;
|
||||
import static com.tencent.renderer.NativeRenderException.ExceptionCode.IMAGE_DATA_DECODE_ERR;
|
||||
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.graphics.ImageDecoder.ImageInfo;
|
||||
import android.graphics.ImageDecoder.OnHeaderDecodedListener;
|
||||
import android.graphics.ImageDecoder.Source;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.tencent.renderer.NativeRenderException;
|
||||
import com.tencent.renderer.utils.ImageDataUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -35,16 +41,23 @@ import android.graphics.drawable.Drawable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class ImageDataHolder implements ImageDataSupplier {
|
||||
|
||||
private static final int MAX_SOURCE_KEY_LEN = 32;
|
||||
/** Mark that the image data has been cached. */
|
||||
/**
|
||||
* Mark that the image data has been cached.
|
||||
*/
|
||||
private static final int FLAG_CACHED = 0x00000001;
|
||||
/** Mark that the image data has been attached to the view. */
|
||||
/**
|
||||
* Mark that the image data has been attached to the view.
|
||||
*/
|
||||
private static final int FLAG_ATTACHED = 0x00000002;
|
||||
/** Mark that image data is decoded internally and needs to recycle. */
|
||||
/**
|
||||
* Mark that image data is decoded internally and needs to recycle.
|
||||
*/
|
||||
private static final int FLAG_RECYCLABLE = 0x00000004;
|
||||
private int mStateFlags = 0;
|
||||
private int mWidth;
|
||||
@@ -141,7 +154,7 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
@Override
|
||||
@Nullable
|
||||
public Bitmap getBitmap() {
|
||||
return mBitmap;
|
||||
return mBitmap != null && !mBitmap.isRecycled() ? mBitmap : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,7 +177,17 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
|
||||
@Override
|
||||
public boolean checkImageData() {
|
||||
return (mBitmap != null && !mBitmap.isRecycled()) || mGifMovie != null;
|
||||
if (mOptions == null) {
|
||||
return false;
|
||||
}
|
||||
if (ImageDataUtils.isJpeg(mOptions) || ImageDataUtils.isPng(mOptions)) {
|
||||
return mBitmap != null && !mBitmap.isRecycled();
|
||||
}
|
||||
if (ImageDataUtils.isGif(mOptions) && (android.os.Build.VERSION.SDK_INT
|
||||
< android.os.Build.VERSION_CODES.P)) {
|
||||
return mGifMovie != null;
|
||||
}
|
||||
return mDrawable != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,24 +197,17 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
|
||||
@Override
|
||||
public int getImageWidth() {
|
||||
if (mBitmap != null) {
|
||||
return mBitmap.getWidth();
|
||||
}
|
||||
if (mGifMovie != null) {
|
||||
return mGifMovie.width();
|
||||
}
|
||||
return 0;
|
||||
return (mOptions != null) ? mOptions.outWidth : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getImageHeight() {
|
||||
if (mBitmap != null) {
|
||||
return mBitmap.getHeight();
|
||||
}
|
||||
if (mGifMovie != null) {
|
||||
return mGifMovie.height();
|
||||
}
|
||||
return 0;
|
||||
return (mOptions != null) ? mOptions.outHeight : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnimated() {
|
||||
return mOptions != null && ImageDataUtils.isGif(mOptions);
|
||||
}
|
||||
|
||||
public String getImageType() {
|
||||
@@ -210,14 +226,21 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
try {
|
||||
mOptions = ImageDataUtils.generateBitmapOptions(data);
|
||||
if (ImageDataUtils.isGif(mOptions)) {
|
||||
mGifMovie = Movie.decodeByteArray(data, 0, data.length);
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
mDrawable = decodeGifForTarget28(data);
|
||||
} else {
|
||||
mGifMovie = Movie.decodeByteArray(data, 0, data.length);
|
||||
}
|
||||
mBitmap = null;
|
||||
} else {
|
||||
} else if (ImageDataUtils.isJpeg(mOptions) || ImageDataUtils.isPng(mOptions)) {
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
|
||||
decodeLocalFileForTarget28(ByteBuffer.wrap(data));
|
||||
} else {
|
||||
decodeImageData(data);
|
||||
decodeBitmap(data);
|
||||
}
|
||||
mGifMovie = null;
|
||||
} else {
|
||||
throw new RuntimeException("Unsupported picture type!");
|
||||
}
|
||||
} catch (OutOfMemoryError | Exception e) {
|
||||
throw new NativeRenderException(IMAGE_DATA_DECODE_ERR, e.getMessage());
|
||||
@@ -227,15 +250,28 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
public void setData(Bitmap bitmap) {
|
||||
mBitmap = bitmap;
|
||||
mGifMovie = null;
|
||||
mDrawable = null;
|
||||
resetStateFlag(FLAG_RECYCLABLE);
|
||||
}
|
||||
|
||||
public boolean isAnimated() {
|
||||
return mGifMovie != null;
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
@NonNull
|
||||
private Drawable decodeGifForTarget28(@NonNull byte[] data) throws IOException {
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(ByteBuffer.wrap(data));
|
||||
return ImageDecoder.decodeDrawable(source,
|
||||
new OnHeaderDecodedListener() {
|
||||
@Override
|
||||
public void onHeaderDecoded(@NonNull ImageDecoder decoder,
|
||||
@NonNull ImageInfo info, @NonNull Source source1) {
|
||||
// This is a synchronous callback, although the callback parameter is not
|
||||
// used at present, it is still reserved, image width and height can also be
|
||||
// obtained through mOptions.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
private void decodeImageSource(@Nullable ImageDecoder.Source source)
|
||||
private void decodeBitmapForTarget28(@Nullable ImageDecoder.Source source)
|
||||
throws IOException {
|
||||
if (source != null) {
|
||||
mBitmap = ImageDecoder.decodeBitmap(source);
|
||||
@@ -247,25 +283,25 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
private void decodeLocalFileForTarget28(@NonNull File file) throws IOException {
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(file);
|
||||
decodeImageSource(source);
|
||||
decodeBitmapForTarget28(source);
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
private void decodeLocalFileForTarget28(@NonNull String fileName) throws IOException {
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(
|
||||
ContextHolder.getAppContext().getAssets(), fileName);
|
||||
decodeImageSource(source);
|
||||
decodeBitmapForTarget28(source);
|
||||
}
|
||||
|
||||
@RequiresApi(api = VERSION_CODES.P)
|
||||
private void decodeLocalFileForTarget28(@NonNull ByteBuffer buffer) throws IOException {
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(buffer);
|
||||
decodeImageSource(source);
|
||||
decodeBitmapForTarget28(source);
|
||||
}
|
||||
|
||||
private int getSampleSize(int outWidth, int outHeight) {
|
||||
int sampleSize = 1;
|
||||
if (mWidth <=0 || mHeight <= 0) {
|
||||
if (mWidth <= 0 || mHeight <= 0) {
|
||||
return sampleSize;
|
||||
}
|
||||
if (outWidth >= outHeight) {
|
||||
@@ -281,7 +317,7 @@ public class ImageDataHolder implements ImageDataSupplier {
|
||||
return sampleSize;
|
||||
}
|
||||
|
||||
private void decodeImageData(@Nullable byte[] data) {
|
||||
private void decodeBitmap(@Nullable byte[] data) {
|
||||
if (data == null || data.length <= 0 || mOptions == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
+2
@@ -38,6 +38,8 @@ public interface ImageDataSupplier {
|
||||
|
||||
boolean isRecyclable();
|
||||
|
||||
boolean isAnimated();
|
||||
|
||||
@NonNull
|
||||
String getSource();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user