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     public static final int UNCONSTRAINED = -1;
     35     private static final int COMPRESS_JPEG_QUALITY = 90;
     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     // Fin the min x that 1 / x <= scale
     97     public static int computeSampleSizeLarger(float scale) {
     98         int initialSize = (int) Math.floor(1f / 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 resizeDownToPixels(
    116             Bitmap bitmap, int targetPixels, boolean recycle) {
    117         int width = bitmap.getWidth();
    118         int height = bitmap.getHeight();
    119         float scale = (float) Math.sqrt(
    120                 (double) targetPixels / (width * height));
    121         if (scale >= 1.0f) return bitmap;
    122         return resizeBitmapByScale(bitmap, scale, recycle);
    123     }
    124 
    125     public static Bitmap resizeBitmapByScale(
    126             Bitmap bitmap, float scale, boolean recycle) {
    127         int width = Math.round(bitmap.getWidth() * scale);
    128         int height = Math.round(bitmap.getHeight() * scale);
    129         if (width == bitmap.getWidth()
    130                 && height == bitmap.getHeight()) return bitmap;
    131         Bitmap target = Bitmap.createBitmap(width, height, getConfig(bitmap));
    132         Canvas canvas = new Canvas(target);
    133         canvas.scale(scale, scale);
    134         Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
    135         canvas.drawBitmap(bitmap, 0, 0, paint);
    136         if (recycle) bitmap.recycle();
    137         return target;
    138     }
    139 
    140     private static Bitmap.Config getConfig(Bitmap bitmap) {
    141         Bitmap.Config config = bitmap.getConfig();
    142         if (config == null) {
    143             config = Bitmap.Config.ARGB_8888;
    144         }
    145         return config;
    146     }
    147 
    148     public static Bitmap resizeDownBySideLength(
    149             Bitmap bitmap, int maxLength, boolean recycle) {
    150         int srcWidth = bitmap.getWidth();
    151         int srcHeight = bitmap.getHeight();
    152         float scale = Math.min(
    153                 (float) maxLength / srcWidth, (float) maxLength / srcHeight);
    154         if (scale >= 1.0f) return bitmap;
    155         return resizeBitmapByScale(bitmap, scale, recycle);
    156     }
    157 
    158     // Resize the bitmap if each side is >= targetSize * 2
    159     public static Bitmap resizeDownIfTooBig(
    160             Bitmap bitmap, int targetSize, boolean recycle) {
    161         int srcWidth = bitmap.getWidth();
    162         int srcHeight = bitmap.getHeight();
    163         float scale = Math.max(
    164                 (float) targetSize / srcWidth, (float) targetSize / srcHeight);
    165         if (scale > 0.5f) return bitmap;
    166         return resizeBitmapByScale(bitmap, scale, recycle);
    167     }
    168 
    169     // Crops a square from the center of the original image.
    170     public static Bitmap cropCenter(Bitmap bitmap, boolean recycle) {
    171         int width = bitmap.getWidth();
    172         int height = bitmap.getHeight();
    173         if (width == height) return bitmap;
    174         int size = Math.min(width, height);
    175 
    176         Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
    177         Canvas canvas = new Canvas(target);
    178         canvas.translate((size - width) / 2, (size - height) / 2);
    179         Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG);
    180         canvas.drawBitmap(bitmap, 0, 0, paint);
    181         if (recycle) bitmap.recycle();
    182         return target;
    183     }
    184 
    185     public static Bitmap resizeDownAndCropCenter(Bitmap bitmap, int size,
    186             boolean recycle) {
    187         int w = bitmap.getWidth();
    188         int h = bitmap.getHeight();
    189         int minSide = Math.min(w, h);
    190         if (w == h && minSide <= size) return bitmap;
    191         size = Math.min(size, minSide);
    192 
    193         float scale = Math.max((float) size / bitmap.getWidth(),
    194                 (float) size / bitmap.getHeight());
    195         Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
    196         int width = Math.round(scale * bitmap.getWidth());
    197         int height = Math.round(scale * bitmap.getHeight());
    198         Canvas canvas = new Canvas(target);
    199         canvas.translate((size - width) / 2f, (size - height) / 2f);
    200         canvas.scale(scale, scale);
    201         Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
    202         canvas.drawBitmap(bitmap, 0, 0, paint);
    203         if (recycle) bitmap.recycle();
    204         return target;
    205     }
    206 
    207     public static void recycleSilently(Bitmap bitmap) {
    208         if (bitmap == null) return;
    209         try {
    210             bitmap.recycle();
    211         } catch (Throwable t) {
    212             Log.w(TAG, "unable recycle bitmap", t);
    213         }
    214     }
    215 
    216     public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
    217         if (rotation == 0) return source;
    218         int w = source.getWidth();
    219         int h = source.getHeight();
    220         Matrix m = new Matrix();
    221         m.postRotate(rotation);
    222         Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
    223         if (recycle) source.recycle();
    224         return bitmap;
    225     }
    226 
    227     public static Bitmap createVideoThumbnail(String filePath) {
    228         // MediaMetadataRetriever is available on API Level 8
    229         // but is hidden until API Level 10
    230         Class<?> clazz = null;
    231         Object instance = null;
    232         try {
    233             clazz = Class.forName("android.media.MediaMetadataRetriever");
    234             instance = clazz.newInstance();
    235 
    236             Method method = clazz.getMethod("setDataSource", String.class);
    237             method.invoke(instance, filePath);
    238 
    239             // The method name changes between API Level 9 and 10.
    240             if (Build.VERSION.SDK_INT <= 9) {
    241                 return (Bitmap) clazz.getMethod("captureFrame").invoke(instance);
    242             } else {
    243                 byte[] data = (byte[]) clazz.getMethod("getEmbeddedPicture").invoke(instance);
    244                 if (data != null) {
    245                     Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
    246                     if (bitmap != null) return bitmap;
    247                 }
    248                 return (Bitmap) clazz.getMethod("getFrameAtTime").invoke(instance);
    249             }
    250         } catch (IllegalArgumentException ex) {
    251             // Assume this is a corrupt video file
    252         } catch (RuntimeException ex) {
    253             // Assume this is a corrupt video file.
    254         } catch (InstantiationException e) {
    255             Log.e(TAG, "createVideoThumbnail", e);
    256         } catch (InvocationTargetException e) {
    257             Log.e(TAG, "createVideoThumbnail", e);
    258         } catch (ClassNotFoundException e) {
    259             Log.e(TAG, "createVideoThumbnail", e);
    260         } catch (NoSuchMethodException e) {
    261             Log.e(TAG, "createVideoThumbnail", e);
    262         } catch (IllegalAccessException e) {
    263             Log.e(TAG, "createVideoThumbnail", e);
    264         } finally {
    265             try {
    266                 if (instance != null) {
    267                     clazz.getMethod("release").invoke(instance);
    268                 }
    269             } catch (Exception ignored) {
    270             }
    271         }
    272         return null;
    273     }
    274 
    275     public static byte[] compressBitmap(Bitmap bitmap) {
    276         ByteArrayOutputStream os = new ByteArrayOutputStream();
    277         bitmap.compress(Bitmap.CompressFormat.JPEG,
    278                 COMPRESS_JPEG_QUALITY, os);
    279         return os.toByteArray();
    280     }
    281 
    282     public static boolean isSupportedByRegionDecoder(String mimeType) {
    283         if (mimeType == null) return false;
    284         mimeType = mimeType.toLowerCase();
    285         return mimeType.startsWith("image/") &&
    286                 (!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
    287     }
    288 
    289     public static boolean isRotationSupported(String mimeType) {
    290         if (mimeType == null) return false;
    291         mimeType = mimeType.toLowerCase();
    292         return mimeType.equals("image/jpeg");
    293     }
    294 
    295     public static byte[] compressToBytes(Bitmap bitmap, int quality) {
    296         ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
    297         bitmap.compress(CompressFormat.JPEG, quality, baos);
    298         return baos.toByteArray();
    299     }
    300 }
    301