Home | History | Annotate | Download | only in photoeditor
      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.photoeditor;
     18 
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Bitmap.CompressFormat;
     23 import android.graphics.BitmapFactory;
     24 import android.graphics.Matrix;
     25 import android.graphics.Rect;
     26 import android.net.Uri;
     27 import android.provider.MediaStore.Images.ImageColumns;
     28 import android.util.Log;
     29 
     30 import java.io.Closeable;
     31 import java.io.File;
     32 import java.io.FileNotFoundException;
     33 import java.io.FileOutputStream;
     34 import java.io.IOException;
     35 import java.io.InputStream;
     36 import java.io.OutputStream;
     37 
     38 /**
     39  * Utils for bitmap operations.
     40  */
     41 public class BitmapUtils {
     42 
     43     private static final String TAG = "BitmapUtils";
     44     private static final int DEFAULT_COMPRESS_QUALITY = 90;
     45     private static final int INDEX_ORIENTATION = 0;
     46 
     47     private static final String[] IMAGE_PROJECTION = new String[] {
     48         ImageColumns.ORIENTATION
     49     };
     50 
     51     private final Context context;
     52 
     53     public BitmapUtils(Context context) {
     54         this.context = context;
     55     }
     56 
     57     /**
     58      * Returns an immutable bitmap from subset of source bitmap transformed by the given matrix.
     59      */
     60     public static Bitmap createBitmap(Bitmap bitmap, Matrix m) {
     61         // TODO: Re-implement createBitmap to avoid ARGB -> RBG565 conversion on platforms
     62         // prior to honeycomb.
     63         return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, true);
     64     }
     65 
     66     private void closeStream(Closeable stream) {
     67         if (stream != null) {
     68             try {
     69                 stream.close();
     70             } catch (IOException e) {
     71                 e.printStackTrace();
     72             }
     73         }
     74     }
     75 
     76     private Rect getBitmapBounds(Uri uri) {
     77         Rect bounds = new Rect();
     78         InputStream is = null;
     79 
     80         try {
     81             is = context.getContentResolver().openInputStream(uri);
     82             BitmapFactory.Options options = new BitmapFactory.Options();
     83             options.inJustDecodeBounds = true;
     84             BitmapFactory.decodeStream(is, null, options);
     85 
     86             bounds.right = options.outWidth;
     87             bounds.bottom = options.outHeight;
     88         } catch (FileNotFoundException e) {
     89             e.printStackTrace();
     90         } finally {
     91             closeStream(is);
     92         }
     93 
     94         return bounds;
     95     }
     96 
     97     private int getOrientation(Uri uri) {
     98         int orientation = 0;
     99         Cursor cursor = context.getContentResolver().query(uri, IMAGE_PROJECTION, null, null, null);
    100         if ((cursor != null) && cursor.moveToNext()) {
    101             orientation = cursor.getInt(INDEX_ORIENTATION);
    102         }
    103         return orientation;
    104     }
    105 
    106     /**
    107      * Decodes immutable bitmap that keeps aspect-ratio and spans most within the given rectangle.
    108      */
    109     private Bitmap decodeBitmap(Uri uri, int width, int height) {
    110         InputStream is = null;
    111         Bitmap bitmap = null;
    112 
    113         try {
    114             // TODO: Take max pixels allowed into account for calculation to avoid possible OOM.
    115             Rect bounds = getBitmapBounds(uri);
    116             int sampleSize = Math.max(bounds.width() / width, bounds.height() / height);
    117             sampleSize = Math.min(sampleSize,
    118                     Math.max(bounds.width() / height, bounds.height() / width));
    119 
    120             BitmapFactory.Options options = new BitmapFactory.Options();
    121             options.inSampleSize = Math.max(sampleSize, 1);
    122             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    123 
    124             is = context.getContentResolver().openInputStream(uri);
    125             bitmap = BitmapFactory.decodeStream(is, null, options);
    126         } catch (FileNotFoundException e) {
    127             Log.e(TAG, "FileNotFoundException: " + uri);
    128         } finally {
    129             closeStream(is);
    130         }
    131 
    132         // Scale down the sampled bitmap if it's still larger than the desired dimension.
    133         if (bitmap != null) {
    134             float scale = Math.min((float) width / bitmap.getWidth(),
    135                     (float) height / bitmap.getHeight());
    136             scale = Math.max(scale, Math.min((float) height / bitmap.getWidth(),
    137                     (float) width / bitmap.getHeight()));
    138             if (scale < 1) {
    139                 Matrix m = new Matrix();
    140                 m.setScale(scale, scale);
    141                 Bitmap transformed = createBitmap(bitmap, m);
    142                 bitmap.recycle();
    143                 return transformed;
    144             }
    145         }
    146         return bitmap;
    147     }
    148 
    149     /**
    150      * Gets decoded bitmap that keeps orientation as well.
    151      */
    152     public Bitmap getBitmap(Uri uri, int width, int height) {
    153         Bitmap bitmap = decodeBitmap(uri, width, height);
    154 
    155         // Rotate the decoded bitmap according to its orientation if it's necessary.
    156         if (bitmap != null) {
    157             int orientation = getOrientation(uri);
    158             if (orientation != 0) {
    159                 Matrix m = new Matrix();
    160                 m.setRotate(orientation);
    161                 Bitmap transformed = createBitmap(bitmap, m);
    162                 bitmap.recycle();
    163                 return transformed;
    164             }
    165         }
    166         return bitmap;
    167     }
    168 
    169     /**
    170      * Saves the bitmap by given directory, filename, and format; if the directory is given null,
    171      * then saves it under the cache directory.
    172      */
    173     public File saveBitmap(
    174             Bitmap bitmap, String directory, String filename, CompressFormat format) {
    175 
    176         if (directory == null) {
    177             directory = context.getCacheDir().getAbsolutePath();
    178         } else {
    179             // Check if the given directory exists or try to create it.
    180             File file = new File(directory);
    181             if (!file.isDirectory() && !file.mkdirs()) {
    182                 return null;
    183             }
    184         }
    185 
    186         File file = null;
    187         OutputStream os = null;
    188 
    189         try {
    190             filename = (format == CompressFormat.PNG) ? filename + ".png" : filename + ".jpg";
    191             file = new File(directory, filename);
    192             os = new FileOutputStream(file);
    193             bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
    194         } catch (FileNotFoundException e) {
    195             e.printStackTrace();
    196         } finally {
    197             closeStream(os);
    198         }
    199         return file;
    200     }
    201 }
    202