diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/drawable/ContentDrawable.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/drawable/ContentDrawable.java index ef8f01c95..0e0f6e9f6 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/drawable/ContentDrawable.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/drawable/ContentDrawable.java @@ -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; } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java index bfd641768..85b211786 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageComponent.java @@ -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(); diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataHolder.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataHolder.java index a5e75c092..2b84d361f 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataHolder.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataHolder.java @@ -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; } diff --git a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataSupplier.java b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataSupplier.java index f146a0810..21e189a87 100644 --- a/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataSupplier.java +++ b/renderer/native/android/src/main/java/com/tencent/renderer/component/image/ImageDataSupplier.java @@ -38,6 +38,8 @@ public interface ImageDataSupplier { boolean isRecyclable(); + boolean isAnimated(); + @NonNull String getSource();