| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.support.rastermill; |
| |
| import android.graphics.Bitmap; |
| import java.nio.ByteBuffer; |
| |
| import java.io.InputStream; |
| |
| public class FrameSequence { |
| static { |
| System.loadLibrary("framesequence"); |
| } |
| |
| private final long mNativeFrameSequence; |
| private final int mWidth; |
| private final int mHeight; |
| private final boolean mOpaque; |
| private final int mFrameCount; |
| private final int mDefaultLoopCount; |
| |
| public int getWidth() { return mWidth; } |
| public int getHeight() { return mHeight; } |
| public boolean isOpaque() { return mOpaque; } |
| public int getFrameCount() { return mFrameCount; } |
| public int getDefaultLoopCount() { return mDefaultLoopCount; } |
| |
| private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length); |
| private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage); |
| private static native FrameSequence nativeDecodeByteBuffer(ByteBuffer buffer, int offset, int capacity); |
| private static native void nativeDestroyFrameSequence(long nativeFrameSequence); |
| private static native long nativeCreateState(long nativeFrameSequence); |
| private static native void nativeDestroyState(long nativeState); |
| private static native long nativeGetFrame(long nativeState, int frameNr, |
| Bitmap output, int previousFrameNr); |
| |
| @SuppressWarnings("unused") // called by native |
| private FrameSequence(long nativeFrameSequence, int width, int height, |
| boolean opaque, int frameCount, int defaultLoopCount) { |
| mNativeFrameSequence = nativeFrameSequence; |
| mWidth = width; |
| mHeight = height; |
| mOpaque = opaque; |
| mFrameCount = frameCount; |
| mDefaultLoopCount = defaultLoopCount; |
| } |
| |
| public static FrameSequence decodeByteArray(byte[] data) { |
| return decodeByteArray(data, 0, data.length); |
| } |
| |
| public static FrameSequence decodeByteArray(byte[] data, int offset, int length) { |
| if (data == null) throw new IllegalArgumentException(); |
| if (offset < 0 || length < 0 || (offset + length > data.length)) { |
| throw new IllegalArgumentException("invalid offset/length parameters"); |
| } |
| return nativeDecodeByteArray(data, offset, length); |
| } |
| |
| public static FrameSequence decodeByteBuffer(ByteBuffer buffer) { |
| if (buffer == null) throw new IllegalArgumentException(); |
| if (!buffer.isDirect()) { |
| if (buffer.hasArray()) { |
| byte[] byteArray = buffer.array(); |
| return decodeByteArray(byteArray, buffer.position(), buffer.remaining()); |
| } else { |
| throw new IllegalArgumentException("Cannot have non-direct ByteBuffer with no byte array"); |
| } |
| } |
| return nativeDecodeByteBuffer(buffer, buffer.position(), buffer.remaining()); |
| } |
| |
| public static FrameSequence decodeStream(InputStream stream) { |
| if (stream == null) throw new IllegalArgumentException(); |
| byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool |
| return nativeDecodeStream(stream, tempStorage); |
| } |
| |
| State createState() { |
| if (mNativeFrameSequence == 0) { |
| throw new IllegalStateException("attempted to use incorrectly built FrameSequence"); |
| } |
| |
| long nativeState = nativeCreateState(mNativeFrameSequence); |
| if (nativeState == 0) { |
| return null; |
| } |
| return new State(nativeState); |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| try { |
| if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| /** |
| * Playback state used when moving frames forward in a frame sequence. |
| * |
| * Note that this doesn't require contiguous frames to be rendered, it just stores |
| * information (in the case of gif, a recall buffer) that will be used to construct |
| * frames based upon data recorded before previousFrameNr. |
| * |
| * Note: {@link #destroy()} *must* be called before the object is GC'd to free native resources |
| * |
| * Note: State holds a native ref to its FrameSequence instance, so its FrameSequence should |
| * remain ref'd while it is in use |
| */ |
| static class State { |
| private long mNativeState; |
| |
| public State(long nativeState) { |
| mNativeState = nativeState; |
| } |
| |
| public void destroy() { |
| if (mNativeState != 0) { |
| nativeDestroyState(mNativeState); |
| mNativeState = 0; |
| } |
| } |
| |
| // TODO: consider adding alternate API for drawing into a SurfaceTexture |
| public long getFrame(int frameNr, Bitmap output, int previousFrameNr) { |
| if (output == null || output.getConfig() != Bitmap.Config.ARGB_8888) { |
| throw new IllegalArgumentException("Bitmap passed must be non-null and ARGB_8888"); |
| } |
| if (mNativeState == 0) { |
| throw new IllegalStateException("attempted to draw destroyed FrameSequenceState"); |
| } |
| return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr); |
| } |
| } |
| } |