summaryrefslogtreecommitdiffstats
path: root/framesequence/src/android/support/rastermill/FrameSequence.java
blob: 5881ea9d73e9cdf5a9e7e3396f5f525eb75d18e9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
/*
 * 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.io.InputStream;

public class FrameSequence {
    static {
        System.loadLibrary("framesequence");
    }

    private final int mNativeFrameSequence;
    private final int mWidth;
    private final int mHeight;
    private final int mFrameCount;
    private final boolean mOpaque;

    public int getWidth() { return mWidth; }
    public int getHeight() { return mHeight; }
    public int getFrameCount() { return mFrameCount; }
    public boolean isOpaque() { return mOpaque; }

    private static native FrameSequence nativeDecodeByteArray(byte[] data, int offset, int length);
    private static native FrameSequence nativeDecodeStream(InputStream is, byte[] tempStorage);
    private static native void nativeDestroyFrameSequence(int nativeFrameSequence);
    private static native int nativeCreateState(int nativeFrameSequence);
    private static native void nativeDestroyState(int nativeState);
    private static native long nativeGetFrame(int nativeState, int frameNr,
            Bitmap output, int previousFrameNr);

    @SuppressWarnings("unused") // called by native
    private FrameSequence(int nativeFrameSequence, int width, int height,
                          int frameCount, boolean opaque) {
        mNativeFrameSequence = nativeFrameSequence;
        mWidth = width;
        mHeight = height;
        mFrameCount = frameCount;
        mOpaque = opaque;
    }

    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 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");
        }

        int 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 #recycle()} *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 int mNativeState;

        public State(int nativeState) {
            mNativeState = nativeState;
        }

        public void recycle() {
            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 recycled FrameSequenceState");
            }
            return nativeGetFrame(mNativeState, frameNr, output, previousFrameNr);
        }
    }

    // TODO: add recycle() cleanup method
}