Home | History | Annotate | Download | only in common
      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.common;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.Bitmap.CompressFormat;
     21 import android.graphics.BitmapFactory;
     22 import android.graphics.Canvas;
     23 import android.graphics.Matrix;
     24 import android.graphics.Paint;
     25 import android.os.Build;
     26 import android.util.Log;
     27 
     28 import java.io.ByteArrayOutputStream;
     29 import java.lang.reflect.InvocationTargetException;
     30 import java.lang.reflect.Method;
     31 
     32 public class BitmapUtils {
     33     private static final String TAG = "BitmapUtils";
     34     private static final int DEFAULT_JPEG_QUALITY = 90;
     35     public static final int UNCONSTRAINED = -1;
     36 
     37     private BitmapUtils(){}
     38 
     39     /*
     40      * Compute the sample size as a function of minSideLength
     41      * and maxNumOfPixels.
     42      * minSideLength is used to specify that minimal width or height of a
     43      * bitmap.
     44      * maxNumOfPixels is used to specify the maximal size in pixels that is
     45      * tolerable in terms of memory usage.
     46      *
     47      * The function returns a sample size based on the constraints.
     48      * Both size and minSideLength can be passed in as UNCONSTRAINED,
     49      * which indicates no care of the corresponding constraint.
     50      * The functions prefers returning a sample size that
     51      * generates a smaller bitmap, unless minSideLength = UNCONSTRAINED.
     52      *
     53      * Also, the function rounds up the sample size to a power of 2 or multiple
     54      * of 8 because BitmapFactory only honors sample size this way.
     55      * For example, BitmapFactory downsamples an image by 2 even though the
     56      * request is 3. So we round up the sample size to avoid OOM.
     57      */
     58     public static int computeSampleSize(int width, int height,
     59             int minSideLength, int maxNumOfPixels) {
     60         int initialSize = computeInitialSampleSize(
     61                 width, height, minSideLength, maxNumOfPixels);
     62 
     63         return initialSize <= 8
     64                 ? Utils.nextPowerOf2(initialSize)
     65                 : (initialSize + 7) / 8 * 8;
     66     }
     67 
     68     private static int computeInitialSampleSize(int w, int h,
     69             int minSideLength, int maxNumOfPixels) {
     70         if (maxNumOfPixels == UNCONSTRAINED
     71                 && minSideLength == UNCONSTRAINED) return 1;
     72 
     73         int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
     74                 (int) Math.ceil(Math.sqrt((double) (w * h) / maxNumOfPixels));
     75 
     76         if (minSideLength == UNCONSTRAINED) {
     77             return lowerBound;
     78         } else {
     79             int sampleSize = Math.min(w / minSideLength, h / minSideLength);
     80             return Math.max(sampleSize, lowerBound);
     81         }
     82     }
     83 
     84     // This computes a sample size which makes the longer side at least
     85     // minSideLength long. If that's not possible, return 1.
     86     public static int computeSampleSizeLarger(int w, int h,
     87             int minSideLength) {
     88         int initialSize = Math.max(w / minSideLength, h / minSideLength);
     89         if (initialSize <= 1) return 1;
     90 
     91         return initialSize <= 8
     92                 ? Utils.prevPowerOf2(initialSize)
     93                 : initialSize / 8 * 8;
     94     }
     95 
     96     // Find the min x that 1 / x >= scale
     97     public static int computeSampleSizeLarger(float scale) {
     98         int initialSize = (int) Math.floor(1d / scale);
     99         if (initialSize <= 1) return 1;
    100 
    101         return initialSize <= 8
    102                 ? Utils.prevPowerOf2(initialSize)
    103                 : initialSize / 8 * 8;
    104     }
    105 
    106     // Find the max x that 1 / x <= scale.
    107     public static int computeSampleSize(float scale) {
    108         Utils.assertTrue(scale > 0);
    109         int initialSize = Math.max(1, (int) Math.ceil(1 / scale));
    110         return initialSize <= 8
    111                 ? Utils.nextPowerOf2(initialSize)
    112                 : (initialSize + 7) / 8 * 8;
    113     }
    114 
    115     public static Bitmap resizeBitmapByScale(
    116             Bitmap bitmap, float scale, boolean recycle) {
    117         int width = Math.round(bitmap.getWidth() * scale);
    118         int height = Math.round(bitmap.getHeight() * scale);
    119         if (width == bitmap.getWidth()
    120                 && height == bitmap.getHeight()) return bitmap;
    121         Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
    122         Canvas canvas = new Canvas(target);
    123         canvas.scale(scale, scale);
    124         Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
    125         canvas.drawBitmap(bitmap, 0, 0, paint);
    126         if (recycle) bitmap.recycle();
    127         return target;
    128     }
    129 
    130     private static Bitmap.Config getConfig(Bitmap bitmap) {
    131         Bitmap.Config config = bitmap.getConfig();
    132         if (config == null) {
    133             config = Bitmap.Config.ARGB_8888;
    134         }
    135         return config;
    136     }
    137 
    138     public static Bitmap resizeDownBySideLength(
    139             Bitmap bitmap, int maxLength, boolean recycle) {
    140         int srcWidth = bitmap.getWidth();
    141         int srcHeight = bitmap.getHeight();
    142         float scale = Math.min(
    143                 (float) maxLength / srcWidth, (float) maxLength / srcHeight);
    144         if (scale >= 1.0f) return bitmap;
    145         return resizeBitmapByScale(bitmap, scale, recycle);
    146     }
    147 
    148     public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
    149         int w = bitmap.getWidth();
    150         int h = bitmap.getHeight();
    151         if (w == size && h == size) return bitmap;
    152 
    153         // scale the image so that the shorter side equals to the target;
    154         // the longer side will be center-cropped.
    155         float scale = (float) size / Math.min(w,  h);
    156 
    157         Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
    158         int width = Math.round(scale * bitmap.getWidth());
    159         int height = Math.round(scale * bitmap.getHeight());
    160         Canvas canvas = new Canvas(target);
    161         canvas.translate((size - width) / 2f, (size - height) / 2f);
    162         canvas.scale(scale, scale);
    163         Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
    164         canvas.drawBitmap(bitmap, 0, 0, paint);
    165         if (recycle) bitmap.recycle();
    166         return target;
    167     }
    168 
    169     public static void recycleSilently(Bitmap bitmap) {
    170         if (bitmap == null) return;
    171         try {
    172             bitmap.recycle();
    173         } catch (Throwable t) {
    174             Log.w(TAG, "unable recycle bitmap", t);
    175         }
    176     }
    177 
    178     public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
    179         if (rotation == 0) return source;
    180         int w = source.getWidth();
    181         int h = source.getHeight();
    182         Matrix m = new Matrix();
    183         m.postRotate(rotation);
    184         Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
    185         if (recycle) source.recycle();
    186         return bitmap;
    187     }
    188 
    189     public static Bitmap createVideoThumbnail(String filePath) {
    190         // MediaMetadataRetriever is available on API Level 8
    191         // but is hidden until API Level 10
    192         Class<?> clazz = null;
    193         Object instance = null;
    194         try {
    195             clazz = Class.forName("android.media.MediaMetadataRetriever");
    196             instance = clazz.newInstance();
    197 
    198             Method method = clazz.getMethod("setDataSource", String.class);
    199             method.invoke(instance, filePath);
    200 
    201             // The method name changes between API Level 9 and 10.
    202             if (Build.VERSION.SDK_INT <= 9) {
    203                 return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
    204             } else {
    205                 byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
    206                 if (data != null) {
    207                     Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
    208                     if (bitmap != null) return bitmap;
    209                 }
    210                 return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
    211             }
    212         } catch (IllegalArgumentException ex) {
    213             // Assume this is a corrupt video file
    214         } catch (RuntimeException ex) {
    215             // Assume this is a corrupt video file.
    216         } catch (InstantiationException e) {
    217             Log.e(TAG, "createVideoThumbnail", e);
    218         } catch (InvocationTargetException e) {
    219             Log.e(TAG, "createVideoThumbnail", e);
    220         } catch (ClassNotFoundException e) {
    221             Log.e(TAG, "createVideoThumbnail", e);
    222         } catch (NoSuchMethodException e) {
    223             Log.e(TAG, "createVideoThumbnail", e);
    224         } catch (IllegalAccessException e) {
    225             Log.e(TAG, "createVideoThumbnail", e);
    226         } finally {
    227             try {
    228                 if (instance != null) {
    229                     clazz.getMethod("release").invoke(instance);
    230                 }
    231             } catch (Exception ignored) {
    232             }
    233         }
    234         return null;
    235     }
    236 
    237     public static byte[] compressToBytes(Bitmap bitmap) {
    238         return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
    239     }
    240 
    241     public static byte[] compressToBytes(Bitmap bitmap, int quality) {
    242         ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
    243         bitmap.compress(CompressFormat.JPEG, quality, baos);
    244         return baos.toByteArray();
    245     }
    246 
    247     public static boolean isSupportedByRegionDecoder(String mimeType) {
    248         if (mimeType == null) return false;
    249         mimeType = mimeType.toLowerCase();
    250         return mimeType.startsWith("image/") &&
    251                 (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
    252     }
    253 
    254     public static boolean isRotationSupported(String mimeType) {
    255         if (mimeType == null) return false;
    256         mimeType = mimeType.toLowerCase();
    257         return mimeType.equals("image/jpeg");
    258     }
    259 }
    260