1/* Copyright (C) 2010 The Android Open Source Project
2 *
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16package android.graphics;
17
18import android.content.res.AssetManager;
19
20import java.io.BufferedInputStream;
21import java.io.FileDescriptor;
22import java.io.FileInputStream;
23import java.io.IOException;
24import java.io.InputStream;
25
26/**
27 * BitmapRegionDecoder can be used to decode a rectangle region from an image.
28 * BitmapRegionDecoder is particularly useful when an original image is large and
29 * you only need parts of the image.
30 *
31 * <p>To create a BitmapRegionDecoder, call newInstance(...).
32 * Given a BitmapRegionDecoder, users can call decodeRegion() repeatedly
33 * to get a decoded Bitmap of the specified region.
34 *
35 */
36public final class BitmapRegionDecoder {
37    private int mNativeBitmapRegionDecoder;
38    private boolean mRecycled;
39    // ensures that the native decoder object exists and that only one decode can
40    // occur at a time.
41    private final Object mNativeLock = new Object();
42
43    /**
44     * Create a BitmapRegionDecoder from the specified byte array.
45     * Currently only the JPEG and PNG formats are supported.
46     *
47     * @param data byte array of compressed image data.
48     * @param offset offset into data for where the decoder should begin
49     *               parsing.
50     * @param length the number of bytes, beginning at offset, to parse
51     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
52     *                    shallow reference to the input. If this is false,
53     *                    then the BitmapRegionDecoder will explicitly make a copy of the
54     *                    input data, and keep that. Even if sharing is allowed,
55     *                    the implementation may still decide to make a deep
56     *                    copy of the input data. If an image is progressively encoded,
57     *                    allowing sharing may degrade the decoding speed.
58     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
59     * @throws IOException if the image format is not supported or can not be decoded.
60     */
61    public static BitmapRegionDecoder newInstance(byte[] data,
62            int offset, int length, boolean isShareable) throws IOException {
63        if ((offset | length) < 0 || data.length < offset + length) {
64            throw new ArrayIndexOutOfBoundsException();
65        }
66        return nativeNewInstance(data, offset, length, isShareable);
67    }
68
69    /**
70     * Create a BitmapRegionDecoder from the file descriptor.
71     * The position within the descriptor will not be changed when
72     * this returns, so the descriptor can be used again as is.
73     * Currently only the JPEG and PNG formats are supported.
74     *
75     * @param fd The file descriptor containing the data to decode
76     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
77     *                    shallow reference to the input. If this is false,
78     *                    then the BitmapRegionDecoder will explicitly make a copy of the
79     *                    input data, and keep that. Even if sharing is allowed,
80     *                    the implementation may still decide to make a deep
81     *                    copy of the input data. If an image is progressively encoded,
82     *                    allowing sharing may degrade the decoding speed.
83     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
84     * @throws IOException if the image format is not supported or can not be decoded.
85     */
86    public static BitmapRegionDecoder newInstance(
87            FileDescriptor fd, boolean isShareable) throws IOException {
88        return nativeNewInstance(fd, isShareable);
89    }
90
91    /**
92     * Create a BitmapRegionDecoder from an input stream.
93     * The stream's position will be where ever it was after the encoded data
94     * was read.
95     * Currently only the JPEG and PNG formats are supported.
96     *
97     * @param is The input stream that holds the raw data to be decoded into a
98     *           BitmapRegionDecoder.
99     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
100     *                    shallow reference to the input. If this is false,
101     *                    then the BitmapRegionDecoder will explicitly make a copy of the
102     *                    input data, and keep that. Even if sharing is allowed,
103     *                    the implementation may still decide to make a deep
104     *                    copy of the input data. If an image is progressively encoded,
105     *                    allowing sharing may degrade the decoding speed.
106     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
107     * @throws IOException if the image format is not supported or can not be decoded.
108     */
109    public static BitmapRegionDecoder newInstance(InputStream is,
110            boolean isShareable) throws IOException {
111        // we need mark/reset to work properly in JNI
112
113        if (!is.markSupported()) {
114            is = new BufferedInputStream(is, 16 * 1024);
115        }
116
117        if (is instanceof AssetManager.AssetInputStream) {
118            return nativeNewInstance(
119                    ((AssetManager.AssetInputStream) is).getAssetInt(),
120                    isShareable);
121        } else {
122            // pass some temp storage down to the native code. 1024 is made up,
123            // but should be large enough to avoid too many small calls back
124            // into is.read(...).
125            byte [] tempStorage = new byte[16 * 1024];
126            return nativeNewInstance(is, tempStorage, isShareable);
127        }
128    }
129
130    /**
131     * Create a BitmapRegionDecoder from a file path.
132     * Currently only the JPEG and PNG formats are supported.
133     *
134     * @param pathName complete path name for the file to be decoded.
135     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
136     *                    shallow reference to the input. If this is false,
137     *                    then the BitmapRegionDecoder will explicitly make a copy of the
138     *                    input data, and keep that. Even if sharing is allowed,
139     *                    the implementation may still decide to make a deep
140     *                    copy of the input data. If an image is progressively encoded,
141     *                    allowing sharing may degrade the decoding speed.
142     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
143     * @throws IOException if the image format is not supported or can not be decoded.
144     */
145    public static BitmapRegionDecoder newInstance(String pathName,
146            boolean isShareable) throws IOException {
147        BitmapRegionDecoder decoder = null;
148        InputStream stream = null;
149
150        try {
151            stream = new FileInputStream(pathName);
152            decoder = newInstance(stream, isShareable);
153        } finally {
154            if (stream != null) {
155                try {
156                    stream.close();
157                } catch (IOException e) {
158                    // do nothing here
159                }
160            }
161        }
162        return decoder;
163    }
164
165    /*  Private constructor that must receive an already allocated native
166        region decoder int (pointer).
167
168        This can be called from JNI code.
169    */
170    private BitmapRegionDecoder(int decoder) {
171        mNativeBitmapRegionDecoder = decoder;
172        mRecycled = false;
173    }
174
175    /**
176     * Decodes a rectangle region in the image specified by rect.
177     *
178     * @param rect The rectangle that specified the region to be decode.
179     * @param options null-ok; Options that control downsampling.
180     *             inPurgeable is not supported.
181     * @return The decoded bitmap, or null if the image data could not be
182     *         decoded.
183     */
184    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
185        synchronized (mNativeLock) {
186            checkRecycled("decodeRegion called on recycled region decoder");
187            if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
188                    || rect.top >= getHeight())
189                throw new IllegalArgumentException("rectangle is outside the image");
190            return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
191                    rect.right - rect.left, rect.bottom - rect.top, options);
192        }
193    }
194
195    /** Returns the original image's width */
196    public int getWidth() {
197        synchronized (mNativeLock) {
198            checkRecycled("getWidth called on recycled region decoder");
199            return nativeGetWidth(mNativeBitmapRegionDecoder);
200        }
201    }
202
203    /** Returns the original image's height */
204    public int getHeight() {
205        synchronized (mNativeLock) {
206            checkRecycled("getHeight called on recycled region decoder");
207            return nativeGetHeight(mNativeBitmapRegionDecoder);
208        }
209    }
210
211    /**
212     * Frees up the memory associated with this region decoder, and mark the
213     * region decoder as "dead", meaning it will throw an exception if decodeRegion(),
214     * getWidth() or getHeight() is called.
215     *
216     * <p>This operation cannot be reversed, so it should only be called if you are
217     * sure there are no further uses for the region decoder. This is an advanced call,
218     * and normally need not be called, since the normal GC process will free up this
219     * memory when there are no more references to this region decoder.
220     */
221    public void recycle() {
222        synchronized (mNativeLock) {
223            if (!mRecycled) {
224                nativeClean(mNativeBitmapRegionDecoder);
225                mRecycled = true;
226            }
227        }
228    }
229
230    /**
231     * Returns true if this region decoder has been recycled.
232     * If so, then it is an error to try use its method.
233     *
234     * @return true if the region decoder has been recycled
235     */
236    public final boolean isRecycled() {
237        return mRecycled;
238    }
239
240    /**
241     * Called by methods that want to throw an exception if the region decoder
242     * has already been recycled.
243     */
244    private void checkRecycled(String errorMessage) {
245        if (mRecycled) {
246            throw new IllegalStateException(errorMessage);
247        }
248    }
249
250    @Override
251    protected void finalize() throws Throwable {
252        try {
253            recycle();
254        } finally {
255            super.finalize();
256        }
257    }
258
259    private static native Bitmap nativeDecodeRegion(int lbm,
260            int start_x, int start_y, int width, int height,
261            BitmapFactory.Options options);
262    private static native int nativeGetWidth(int lbm);
263    private static native int nativeGetHeight(int lbm);
264    private static native void nativeClean(int lbm);
265
266    private static native BitmapRegionDecoder nativeNewInstance(
267            byte[] data, int offset, int length, boolean isShareable);
268    private static native BitmapRegionDecoder nativeNewInstance(
269            FileDescriptor fd, boolean isShareable);
270    private static native BitmapRegionDecoder nativeNewInstance(
271            InputStream is, byte[] storage, boolean isShareable);
272    private static native BitmapRegionDecoder nativeNewInstance(
273            int asset, boolean isShareable);
274}
275