summaryrefslogtreecommitdiffstats
path: root/framesequence/src/android/support/rastermill/FrameSequence.java
blob: 8ff241fa2145ca892c22ed1a42e675a628603826 (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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
/*
 * 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);
        }
    }
}