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