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