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
40    /**
41     * Create a BitmapRegionDecoder from the specified byte array.
42     * Currently only the JPEG and PNG formats are supported.
43     *
44     * @param data byte array of compressed image data.
45     * @param offset offset into data for where the decoder should begin
46     *               parsing.
47     * @param length the number of bytes, beginning at offset, to parse
48     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
49     *                    shallow reference to the input. If this is false,
50     *                    then the BitmapRegionDecoder will explicitly make a copy of the
51     *                    input data, and keep that. Even if sharing is allowed,
52     *                    the implementation may still decide to make a deep
53     *                    copy of the input data. If an image is progressively encoded,
54     *                    allowing sharing may degrade the decoding speed.
55     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
56     * @throws IOException if the image format is not supported or can not be decoded.
57     */
58    public static BitmapRegionDecoder newInstance(byte[] data,
59            int offset, int length, boolean isShareable) throws IOException {
60        if ((offset | length) < 0 || data.length < offset + length) {
61            throw new ArrayIndexOutOfBoundsException();
62        }
63        return nativeNewInstance(data, offset, length, isShareable);
64    }
65
66    /**
67     * Create a BitmapRegionDecoder from the file descriptor.
68     * The position within the descriptor will not be changed when
69     * this returns, so the descriptor can be used again as is.
70     * Currently only the JPEG and PNG formats are supported.
71     *
72     * @param fd The file descriptor containing the data to decode
73     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
74     *                    shallow reference to the input. If this is false,
75     *                    then the BitmapRegionDecoder will explicitly make a copy of the
76     *                    input data, and keep that. Even if sharing is allowed,
77     *                    the implementation may still decide to make a deep
78     *                    copy of the input data. If an image is progressively encoded,
79     *                    allowing sharing may degrade the decoding speed.
80     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
81     * @throws IOException if the image format is not supported or can not be decoded.
82     */
83    public static BitmapRegionDecoder newInstance(
84            FileDescriptor fd, boolean isShareable) throws IOException {
85        return nativeNewInstance(fd, isShareable);
86    }
87
88    /**
89     * Create a BitmapRegionDecoder from an input stream.
90     * The stream's position will be where ever it was after the encoded data
91     * was read.
92     * Currently only the JPEG and PNG formats are supported.
93     *
94     * @param is The input stream that holds the raw data to be decoded into a
95     *           BitmapRegionDecoder.
96     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
97     *                    shallow reference to the input. If this is false,
98     *                    then the BitmapRegionDecoder will explicitly make a copy of the
99     *                    input data, and keep that. Even if sharing is allowed,
100     *                    the implementation may still decide to make a deep
101     *                    copy of the input data. If an image is progressively encoded,
102     *                    allowing sharing may degrade the decoding speed.
103     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
104     * @throws IOException if the image format is not supported or can not be decoded.
105     */
106    public static BitmapRegionDecoder newInstance(InputStream is,
107            boolean isShareable) throws IOException {
108        // we need mark/reset to work properly in JNI
109
110        if (!is.markSupported()) {
111            is = new BufferedInputStream(is, 16 * 1024);
112        }
113
114        if (is instanceof AssetManager.AssetInputStream) {
115            return nativeNewInstance(
116                    ((AssetManager.AssetInputStream) is).getAssetInt(),
117                    isShareable);
118        } else {
119            // pass some temp storage down to the native code. 1024 is made up,
120            // but should be large enough to avoid too many small calls back
121            // into is.read(...).
122            byte [] tempStorage = new byte[16 * 1024];
123            return nativeNewInstance(is, tempStorage, isShareable);
124        }
125    }
126
127    /**
128     * Create a BitmapRegionDecoder from a file path.
129     * Currently only the JPEG and PNG formats are supported.
130     *
131     * @param pathName complete path name for the file to be decoded.
132     * @param isShareable If this is true, then the BitmapRegionDecoder may keep a
133     *                    shallow reference to the input. If this is false,
134     *                    then the BitmapRegionDecoder will explicitly make a copy of the
135     *                    input data, and keep that. Even if sharing is allowed,
136     *                    the implementation may still decide to make a deep
137     *                    copy of the input data. If an image is progressively encoded,
138     *                    allowing sharing may degrade the decoding speed.
139     * @return BitmapRegionDecoder, or null if the image data could not be decoded.
140     * @throws IOException if the image format is not supported or can not be decoded.
141     */
142    public static BitmapRegionDecoder newInstance(String pathName,
143            boolean isShareable) throws IOException {
144        BitmapRegionDecoder decoder = null;
145        InputStream stream = null;
146
147        try {
148            stream = new FileInputStream(pathName);
149            decoder = newInstance(stream, isShareable);
150        } finally {
151            if (stream != null) {
152                try {
153                    stream.close();
154                } catch (IOException e) {
155                    // do nothing here
156                }
157            }
158        }
159        return decoder;
160    }
161
162    /*  Private constructor that must receive an already allocated native
163        region decoder int (pointer).
164
165        This can be called from JNI code.
166    */
167    private BitmapRegionDecoder(int decoder) {
168        mNativeBitmapRegionDecoder = decoder;
169        mRecycled = false;
170    }
171
172    /**
173     * Decodes a rectangle region in the image specified by rect.
174     *
175     * @param rect The rectangle that specified the region to be decode.
176     * @param options null-ok; Options that control downsampling.
177     *             inPurgeable is not supported.
178     * @return The decoded bitmap, or null if the image data could not be
179     *         decoded.
180     */
181    public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
182        checkRecycled("decodeRegion called on recycled region decoder");
183        if (rect.right <= 0 || rect.bottom <= 0 || rect.left >= getWidth()
184                || rect.top >= getHeight())
185            throw new IllegalArgumentException("rectangle is outside the image");
186        return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
187                rect.right - rect.left, rect.bottom - rect.top, options);
188    }
189
190    /** Returns the original image's width */
191    public int getWidth() {
192        checkRecycled("getWidth called on recycled region decoder");
193        return nativeGetWidth(mNativeBitmapRegionDecoder);
194    }
195
196    /** Returns the original image's height */
197    public int getHeight() {
198        checkRecycled("getHeight called on recycled region decoder");
199        return nativeGetHeight(mNativeBitmapRegionDecoder);
200    }
201
202    /**
203     * Frees up the memory associated with this region decoder, and mark the
204     * region decoder as "dead", meaning it will throw an exception if decodeRegion(),
205     * getWidth() or getHeight() is called.
206     *
207     * <p>This operation cannot be reversed, so it should only be called if you are
208     * sure there are no further uses for the region decoder. This is an advanced call,
209     * and normally need not be called, since the normal GC process will free up this
210     * memory when there are no more references to this region decoder.
211     */
212    public void recycle() {
213        if (!mRecycled) {
214            nativeClean(mNativeBitmapRegionDecoder);
215            mRecycled = true;
216        }
217    }
218
219    /**
220     * Returns true if this region decoder has been recycled.
221     * If so, then it is an error to try use its method.
222     *
223     * @return true if the region decoder has been recycled
224     */
225    public final boolean isRecycled() {
226        return mRecycled;
227    }
228
229    /**
230     * Called by methods that want to throw an exception if the region decoder
231     * has already been recycled.
232     */
233    private void checkRecycled(String errorMessage) {
234        if (mRecycled) {
235            throw new IllegalStateException(errorMessage);
236        }
237    }
238
239    @Override
240    protected void finalize() throws Throwable {
241        try {
242            recycle();
243        } finally {
244            super.finalize();
245        }
246    }
247
248    private static native Bitmap nativeDecodeRegion(int lbm,
249            int start_x, int start_y, int width, int height,
250            BitmapFactory.Options options);
251    private static native int nativeGetWidth(int lbm);
252    private static native int nativeGetHeight(int lbm);
253    private static native void nativeClean(int lbm);
254
255    private static native BitmapRegionDecoder nativeNewInstance(
256            byte[] data, int offset, int length, boolean isShareable);
257    private static native BitmapRegionDecoder nativeNewInstance(
258            FileDescriptor fd, boolean isShareable);
259    private static native BitmapRegionDecoder nativeNewInstance(
260            InputStream is, byte[] storage, boolean isShareable);
261    private static native BitmapRegionDecoder nativeNewInstance(
262            int asset, boolean isShareable);
263}
264