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