1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.support.rastermill; 18 19 import android.graphics.Bitmap; 20 import java.nio.ByteBuffer; 21 22 import java.io.InputStream; 23 24 public class FrameSequence { 25 static { 26 System.loadLibrary("framesequence"); 27 } 28 29 private final long mNativeFrameSequence; 30 private final int mWidth; 31 private final int mHeight; 32 private final boolean mOpaque; 33 private final int mFrameCount; 34 private final int mDefaultLoopCount; 35 getWidth()36 public int getWidth() { return mWidth; } getHeight()37 public int getHeight() { return mHeight; } isOpaque()38 public boolean isOpaque() { return mOpaque; } getFrameCount()39 public int getFrameCount() { return mFrameCount; } getDefaultLoopCount()40 public int getDefaultLoopCount() { return mDefaultLoopCount; } 41 nativeDecodeByteArray(byte[] data, int offset, int length)42 private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length); nativeDecodeStream(InputStream is, byte[] tempStorage)43 private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage); nativeDecodeByteBuffer(ByteBuffer buffer, int offset, int capacity)44 private static native FrameSequence nativeDecodeByteBuffer(ByteBuffer buffer, int offset, int capacity); nativeDestroyFrameSequence(long nativeFrameSequence)45 private static native void nativeDestroyFrameSequence(long nativeFrameSequence); nativeCreateState(long nativeFrameSequence)46 private static native long nativeCreateState(long nativeFrameSequence); nativeDestroyState(long nativeState)47 private static native void nativeDestroyState(long nativeState); nativeGetFrame(long nativeState, int frameNr, Bitmap output, int previousFrameNr)48 private static native long nativeGetFrame(long nativeState, int frameNr, 49 Bitmap output, int previousFrameNr); 50 51 @SuppressWarnings("unused") // called by native FrameSequence(long nativeFrameSequence, int width, int height, boolean opaque, int frameCount, int defaultLoopCount)52 private FrameSequence(long nativeFrameSequence, int width, int height, 53 boolean opaque, int frameCount, int defaultLoopCount) { 54 mNativeFrameSequence = nativeFrameSequence; 55 mWidth = width; 56 mHeight = height; 57 mOpaque = opaque; 58 mFrameCount = frameCount; 59 mDefaultLoopCount = defaultLoopCount; 60 } 61 decodeByteArray(byte[] data)62 public static FrameSequence decodeByteArray(byte[] data) { 63 return decodeByteArray(data, 0, data.length); 64 } 65 decodeByteArray(byte[] data, int offset, int length)66 public static FrameSequence decodeByteArray(byte[] data, int offset, int length) { 67 if (data == null) throw new IllegalArgumentException(); 68 if (offset < 0 || length < 0 || (offset + length > data.length)) { 69 throw new IllegalArgumentException("invalid offset/length parameters"); 70 } 71 return nativeDecodeByteArray(data, offset, length); 72 } 73 decodeByteBuffer(ByteBuffer buffer)74 public static FrameSequence decodeByteBuffer(ByteBuffer buffer) { 75 if (buffer == null) throw new IllegalArgumentException(); 76 if (!buffer.isDirect()) { 77 if (buffer.hasArray()) { 78 byte[] byteArray = buffer.array(); 79 return decodeByteArray(byteArray, buffer.position(), buffer.remaining()); 80 } else { 81 throw new IllegalArgumentException("Cannot have non-direct ByteBuffer with no byte array"); 82 } 83 } 84 return nativeDecodeByteBuffer(buffer, buffer.position(), buffer.remaining()); 85 } 86 decodeStream(InputStream stream)87 public static FrameSequence decodeStream(InputStream stream) { 88 if (stream == null) throw new IllegalArgumentException(); 89 byte[] tempStorage = new byte[16 * 1024]; // TODO: use buffer pool 90 return nativeDecodeStream(stream, tempStorage); 91 } 92 createState()93 State createState() { 94 if (mNativeFrameSequence == 0) { 95 throw new IllegalStateException("attempted to use incorrectly built FrameSequence"); 96 } 97 98 long nativeState = nativeCreateState(mNativeFrameSequence); 99 if (nativeState == 0) { 100 return null; 101 } 102 return new State(nativeState); 103 } 104 105 @Override finalize()106 protected void finalize() throws Throwable { 107 try { 108 if (mNativeFrameSequence != 0) nativeDestroyFrameSequence(mNativeFrameSequence); 109 } finally { 110 super.finalize(); 111 } 112 } 113 114 /** 115 * Playback state used when moving frames forward in a frame sequence. 116 * 117 * Note that this doesn't require contiguous frames to be rendered, it just stores 118 * information (in the case of gif, a recall buffer) that will be used to construct 119 * frames based upon data recorded before previousFrameNr. 120 * 121 * Note: {@link #destroy()} *must* be called before the object is GC'd to free native resources 122 * 123 * Note: State holds a native ref to its FrameSequence instance, so its FrameSequence should 124 * remain ref'd while it is in use 125 */ 126 static class State { 127 private long mNativeState; 128 State(long nativeState)129 public State(long nativeState) { 130 mNativeState = nativeState; 131 } 132 destroy()133 public void destroy() { 134 if (mNativeState != 0) { 135 nativeDestroyState(mNativeState); 136 mNativeState = 0; 137 } 138 } 139 140 // TODO: consider adding alternate API for drawing into a SurfaceTexture getFrame(int frameNr, Bitmap output, int previousFrameNr)141 public long getFrame(int frameNr, Bitmap output, int previousFrameNr) { 142 if (output == null || output.getConfig() != Bitmap.Config.ARGB_8888) { 143 throw new IllegalArgumentException("Bitmap passed must be non-null and ARGB_8888"); 144 } 145 if (mNativeState == 0) { 146 throw new IllegalStateException("attempted to draw destroyed FrameSequenceState"); 147 } 148 return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr); 149 } 150 } 151 } 152