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
17package com.android.gallery3d.jpegstream;
18
19import android.graphics.Point;
20
21import java.io.FilterInputStream;
22import java.io.IOException;
23import java.io.InputStream;
24
25public class JPEGInputStream extends FilterInputStream {
26    private long JNIPointer = 0; // Used by JNI code. Don't touch.
27
28    private boolean mValidConfig = false;
29    private boolean mConfigChanged = false;
30    private int mFormat = -1;
31    private byte[] mTmpBuffer = new byte[1];
32    private int mWidth = 0;
33    private int mHeight = 0;
34
35    public JPEGInputStream(InputStream in) {
36        super(in);
37    }
38
39    public JPEGInputStream(InputStream in, int format) {
40        super(in);
41        setConfig(format);
42    }
43
44    public boolean setConfig(int format) {
45        // Make sure format is valid
46        switch (format) {
47            case JpegConfig.FORMAT_GRAYSCALE:
48            case JpegConfig.FORMAT_RGB:
49            case JpegConfig.FORMAT_ABGR:
50            case JpegConfig.FORMAT_RGBA:
51                break;
52            default:
53                return false;
54        }
55        mFormat = format;
56        mValidConfig = true;
57        mConfigChanged = true;
58        return true;
59    }
60
61    public Point getDimensions() throws IOException {
62        if (mValidConfig) {
63            applyConfigChange();
64            return new Point(mWidth, mHeight);
65        }
66        return null;
67    }
68
69    @Override
70    public int available() {
71        return 0; // TODO
72    }
73
74    @Override
75    public void close() throws IOException {
76        cleanup();
77        super.close();
78    }
79
80    @Override
81    public synchronized void mark(int readlimit) {
82        // Do nothing
83    }
84
85    @Override
86    public boolean markSupported() {
87        return false;
88    }
89
90    @Override
91    public int read() throws IOException {
92        read(mTmpBuffer, 0, 1);
93        return 0xFF & mTmpBuffer[0];
94    }
95
96    @Override
97    public int read(byte[] buffer) throws IOException {
98        return read(buffer, 0, buffer.length);
99    }
100
101    @Override
102    public int read(byte[] buffer, int offset, int count) throws IOException {
103        if (offset < 0 || count < 0 || (offset + count) > buffer.length) {
104            throw new ArrayIndexOutOfBoundsException(String.format(
105                    " buffer length %d, offset %d, length %d",
106                    buffer.length, offset, count));
107        }
108        if (!mValidConfig) {
109            return 0;
110        }
111        applyConfigChange();
112        int flag = JpegConfig.J_ERROR_FATAL;
113        try {
114            flag = readDecodedBytes(buffer, offset, count);
115        } finally {
116            if (flag < 0) {
117                cleanup();
118            }
119        }
120        if (flag < 0) {
121            switch (flag) {
122                case JpegConfig.J_DONE:
123                    return -1; // Returns -1 after reading EOS.
124                default:
125                    throw new IOException("Error reading jpeg stream");
126            }
127        }
128        return flag;
129    }
130
131    @Override
132    public synchronized void reset() throws IOException {
133        throw new IOException("Reset not supported.");
134    }
135
136    @Override
137    public long skip(long byteCount) throws IOException {
138        if (byteCount <= 0) {
139            return 0;
140        }
141        // Shorten skip to a reasonable amount
142        int flag = skipDecodedBytes((int) (0x7FFFFFFF & byteCount));
143        if (flag < 0) {
144            switch (flag) {
145                case JpegConfig.J_DONE:
146                    return 0; // Returns 0 after reading EOS.
147                default:
148                    throw new IOException("Error skipping jpeg stream");
149            }
150        }
151        return flag;
152    }
153
154    @Override
155    protected void finalize() throws Throwable {
156        try {
157            cleanup();
158        } finally {
159            super.finalize();
160        }
161    }
162
163    private void applyConfigChange() throws IOException {
164        if (mConfigChanged) {
165            cleanup();
166            Point dimens = new Point(0, 0);
167            int flag = setup(dimens, in, mFormat);
168            switch(flag) {
169                case JpegConfig.J_SUCCESS:
170                    break; // allow setup to continue
171                case JpegConfig.J_ERROR_BAD_ARGS:
172                    throw new IllegalArgumentException("Bad arguments to read");
173                default:
174                    throw new IOException("Error to reading jpeg headers.");
175            }
176            mWidth = dimens.x;
177            mHeight = dimens.y;
178            mConfigChanged = false;
179        }
180    }
181
182    native private int setup(Point dimens, InputStream in, int format);
183
184    native private void cleanup();
185
186    native private int readDecodedBytes( byte[] inBuffer, int offset, int inCount);
187
188    native private int skipDecodedBytes(int bytes);
189
190    static {
191        System.loadLibrary("jni_jpegstream");
192    }
193}
194