diff --git a/animated-webp/src/main/java/com/facebook/animated/webpdrawable/WebpAnimationBackend.java b/animated-webp/src/main/java/com/facebook/animated/webpdrawable/WebpAnimationBackend.java deleted file mode 100644 index eac344b552..0000000000 --- a/animated-webp/src/main/java/com/facebook/animated/webpdrawable/WebpAnimationBackend.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.animated.webpdrawable; - -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.ColorFilter; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import com.facebook.animated.webp.WebPFrame; -import com.facebook.animated.webp.WebPImage; -import com.facebook.fresco.animation.backend.AnimationBackend; -import com.facebook.infer.annotation.Nullsafe; -import java.io.BufferedInputStream; -import java.io.Closeable; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import javax.annotation.Nullable; -import javax.annotation.concurrent.GuardedBy; - -/** Animation backend that is used to draw webp frames. */ -@Nullsafe(Nullsafe.Mode.LOCAL) -public class WebpAnimationBackend implements AnimationBackend { - - private final Rect mRenderDstRect = new Rect(); - private final Rect mRenderSrcRect = new Rect(); - private final WebPImage mWebPImage; - - // NULLSAFE_FIXME[Field Not Initialized] - private Rect mBounds; - - @GuardedBy("this") - private @Nullable Bitmap mTempBitmap; - - public static WebpAnimationBackend create(String filePath) throws IOException { - InputStream is = null; - try { - is = new BufferedInputStream(new FileInputStream(filePath)); - is.mark(Integer.MAX_VALUE); - byte[] targetArray = new byte[is.available()]; - is.read(targetArray); - - WebPImage webPImage = WebPImage.createFromByteArray(targetArray, null); - is.reset(); - - return new WebpAnimationBackend(webPImage); - } finally { - closeSilently(is); - } - } - - private WebpAnimationBackend(WebPImage webPImage) { - mWebPImage = webPImage; - } - - @Override - public boolean drawFrame(Drawable parent, Canvas canvas, int frameNumber) { - WebPFrame frame = mWebPImage.getFrame(frameNumber); - - double xScale = (double) mBounds.width() / (double) parent.getIntrinsicWidth(); - double yScale = (double) mBounds.height() / (double) parent.getIntrinsicHeight(); - - int frameWidth = (int) Math.round(frame.getWidth() * xScale); - int frameHeight = (int) Math.round(frame.getHeight() * yScale); - int xOffset = (int) (frame.getXOffset() * xScale); - int yOffset = (int) (frame.getYOffset() * yScale); - - synchronized (this) { - int renderedWidth = mBounds.width(); - int renderedHeight = mBounds.height(); - // Update the temp bitmap to be >= rendered dimensions - prepareTempBitmapForThisSize(renderedWidth, renderedHeight); - if (mTempBitmap == null) { - return false; - } - frame.renderFrame(frameWidth, frameHeight, mTempBitmap); - // Temporary bitmap can be bigger than frame, so we should draw only rendered area of bitmap - mRenderSrcRect.set(0, 0, renderedWidth, renderedHeight); - mRenderDstRect.set(xOffset, yOffset, xOffset + renderedWidth, yOffset + renderedHeight); - - canvas.drawBitmap(mTempBitmap, mRenderSrcRect, mRenderDstRect, null); - } - return true; - } - - @Override - public void setAlpha(int alpha) { - // unimplemented - } - - @Override - public void setColorFilter(@Nullable ColorFilter colorFilter) { - // unimplemented - } - - @Override - public synchronized void setBounds(Rect bounds) { - mBounds = bounds; - } - - @Override - public int getIntrinsicWidth() { - return mWebPImage.getWidth(); - } - - @Override - public int getIntrinsicHeight() { - return mWebPImage.getHeight(); - } - - @Override - public int getSizeInBytes() { - return 0; - } - - @Override - public void clear() { - mWebPImage.dispose(); - } - - @Override - public int getFrameCount() { - return mWebPImage.getFrameCount(); - } - - @Override - public int getFrameDurationMs(int frameNumber) { - return mWebPImage.getFrameDurations()[frameNumber]; - } - - @Override - public int getLoopDurationMs() { - return mWebPImage.getDuration(); - } - - @Override - public int width() { - return mWebPImage.getWidth(); - } - - @Override - public int height() { - return mWebPImage.getHeight(); - } - - @Override - public int getLoopCount() { - return mWebPImage.getLoopCount(); - } - - @Override - public void preloadAnimation() { - // not needed as bitmaps are extracted on fly - } - - @Override - public void setAnimationListener(@Nullable Listener listener) { - // unimplementedå - } - - private synchronized void prepareTempBitmapForThisSize(int width, int height) { - // Different webp frames can be different size, - // So we need to ensure we can fit next frame to temporary bitmap - if (mTempBitmap != null - && (mTempBitmap.getWidth() < width || mTempBitmap.getHeight() < height)) { - clearTempBitmap(); - } - if (mTempBitmap == null) { - mTempBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - } - mTempBitmap.eraseColor(Color.TRANSPARENT); - } - - private synchronized void clearTempBitmap() { - if (mTempBitmap != null) { - mTempBitmap.recycle(); - mTempBitmap = null; - } - } - - private static void closeSilently(@Nullable Closeable closeable) { - if (closeable == null) { - return; - } - try { - closeable.close(); - } catch (IOException ignored) { - // ignore - } - } -} diff --git a/animated-webp/src/main/java/com/facebook/animated/webpdrawable/WebpAnimationBackend.kt b/animated-webp/src/main/java/com/facebook/animated/webpdrawable/WebpAnimationBackend.kt new file mode 100644 index 0000000000..08d8946b21 --- /dev/null +++ b/animated-webp/src/main/java/com/facebook/animated/webpdrawable/WebpAnimationBackend.kt @@ -0,0 +1,159 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.animated.webpdrawable + +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.ColorFilter +import android.graphics.Rect +import android.graphics.drawable.Drawable +import com.facebook.animated.webp.WebPImage +import com.facebook.fresco.animation.backend.AnimationBackend +import java.io.BufferedInputStream +import java.io.Closeable +import java.io.FileInputStream +import java.io.IOException +import java.io.InputStream +import javax.annotation.concurrent.GuardedBy + +/** Animation backend that is used to draw webp frames. */ +class WebpAnimationBackend private constructor(private val webPImage: WebPImage) : + AnimationBackend { + + private val renderDstRect = Rect() + private val renderSrcRect = Rect() + + private var bounds: Rect? = null + + @GuardedBy("this") private var tempBitmap: Bitmap? = null + + override fun drawFrame(parent: Drawable, canvas: Canvas, frameNumber: Int): Boolean { + val frame = webPImage.getFrame(frameNumber) + + val xScale = bounds!!.width().toDouble() / parent.intrinsicWidth.toDouble() + val yScale = bounds!!.height().toDouble() / parent.intrinsicHeight.toDouble() + + val frameWidth = Math.round(frame.width * xScale).toInt() + val frameHeight = Math.round(frame.height * yScale).toInt() + val xOffset = (frame.xOffset * xScale).toInt() + val yOffset = (frame.yOffset * yScale).toInt() + + synchronized(this) { + val renderedWidth = bounds!!.width() + val renderedHeight = bounds!!.height() + // Update the temp bitmap to be >= rendered dimensions + prepareTempBitmapForThisSize(renderedWidth, renderedHeight) + if (tempBitmap == null) { + return false + } + frame.renderFrame(frameWidth, frameHeight, tempBitmap!!) + // Temporary bitmap can be bigger than frame, so we should draw only rendered area of bitmap + renderSrcRect[0, 0, renderedWidth] = renderedHeight + renderDstRect[xOffset, yOffset, xOffset + renderedWidth] = yOffset + renderedHeight + canvas.drawBitmap(tempBitmap!!, renderSrcRect, renderDstRect, null) + } + return true + } + + override fun setAlpha(alpha: Int) { + // unimplemented + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + // unimplemented + } + + @Synchronized + override fun setBounds(bounds: Rect) { + this.bounds = bounds + } + + override fun getIntrinsicWidth(): Int = webPImage.width + + override fun getIntrinsicHeight(): Int = webPImage.height + + override fun getSizeInBytes(): Int = 0 + + override fun clear() { + webPImage.dispose() + } + + override fun getFrameCount(): Int = webPImage.frameCount + + override fun getFrameDurationMs(frameNumber: Int): Int = webPImage.frameDurations[frameNumber] + + override fun getLoopDurationMs(): Int = webPImage.duration + + override fun width(): Int = webPImage.width + + override fun height(): Int = webPImage.height + + override fun getLoopCount(): Int = webPImage.loopCount + + override fun preloadAnimation() { + // not needed as bitmaps are extracted on fly + } + + override fun setAnimationListener(listener: AnimationBackend.Listener?) { + // unimplementedå + } + + @Synchronized + private fun prepareTempBitmapForThisSize(width: Int, height: Int) { + // Different webp frames can be different size, + // So we need to ensure we can fit next frame to temporary bitmap + if (tempBitmap != null && (tempBitmap!!.width < width || tempBitmap!!.height < height)) { + clearTempBitmap() + } + if (tempBitmap == null) { + tempBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + } + tempBitmap!!.eraseColor(Color.TRANSPARENT) + } + + @Synchronized + private fun clearTempBitmap() { + if (tempBitmap != null) { + tempBitmap!!.recycle() + tempBitmap = null + } + } + + companion object { + @JvmStatic + @Throws(IOException::class) + fun create(filePath: String?): WebpAnimationBackend { + var `is`: InputStream? = null + try { + `is` = BufferedInputStream(FileInputStream(filePath)) + `is`.mark(Int.MAX_VALUE) + val targetArray = ByteArray(`is`.available()) + `is`.read(targetArray) + + val webPImage = WebPImage.createFromByteArray(targetArray, null) + `is`.reset() + + return WebpAnimationBackend(webPImage) + } finally { + closeSilently(`is`) + } + } + + private fun closeSilently(closeable: Closeable?) { + if (closeable == null) { + return + } + try { + closeable.close() + } catch (ignored: IOException) { + // ignore + } + } + } +}