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.graphics.Bitmap;
     20 import android.graphics.Bitmap.Config;
     21 import android.graphics.BitmapFactory;
     22 import android.graphics.BitmapFactory.Options;
     23 import android.graphics.BitmapRegionDecoder;
     24 import android.util.FloatMath;
     25 
     26 import com.android.gallery3d.common.BitmapUtils;
     27 import com.android.gallery3d.common.Utils;
     28 import com.android.gallery3d.util.ThreadPool.CancelListener;
     29 import com.android.gallery3d.util.ThreadPool.JobContext;
     30 
     31 import java.io.FileDescriptor;
     32 import java.io.FileInputStream;
     33 import java.io.InputStream;
     34 
     35 public class DecodeUtils {
     36     private static final String TAG = "DecodeService";
     37 
     38     private static class DecodeCanceller implements CancelListener {
     39         Options mOptions;
     40 
     41         public DecodeCanceller(Options options) {
     42             mOptions = options;
     43         }
     44 
     45         @Override
     46         public void onCancel() {
     47             mOptions.requestCancelDecode();
     48         }
     49     }
     50 
     51     public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) {
     52         if (options == null) options = new Options();
     53         jc.setCancelListener(new DecodeCanceller(options));
     54         return ensureGLCompatibleBitmap(
     55                 BitmapFactory.decodeFileDescriptor(fd, null, options));
     56     }
     57 
     58     public static void decodeBounds(JobContext jc, FileDescriptor fd,
     59             Options options) {
     60         Utils.assertTrue(options != null);
     61         options.inJustDecodeBounds = true;
     62         jc.setCancelListener(new DecodeCanceller(options));
     63         BitmapFactory.decodeFileDescriptor(fd, null, options);
     64         options.inJustDecodeBounds = false;
     65     }
     66 
     67     public static Bitmap decode(JobContext jc, byte[] bytes, Options options) {
     68         return decode(jc, bytes, 0, bytes.length, options);
     69     }
     70 
     71     public static Bitmap decode(JobContext jc, byte[] bytes, int offset,
     72             int length, Options options) {
     73         if (options == null) options = new Options();
     74         jc.setCancelListener(new DecodeCanceller(options));
     75         return ensureGLCompatibleBitmap(
     76                 BitmapFactory.decodeByteArray(bytes, offset, length, options));
     77     }
     78 
     79     public static void decodeBounds(JobContext jc, byte[] bytes, int offset,
     80             int length, Options options) {
     81         Utils.assertTrue(options != null);
     82         options.inJustDecodeBounds = true;
     83         jc.setCancelListener(new DecodeCanceller(options));
     84         BitmapFactory.decodeByteArray(bytes, offset, length, options);
     85         options.inJustDecodeBounds = false;
     86     }
     87 
     88     public static Bitmap decodeThumbnail(
     89             JobContext jc, String filePath, Options options, int targetSize, int type) {
     90         FileInputStream fis = null;
     91         try {
     92             fis = new FileInputStream(filePath);
     93             FileDescriptor fd = fis.getFD();
     94             return decodeThumbnail(jc, fd, options, targetSize, type);
     95         } catch (Exception ex) {
     96             Log.w(TAG, ex);
     97             return null;
     98         } finally {
     99             Utils.closeSilently(fis);
    100         }
    101     }
    102 
    103     public static Bitmap decodeThumbnail(
    104             JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {
    105         if (options == null) options = new Options();
    106         jc.setCancelListener(new DecodeCanceller(options));
    107 
    108         options.inJustDecodeBounds = true;
    109         BitmapFactory.decodeFileDescriptor(fd, null, options);
    110         if (jc.isCancelled()) return null;
    111 
    112         int w = options.outWidth;
    113         int h = options.outHeight;
    114 
    115         if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
    116             // We center-crop the original image as it's micro thumbnail. In this case,
    117             // we want to make sure the shorter side >= "targetSize".
    118             float scale = (float) targetSize / Math.min(w, h);
    119             options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
    120 
    121             // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding
    122             // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.
    123             final int MAX_PIXEL_COUNT = 640000; // 400 x 1600
    124             if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {
    125                 options.inSampleSize = BitmapUtils.computeSampleSize(
    126                         FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h)));
    127             }
    128         } else {
    129             // For screen nail, we only want to keep the longer side >= targetSize.
    130             float scale = (float) targetSize / Math.max(w, h);
    131             options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
    132         }
    133 
    134         options.inJustDecodeBounds = false;
    135 
    136         Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
    137         if (result == null) return null;
    138 
    139         // We need to resize down if the decoder does not support inSampleSize
    140         // (For example, GIF images)
    141         float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL
    142                 ? Math.min(result.getWidth(), result.getHeight())
    143                 : Math.max(result.getWidth(), result.getHeight()));
    144 
    145         if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
    146         return ensureGLCompatibleBitmap(result);
    147     }
    148 
    149     /**
    150      * Decodes the bitmap from the given byte array if the image size is larger than the given
    151      * requirement.
    152      *
    153      * Note: The returned image may be resized down. However, both width and height must be
    154      * larger than the <code>targetSize</code>.
    155      */
    156     public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data,
    157             Options options, int targetSize) {
    158         if (options == null) options = new Options();
    159         jc.setCancelListener(new DecodeCanceller(options));
    160 
    161         options.inJustDecodeBounds = true;
    162         BitmapFactory.decodeByteArray(data, 0, data.length, options);
    163         if (jc.isCancelled()) return null;
    164         if (options.outWidth < targetSize || options.outHeight < targetSize) {
    165             return null;
    166         }
    167         options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
    168                 options.outWidth, options.outHeight, targetSize);
    169         options.inJustDecodeBounds = false;
    170         return ensureGLCompatibleBitmap(
    171                 BitmapFactory.decodeByteArray(data, 0, data.length, options));
    172     }
    173 
    174     // TODO: This function should not be called directly from
    175     // DecodeUtils.requestDecode(...), since we don't have the knowledge
    176     // if the bitmap will be uploaded to GL.
    177     public static Bitmap ensureGLCompatibleBitmap(Bitmap bitmap) {
    178         if (bitmap == null || bitmap.getConfig() != null) return bitmap;
    179         Bitmap newBitmap = bitmap.copy(Config.ARGB_8888, false);
    180         bitmap.recycle();
    181         return newBitmap;
    182     }
    183 
    184     public static BitmapRegionDecoder createBitmapRegionDecoder(
    185             JobContext jc, byte[] bytes, int offset, int length,
    186             boolean shareable) {
    187         if (offset < 0 || length <= 0 || offset + length > bytes.length) {
    188             throw new IllegalArgumentException(String.format(
    189                     "offset = %s, length = %s, bytes = %s",
    190                     offset, length, bytes.length));
    191         }
    192 
    193         try {
    194             return BitmapRegionDecoder.newInstance(
    195                     bytes, offset, length, shareable);
    196         } catch (Throwable t)  {
    197             Log.w(TAG, t);
    198             return null;
    199         }
    200     }
    201 
    202     public static BitmapRegionDecoder createBitmapRegionDecoder(
    203             JobContext jc, String filePath, boolean shareable) {
    204         try {
    205             return BitmapRegionDecoder.newInstance(filePath, shareable);
    206         } catch (Throwable t)  {
    207             Log.w(TAG, t);
    208             return null;
    209         }
    210     }
    211 
    212     public static BitmapRegionDecoder createBitmapRegionDecoder(
    213             JobContext jc, FileDescriptor fd, boolean shareable) {
    214         try {
    215             return BitmapRegionDecoder.newInstance(fd, shareable);
    216         } catch (Throwable t)  {
    217             Log.w(TAG, t);
    218             return null;
    219         }
    220     }
    221 
    222     public static BitmapRegionDecoder createBitmapRegionDecoder(
    223             JobContext jc, InputStream is, boolean shareable) {
    224         try {
    225             return BitmapRegionDecoder.newInstance(is, shareable);
    226         } catch (Throwable t)  {
    227             // We often cancel the creating of bitmap region decoder,
    228             // so just log one line.
    229             Log.w(TAG, "requestCreateBitmapRegionDecoder: " + t);
    230             return null;
    231         }
    232     }
    233 }
    234