/* * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.animated.gif; import javax.annotation.concurrent.ThreadSafe; import java.nio.ByteBuffer; import com.facebook.common.internal.DoNotStrip; import com.facebook.common.internal.Preconditions; import com.facebook.common.soloader.SoLoaderShim; import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo; import com.facebook.imagepipeline.animated.base.AnimatedDrawableFrameInfo.BlendOperation; import com.facebook.imagepipeline.animated.base.AnimatedImage; import com.facebook.imagepipeline.animated.factory.AnimatedImageDecoder; /** * A representation of a GIF image. An instance of this class will hold a copy of the encoded * data in memory along with the parsed header data. Frames are decoded on demand via * {@link GifFrame}. */ @ThreadSafe @DoNotStrip public class GifImage implements AnimatedImage, AnimatedImageDecoder { private static final int LOOP_COUNT_FOREVER = 0; private static final int LOOP_COUNT_MISSING = -1; private volatile static boolean sInitialized; // Accessed by native methods @SuppressWarnings("unused") @DoNotStrip private long mNativeContext; private static synchronized void ensure() { if (!sInitialized) { sInitialized = true; SoLoaderShim.loadLibrary("gifimage"); } } /** * Creates a {@link GifImage} from the specified encoded data. This will throw if it fails * to create. This is meant to be called on a worker thread. * * @param source the data to the image (a copy will be made) */ public static GifImage create(byte[] source) { ensure(); Preconditions.checkNotNull(source); ByteBuffer byteBuffer = ByteBuffer.allocateDirect(source.length); byteBuffer.put(source); byteBuffer.rewind(); return nativeCreateFromDirectByteBuffer(byteBuffer); } public static GifImage create(long nativePtr, int sizeInBytes) { ensure(); Preconditions.checkArgument(nativePtr != 0); return nativeCreateFromNativeMemory(nativePtr, sizeInBytes); } @Override public AnimatedImage decode(long nativePtr, int sizeInBytes) { return GifImage.create(nativePtr, sizeInBytes); } @DoNotStrip public GifImage() { } /** * Constructs the image with the native pointer. This is called by native code. * * @param nativeContext the native pointer */ @DoNotStrip GifImage(long nativeContext) { mNativeContext = nativeContext; } @Override protected void finalize() { nativeFinalize(); } @Override public void dispose() { nativeDispose(); } @Override public int getWidth() { return nativeGetWidth(); } @Override public int getHeight() { return nativeGetHeight(); } @Override public int getFrameCount() { return nativeGetFrameCount(); } @Override public int getDuration() { return nativeGetDuration(); } @Override public int[] getFrameDurations() { return nativeGetFrameDurations(); } @Override public int getLoopCount() { // If a GIF image has no Netscape 2.0 loop extension, it is meant to play once and then stop. A // loop count of 0 indicates an endless looping of the animation. Any loop count X>0 indicates // that the animation shall be repeated X times, resulting in the animation to play X+1 times. final int loopCount = nativeGetLoopCount(); switch (loopCount) { case LOOP_COUNT_FOREVER: return AnimatedImage.LOOP_COUNT_INFINITE; case LOOP_COUNT_MISSING: return 1; default: return loopCount + 1; } } @Override public GifFrame getFrame(int frameNumber) { return nativeGetFrame(frameNumber); } @Override public boolean doesRenderSupportScaling() { return false; } @Override public int getSizeInBytes() { return nativeGetSizeInBytes(); } @Override public AnimatedDrawableFrameInfo getFrameInfo(int frameNumber) { GifFrame frame = getFrame(frameNumber); try { return new AnimatedDrawableFrameInfo( frameNumber, frame.getXOffset(), frame.getYOffset(), frame.getWidth(), frame.getHeight(), BlendOperation.BLEND_WITH_PREVIOUS, fromGifDisposalMethod(frame.getDisposalMode())); } finally { frame.dispose(); } } private static AnimatedDrawableFrameInfo.DisposalMethod fromGifDisposalMethod(int disposalMode) { if (disposalMode == 0 /* DISPOSAL_UNSPECIFIED */) { return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_DO_NOT; } else if (disposalMode == 1 /* DISPOSE_DO_NOT */) { return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_DO_NOT; } else if (disposalMode == 2 /* DISPOSE_BACKGROUND */) { return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_TO_BACKGROUND; } else if (disposalMode == 3 /* DISPOSE_PREVIOUS */) { return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_TO_PREVIOUS; } else { return AnimatedDrawableFrameInfo.DisposalMethod.DISPOSE_DO_NOT; } } private static native GifImage nativeCreateFromDirectByteBuffer(ByteBuffer buffer); private static native GifImage nativeCreateFromNativeMemory(long nativePtr, int sizeInBytes); private native int nativeGetWidth(); private native int nativeGetHeight(); private native int nativeGetDuration(); private native int nativeGetFrameCount(); private native int[] nativeGetFrameDurations(); private native int nativeGetLoopCount(); private native GifFrame nativeGetFrame(int frameNumber); private native int nativeGetSizeInBytes(); private native void nativeDispose(); private native void nativeFinalize(); }