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