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.Canvas;
     25 import android.graphics.Matrix;
     26 import android.graphics.Paint;
     27 import android.graphics.Rect;
     28 import android.graphics.RectF;
     29 import android.net.Uri;
     30 import android.provider.MediaStore.Images.ImageColumns;
     31 import android.util.Log;
     32 
     33 import java.io.Closeable;
     34 import java.io.File;
     35 import java.io.FileNotFoundException;
     36 import java.io.FileOutputStream;
     37 import java.io.IOException;
     38 import java.io.InputStream;
     39 import java.io.OutputStream;
     40 
     41 /**
     42  * Utils for bitmap operations.
     43  */
     44 public class BitmapUtils {
     45 
     46     private static final String TAG = "BitmapUtils";
     47     private static final int DEFAULT_COMPRESS_QUALITY = 90;
     48     private static final int INDEX_ORIENTATION = 0;
     49 
     50     private static final String[] IMAGE_PROJECTION = new String[] {
     51         ImageColumns.ORIENTATION
     52     };
     53 
     54     private final Context context;
     55 
     56     public BitmapUtils(Context context) {
     57         this.context = context;
     58     }
     59 
     60     /**
     61      * Creates a mutable bitmap from subset of source bitmap, transformed by the optional matrix.
     62      */
     63     private static Bitmap createBitmap(
     64             Bitmap source, int x, int y, int width, int height, Matrix m) {
     65         // Re-implement Bitmap createBitmap() to always return a mutable bitmap.
     66         Canvas canvas = new Canvas();
     67 
     68         Bitmap bitmap;
     69         Paint paint;
     70         if ((m == null) || m.isIdentity()) {
     71             bitmap = Bitmap.createBitmap(width, height, source.getConfig());
     72             paint = null;
     73         } else {
     74             RectF rect = new RectF(0, 0, width, height);
     75             m.mapRect(rect);
     76             bitmap = Bitmap.createBitmap(
     77                     Math.round(rect.width()), Math.round(rect.height()), source.getConfig());
     78 
     79             canvas.translate(-rect.left, -rect.top);
     80             canvas.concat(m);
     81 
     82             paint = new Paint(Paint.FILTER_BITMAP_FLAG);
     83             if (!m.rectStaysRect()) {
     84                 paint.setAntiAlias(true);
     85             }
     86         }
     87         bitmap.setDensity(source.getDensity());
     88         canvas.setBitmap(bitmap);
     89 
     90         Rect srcBounds = new Rect(x, y, x + width, y + height);
     91         RectF dstBounds = new RectF(0, 0, width, height);
     92         canvas.drawBitmap(source, srcBounds, dstBounds, paint);
     93         return bitmap;
     94     }
     95 
     96     private void closeStream(Closeable stream) {
     97         if (stream != null) {
     98             try {
     99                 stream.close();
    100             } catch (IOException e) {
    101                 e.printStackTrace();
    102             }
    103         }
    104     }
    105 
    106     private Rect getBitmapBounds(Uri uri) {
    107         Rect bounds = new Rect();
    108         InputStream is = null;
    109 
    110         try {
    111             is = context.getContentResolver().openInputStream(uri);
    112             BitmapFactory.Options options = new BitmapFactory.Options();
    113             options.inJustDecodeBounds = true;
    114             BitmapFactory.decodeStream(is, null, options);
    115 
    116             bounds.right = options.outWidth;
    117             bounds.bottom = options.outHeight;
    118         } catch (FileNotFoundException e) {
    119             e.printStackTrace();
    120         } finally {
    121             closeStream(is);
    122         }
    123 
    124         return bounds;
    125     }
    126 
    127     private int getOrientation(Uri uri) {
    128         int orientation = 0;
    129         Cursor cursor = null;
    130         try {
    131             cursor = context.getContentResolver().query(uri, IMAGE_PROJECTION, null, null, null);
    132             if ((cursor != null) && cursor.moveToNext()) {
    133                 orientation = cursor.getInt(INDEX_ORIENTATION);
    134             }
    135         } catch (Exception e) {
    136             // Ignore error for no orientation column; just use the default orientation value 0.
    137         } finally {
    138             if (cursor != null) {
    139                 cursor.close();
    140             }
    141         }
    142         return orientation;
    143     }
    144 
    145     /**
    146      * Decodes bitmap (maybe immutable) that keeps aspect-ratio and spans most within the bounds.
    147      */
    148     private Bitmap decodeBitmap(Uri uri, int width, int height) {
    149         InputStream is = null;
    150         Bitmap bitmap = null;
    151 
    152         try {
    153             // TODO: Take max pixels allowed into account for calculation to avoid possible OOM.
    154             Rect bounds = getBitmapBounds(uri);
    155             int sampleSize = Math.max(bounds.width() / width, bounds.height() / height);
    156             sampleSize = Math.min(sampleSize,
    157                     Math.max(bounds.width() / height, bounds.height() / width));
    158 
    159             BitmapFactory.Options options = new BitmapFactory.Options();
    160             options.inSampleSize = Math.max(sampleSize, 1);
    161             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    162 
    163             is = context.getContentResolver().openInputStream(uri);
    164             bitmap = BitmapFactory.decodeStream(is, null, options);
    165         } catch (FileNotFoundException e) {
    166             Log.e(TAG, "FileNotFoundException: " + uri);
    167         } finally {
    168             closeStream(is);
    169         }
    170 
    171         // Ensure bitmap in 8888 format, good for editing as well as GL compatible.
    172         if ((bitmap != null) && (bitmap.getConfig() != Bitmap.Config.ARGB_8888)) {
    173             Bitmap copy = bitmap.copy(Bitmap.Config.ARGB_8888, true);
    174             bitmap.recycle();
    175             bitmap = copy;
    176         }
    177 
    178         if (bitmap != null) {
    179             // Scale down the sampled bitmap if it's still larger than the desired dimension.
    180             float scale = Math.min((float) width / bitmap.getWidth(),
    181                     (float) height / bitmap.getHeight());
    182             scale = Math.max(scale, Math.min((float) height / bitmap.getWidth(),
    183                     (float) width / bitmap.getHeight()));
    184             if (scale < 1) {
    185                 Matrix m = new Matrix();
    186                 m.setScale(scale, scale);
    187                 Bitmap transformed = createBitmap(
    188                         bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
    189                 bitmap.recycle();
    190                 return transformed;
    191             }
    192         }
    193         return bitmap;
    194     }
    195 
    196     /**
    197      * Gets decoded bitmap that keeps orientation as well.
    198      */
    199     public Bitmap getBitmap(Uri uri, int width, int height) {
    200         Bitmap bitmap = decodeBitmap(uri, width, height);
    201 
    202         // Rotate the decoded bitmap according to its orientation if it's necessary.
    203         if (bitmap != null) {
    204             int orientation = getOrientation(uri);
    205             if (orientation != 0) {
    206                 Matrix m = new Matrix();
    207                 m.setRotate(orientation);
    208                 Bitmap transformed = createBitmap(
    209                         bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m);
    210                 bitmap.recycle();
    211                 return transformed;
    212             }
    213         }
    214         return bitmap;
    215     }
    216 
    217     /**
    218      * Saves the bitmap by given directory, filename, and format; if the directory is given null,
    219      * then saves it under the cache directory.
    220      */
    221     public File saveBitmap(
    222             Bitmap bitmap, String directory, String filename, CompressFormat format) {
    223 
    224         if (directory == null) {
    225             directory = context.getCacheDir().getAbsolutePath();
    226         } else {
    227             // Check if the given directory exists or try to create it.
    228             File file = new File(directory);
    229             if (!file.isDirectory() && !file.mkdirs()) {
    230                 return null;
    231             }
    232         }
    233 
    234         File file = null;
    235         OutputStream os = null;
    236 
    237         try {
    238             filename = (format == CompressFormat.PNG) ? filename + ".png" : filename + ".jpg";
    239             file = new File(directory, filename);
    240             os = new FileOutputStream(file);
    241             bitmap.compress(format, DEFAULT_COMPRESS_QUALITY, os);
    242         } catch (FileNotFoundException e) {
    243             e.printStackTrace();
    244         } finally {
    245             closeStream(os);
    246         }
    247         return file;
    248     }
    249 }
    250