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.gallery3d.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     private static Bitmap createBitmap(Bitmap source, Matrix m) {
     58         return Bitmap.createBitmap(source, 0, 0, source.getWidth(), source.getHeight(), m, true);
     59     }
     60 
     61     private void closeStream(Closeable stream) {
     62         if (stream != null) {
     63             try {
     64                 stream.close();
     65             } catch (IOException e) {
     66                 e.printStackTrace();
     67             }
     68         }
     69     }
     70 
     71     private Rect getBitmapBounds(Uri uri) {
     72         Rect bounds = new Rect();
     73         InputStream is = null;
     74 
     75         try {
     76             is = context.getContentResolver().openInputStream(uri);
     77             BitmapFactory.Options options = new BitmapFactory.Options();
     78             options.inJustDecodeBounds = true;
     79             BitmapFactory.decodeStream(is, null, options);
     80 
     81             bounds.right = options.outWidth;
     82             bounds.bottom = options.outHeight;
     83         } catch (FileNotFoundException e) {
     84             e.printStackTrace();
     85         } finally {
     86             closeStream(is);
     87         }
     88 
     89         return bounds;
     90     }
     91 
     92     private int getOrientation(Uri uri) {
     93         int orientation = 0;
     94         Cursor cursor = null;
     95         try {
     96             cursor = context.getContentResolver().query(uri, IMAGE_PROJECTION, null, null, null);
     97             if ((cursor != null) && cursor.moveToNext()) {
     98                 orientation = cursor.getInt(INDEX_ORIENTATION);
     99             }
    100         } catch (Exception e) {
    101             // Ignore error for no orientation column; just use the default orientation value 0.
    102         } finally {
    103             if (cursor != null) {
    104                 cursor.close();
    105             }
    106         }
    107         return orientation;
    108     }
    109 
    110     /**
    111      * Decodes bitmap that keeps aspect-ratio and spans most within the bounds.
    112      */
    113     private Bitmap decodeBitmap(Uri uri, int width, int height) {
    114         InputStream is = null;
    115         Bitmap bitmap = null;
    116 
    117         try {
    118             // TODO: Take max pixels allowed into account for calculation to avoid possible OOM.
    119             Rect bounds = getBitmapBounds(uri);
    120             int sampleSize = Math.max(bounds.width() / width, bounds.height() / height);
    121             sampleSize = Math.min(sampleSize,
    122                     Math.max(bounds.width() / height, bounds.height() / width));
    123 
    124             BitmapFactory.Options options = new BitmapFactory.Options();
    125             options.inSampleSize = Math.max(sampleSize, 1);
    126             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    127 
    128             is = context.getContentResolver().openInputStream(uri);
    129             bitmap = BitmapFactory.decodeStream(is, null, options);
    130         } catch (FileNotFoundException e) {
    131             Log.e(TAG, "FileNotFoundException: " + uri);
    132         } finally {
    133             closeStream(is);
    134         }
    135 
    136         // Ensure bitmap in 8888 format, good for editing as well as GL compatible.
    137         if ((bitmap != null) && (bitmap.getConfig() != Bitmap.Config.ARGB_8888)) {
    138             Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
    139             bitmap.recycle();
    140             bitmap = copy;
    141         }
    142 
    143         if (bitmap != null) {
    144             // Scale down the sampled bitmap if it's still larger than the desired dimension.
    145             float scale = Math.min((float) width / bitmap.getWidth(),
    146                     (float) height / bitmap.getHeight());
    147             scale = Math.max(scale, Math.min((float) height / bitmap.getWidth(),
    148                     (float) width / bitmap.getHeight()));
    149             if (scale < 1) {
    150                 Matrix m = new Matrix();
    151                 m.setScale(scale, scale);
    152                 Bitmap transformed = createBitmap(bitmap, m);
    153                 bitmap.recycle();
    154                 return transformed;
    155             }
    156         }
    157         return bitmap;
    158     }
    159 
    160     /**
    161      * Gets decoded bitmap (maybe immutable) that keeps orientation as well.
    162      */
    163     public Bitmap getBitmap(Uri uri, int width, int height) {
    164         Bitmap bitmap = decodeBitmap(uri, width, height);
    165 
    166         // Rotate the decoded bitmap according to its orientation if it's necessary.
    167         if (bitmap != null) {
    168             int orientation = getOrientation(uri);
    169             if (orientation != 0) {
    170                 Matrix m = new Matrix();
    171                 m.setRotate(orientation);
    172                 Bitmap transformed = createBitmap(bitmap, m);
    173                 bitmap.recycle();
    174                 return transformed;
    175             }
    176         }
    177         return bitmap;
    178     }
    179 
    180     /**
    181      * Saves the bitmap by given directory, filename, and format; if the directory is given null,
    182      * then saves it under the cache directory.
    183      */
    184     public File saveBitmap(Bitmap bitmap, File directory, String filename, CompressFormat format) {
    185 
    186         if (directory == null) {
    187             directory = context.getCacheDir();
    188         } else {
    189             // Check if the given directory exists or try to create it.
    190             if (!directory.isDirectory() && !directory.mkdirs()) {
    191                 return null;
    192             }
    193         }
    194 
    195         File file = null;
    196         OutputStream os = null;
    197 
    198         try {
    199             filename = (format == CompressFormat.PNG) ? filename + ".png" : filename + ".jpg";
    200             file = new File(directory, filename);
    201             os = new FileOutputStream(file);
    202             bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
    203         } catch (FileNotFoundException e) {
    204             e.printStackTrace();
    205         } finally {
    206             closeStream(os);
    207         }
    208         return file;
    209     }
    210 }
    211