• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
BitmapUtils(Context context)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      */
createBitmap( Bitmap source, int x, int y, int width, int height, Matrix m)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 
closeStream(Closeable stream)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 
getBitmapBounds(Uri uri)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 
getOrientation(Uri uri)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      */
decodeBitmap(Uri uri, int width, int height)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      */
getBitmap(Uri uri, int width, int height)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      */
saveBitmap( Bitmap bitmap, String directory, String filename, CompressFormat format)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