Home | History | Annotate | Download | only in data
      1 /*
      2  * Copyright (C) 2010 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 
     17 package com.android.gallery3d.data;
     18 
     19 import android.annotation.TargetApi;
     20 import android.graphics.Bitmap;
     21 import android.graphics.Bitmap.Config;
     22 import android.graphics.BitmapFactory;
     23 import android.graphics.BitmapFactory.Options;
     24 import android.graphics.BitmapRegionDecoder;
     25 import android.os.Build;
     26 
     27 import com.android.gallery3d.common.ApiHelper;
     28 import com.android.gallery3d.common.BitmapUtils;
     29 import com.android.gallery3d.common.Utils;
     30 import com.android.photos.data.GalleryBitmapPool;
     31 import com.android.gallery3d.ui.Log;
     32 import com.android.gallery3d.util.ThreadPool.CancelListener;
     33 import com.android.gallery3d.util.ThreadPool.JobContext;
     34 
     35 import java.io.FileDescriptor;
     36 import java.io.FileInputStream;
     37 import java.io.InputStream;
     38 
     39 public class DecodeUtils {
     40     private static final String TAG = "DecodeUtils";
     41 
     42     private static class DecodeCanceller implements CancelListener {
     43         Options mOptions;
     44 
     45         public DecodeCanceller(Options options) {
     46             mOptions = options;
     47         }
     48 
     49         @Override
     50         public void onCancel() {
     51             mOptions.requestCancelDecode();
     52         }
     53     }
     54 
     55     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
     56     public static void setOptionsMutable(Options options) {
     57         if (ApiHelper.HAS_OPTIONS_IN_MUTABLE) options.inMutable = true;
     58     }
     59 
     60     public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) {
     61         if (options == null) options = new Options();
     62         jc.setCancelListener(new DecodeCanceller(options));
     63         setOptionsMutable(options);
     64         return ensureGLCompatibleBitmap(
     65                 BitmapFactory.decodeFileDescriptor(fd, null, options));
     66     }
     67 
     68     public static void decodeBounds(JobContext jc, FileDescriptor fd,
     69             Options options) {
     70         Utils.assertTrue(options != null);
     71         options.inJustDecodeBounds = true;
     72         jc.setCancelListener(new DecodeCanceller(options));
     73         BitmapFactory.decodeFileDescriptor(fd, null, options);
     74         options.inJustDecodeBounds = false;
     75     }
     76 
     77     public static Bitmap decode(JobContext jc, byte[] bytes, Options options) {
     78         return decode(jc, bytes, 0, bytes.length, options);
     79     }
     80 
     81     public static Bitmap decode(JobContext jc, byte[] bytes, int offset,
     82             int length, Options options) {
     83         if (options == null) options = new Options();
     84         jc.setCancelListener(new DecodeCanceller(options));
     85         setOptionsMutable(options);
     86         return ensureGLCompatibleBitmap(
     87                 BitmapFactory.decodeByteArray(bytes, offset, length, options));
     88     }
     89 
     90     public static void decodeBounds(JobContext jc, byte[] bytes, int offset,
     91             int length, Options options) {
     92         Utils.assertTrue(options != null);
     93         options.inJustDecodeBounds = true;
     94         jc.setCancelListener(new DecodeCanceller(options));
     95         BitmapFactory.decodeByteArray(bytes, offset, length, options);
     96         options.inJustDecodeBounds = false;
     97     }
     98 
     99     public static Bitmap decodeThumbnail(
    100             JobContext jc, String filePath, Options options, int targetSize, int type) {
    101         FileInputStream fis = null;
    102         try {
    103             fis = new FileInputStream(filePath);
    104             FileDescriptor fd = fis.getFD();
    105             return decodeThumbnail(jc, fd, options, targetSize, type);
    106         } catch (Exception ex) {
    107             Log.w(TAG, ex);
    108             return null;
    109         } finally {
    110             Utils.closeSilently(fis);
    111         }
    112     }
    113 
    114     public static Bitmap decodeThumbnail(
    115             JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {
    116         if (options == null) options = new Options();
    117         jc.setCancelListener(new DecodeCanceller(options));
    118 
    119         options.inJustDecodeBounds = true;
    120         BitmapFactory.decodeFileDescriptor(fd, null, options);
    121         if (jc.isCancelled()) return null;
    122 
    123         int w = options.outWidth;
    124         int h = options.outHeight;
    125 
    126         if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
    127             // We center-crop the original image as it's micro thumbnail. In this case,
    128             // we want to make sure the shorter side >= "targetSize".
    129             float scale = (float) targetSize / Math.min(w, h);
    130             options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
    131 
    132             // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding
    133             // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.
    134             final int MAX_PIXEL_COUNT = 640000; // 400 x 1600
    135             if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {
    136                 options.inSampleSize = BitmapUtils.computeSampleSize(
    137                         (float) Math.sqrt((double) MAX_PIXEL_COUNT / (w * h)));
    138             }
    139         } else {
    140             // For screen nail, we only want to keep the longer side >= targetSize.
    141             float scale = (float) targetSize / Math.max(w, h);
    142             options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
    143         }
    144 
    145         options.inJustDecodeBounds = false;
    146         setOptionsMutable(options);
    147 
    148         Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
    149         if (result == null) return null;
    150 
    151         // We need to resize down if the decoder does not support inSampleSize
    152         // (For example, GIF images)
    153         float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL
    154                 ? Math.min(result.getWidth(), result.getHeight())
    155                 : Math.max(result.getWidth(), result.getHeight()));
    156 
    157         if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
    158         return ensureGLCompatibleBitmap(result);
    159     }
    160 
    161     /**
    162      * Decodes the bitmap from the given byte array if the image size is larger than the given
    163      * requirement.
    164      *
    165      * Note: The returned image may be resized down. However, both width and height must be
    166      * larger than the <code>targetSize</code>.
    167      */
    168     public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data,
    169             Options options, int targetSize) {
    170         if (options == null) options = new Options();
    171         jc.setCancelListener(new DecodeCanceller(options));
    172 
    173         options.inJustDecodeBounds = true;
    174         BitmapFactory.decodeByteArray(data, 0, data.length, options);
    175         if (jc.isCancelled()) return null;
    176         if (options.outWidth < targetSize || options.outHeight < targetSize) {
    177             return null;
    178         }
    179         options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
    180                 options.outWidth, options.outHeight, targetSize);
    181         options.inJustDecodeBounds = false;
    182         setOptionsMutable(options);
    183 
    184         return ensureGLCompatibleBitmap(
    185                 BitmapFactory.decodeByteArray(data, 0, data.length, options));
    186     }
    187 
    188     // TODO: This function should not be called directly from
    189     // DecodeUtils.requestDecode(...), since we don't have the knowledge
    190     // if the bitmap will be uploaded to GL.
    191     public static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
    192         if (bitmap == null || bitmap.getConfig() != null) return bitmap;
    193         Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
    194         bitmap.recycle();
    195         return newBitmap;
    196     }
    197 
    198     public static BitmapRegionDecoder createBitmapRegionDecoder(
    199             JobContext jc, byte[] bytes, int offset, int length,
    200             boolean shareable) {
    201         if (offset < 0 || length <= 0 || offset + length > bytes.length) {
    202             throw new IllegalArgumentException(String.format(
    203                     "offset = %s, length = %s, bytes = %s",
    204                     offset, length, bytes.length));
    205         }
    206 
    207         try {
    208             return BitmapRegionDecoder.newInstance(
    209                     bytes, offset, length, shareable);
    210         } catch (Throwable t)  {
    211             Log.w(TAG, t);
    212             return null;
    213         }
    214     }
    215 
    216     public static BitmapRegionDecoder createBitmapRegionDecoder(
    217             JobContext jc, String filePath, boolean shareable) {
    218         try {
    219             return BitmapRegionDecoder.newInstance(filePath, shareable);
    220         } catch (Throwable t)  {
    221             Log.w(TAG, t);
    222             return null;
    223         }
    224     }
    225 
    226     public static BitmapRegionDecoder createBitmapRegionDecoder(
    227             JobContext jc, FileDescriptor fd, boolean shareable) {
    228         try {
    229             return BitmapRegionDecoder.newInstance(fd, shareable);
    230         } catch (Throwable t)  {
    231             Log.w(TAG, t);
    232             return null;
    233         }
    234     }
    235 
    236     public static BitmapRegionDecoder createBitmapRegionDecoder(
    237             JobContext jc, InputStream is, boolean shareable) {
    238         try {
    239             return BitmapRegionDecoder.newInstance(is, shareable);
    240         } catch (Throwable t)  {
    241             // We often cancel the creating of bitmap region decoder,
    242             // so just log one line.
    243             Log.w(TAG, "requestCreateBitmapRegionDecoder: " + t);
    244             return null;
    245         }
    246     }
    247 
    248     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    249     public static Bitmap decodeUsingPool(JobContext jc, byte[] data, int offset,
    250             int length, BitmapFactory.Options options) {
    251         if (options == null) options = new BitmapFactory.Options();
    252         if (options.inSampleSize < 1) options.inSampleSize = 1;
    253         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    254         options.inBitmap = (options.inSampleSize == 1)
    255                 ? findCachedBitmap(jc, data, offset, length, options) : null;
    256         try {
    257             Bitmap bitmap = decode(jc, data, offset, length, options);
    258             if (options.inBitmap != null && options.inBitmap != bitmap) {
    259                 GalleryBitmapPool.getInstance().put(options.inBitmap);
    260                 options.inBitmap = null;
    261             }
    262             return bitmap;
    263         } catch (IllegalArgumentException e) {
    264             if (options.inBitmap == null) throw e;
    265 
    266             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
    267             GalleryBitmapPool.getInstance().put(options.inBitmap);
    268             options.inBitmap = null;
    269             return decode(jc, data, offset, length, options);
    270         }
    271     }
    272 
    273     // This is the same as the method above except the source data comes
    274     // from a file descriptor instead of a byte array.
    275     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    276     public static Bitmap decodeUsingPool(JobContext jc,
    277             FileDescriptor fileDescriptor, Options options) {
    278         if (options == null) options = new BitmapFactory.Options();
    279         if (options.inSampleSize < 1) options.inSampleSize = 1;
    280         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    281         options.inBitmap = (options.inSampleSize == 1)
    282                 ? findCachedBitmap(jc, fileDescriptor, options) : null;
    283         try {
    284             Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
    285             if (options.inBitmap != null && options.inBitmap != bitmap) {
    286                 GalleryBitmapPool.getInstance().put(options.inBitmap);
    287                 options.inBitmap = null;
    288             }
    289             return bitmap;
    290         } catch (IllegalArgumentException e) {
    291             if (options.inBitmap == null) throw e;
    292 
    293             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
    294             GalleryBitmapPool.getInstance().put(options.inBitmap);
    295             options.inBitmap = null;
    296             return decode(jc, fileDescriptor, options);
    297         }
    298     }
    299 
    300     private static Bitmap findCachedBitmap(JobContext jc, byte[] data,
    301             int offset, int length, Options options) {
    302         decodeBounds(jc, data, offset, length, options);
    303         return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
    304     }
    305 
    306     private static Bitmap findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor,
    307             Options options) {
    308         decodeBounds(jc, fileDescriptor, options);
    309         return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
    310     }
    311 }
    312