• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.camera.crop;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.database.Cursor;
23 import android.database.sqlite.SQLiteException;
24 import android.graphics.Bitmap;
25 import android.graphics.BitmapFactory;
26 import android.graphics.Matrix;
27 import android.graphics.Rect;
28 import android.net.Uri;
29 import android.provider.MediaStore;
30 import android.util.Log;
31 import android.webkit.MimeTypeMap;
32 
33 import com.android.camera.exif.ExifInterface;
34 import com.android.camera.exif.ExifTag;
35 
36 import java.io.FileNotFoundException;
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.util.List;
40 
41 public final class ImageLoader {
42 
43     private static final String LOGTAG = "ImageLoader";
44 
45     public static final String JPEG_MIME_TYPE = "image/jpeg";
46     public static final int DEFAULT_COMPRESS_QUALITY = 95;
47 
48     public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
49     public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
50     public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
51     public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
52     public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT;
53     public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT;
54     public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP;
55     public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM;
56 
57     private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
58     private static final float OVERDRAW_ZOOM = 1.2f;
ImageLoader()59     private ImageLoader() {}
60 
61     /**
62      * Returns the Mime type for a Url.  Safe to use with Urls that do not
63      * come from Gallery's content provider.
64      */
getMimeType(Uri src)65     public static String getMimeType(Uri src) {
66         String postfix = MimeTypeMap.getFileExtensionFromUrl(src.toString());
67         String ret = null;
68         if (postfix != null) {
69             ret = MimeTypeMap.getSingleton().getMimeTypeFromExtension(postfix);
70         }
71         return ret;
72     }
73 
getLocalPathFromUri(Context context, Uri uri)74     public static String getLocalPathFromUri(Context context, Uri uri) {
75         Cursor cursor = context.getContentResolver().query(uri,
76                 new String[]{MediaStore.Images.Media.DATA}, null, null, null);
77         if (cursor == null) {
78             return null;
79         }
80         int index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
81         cursor.moveToFirst();
82         return cursor.getString(index);
83     }
84 
85     /**
86      * Returns the image's orientation flag.  Defaults to ORI_NORMAL if no valid
87      * orientation was found.
88      */
getMetadataOrientation(Context context, Uri uri)89     public static int getMetadataOrientation(Context context, Uri uri) {
90         if (uri == null || context == null) {
91             throw new IllegalArgumentException("bad argument to getOrientation");
92         }
93 
94         // First try to find orientation data in Gallery's ContentProvider.
95         Cursor cursor = null;
96         try {
97             cursor = context.getContentResolver().query(uri,
98                     new String[] { MediaStore.Images.ImageColumns.ORIENTATION },
99                     null, null, null);
100             if (cursor != null && cursor.moveToNext()) {
101                 int ori = cursor.getInt(0);
102                 switch (ori) {
103                     case 90:
104                         return ORI_ROTATE_90;
105                     case 270:
106                         return ORI_ROTATE_270;
107                     case 180:
108                         return ORI_ROTATE_180;
109                     default:
110                         return ORI_NORMAL;
111                 }
112             }
113         } catch (SQLiteException e) {
114             // Do nothing
115         } catch (IllegalArgumentException e) {
116             // Do nothing
117         } catch (IllegalStateException e) {
118             // Do nothing
119         } finally {
120             Utils.closeSilently(cursor);
121         }
122 
123         // Fall back to checking EXIF tags in file.
124         if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
125             String mimeType = getMimeType(uri);
126             if (!JPEG_MIME_TYPE.equals(mimeType)) {
127                 return ORI_NORMAL;
128             }
129             String path = uri.getPath();
130             ExifInterface exif = new ExifInterface();
131             try {
132                 exif.readExif(path);
133                 Integer tagval = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION);
134                 if (tagval != null) {
135                     int orientation = tagval;
136                     switch(orientation) {
137                         case ORI_NORMAL:
138                         case ORI_ROTATE_90:
139                         case ORI_ROTATE_180:
140                         case ORI_ROTATE_270:
141                         case ORI_FLIP_HOR:
142                         case ORI_FLIP_VERT:
143                         case ORI_TRANSPOSE:
144                         case ORI_TRANSVERSE:
145                             return orientation;
146                         default:
147                             return ORI_NORMAL;
148                     }
149                 }
150             } catch (IOException e) {
151                 Log.w(LOGTAG, "Failed to read EXIF orientation", e);
152             }
153         }
154         return ORI_NORMAL;
155     }
156 
157     /**
158      * Returns the rotation of image at the given URI as one of 0, 90, 180,
159      * 270.  Defaults to 0.
160      */
getMetadataRotation(Context context, Uri uri)161     public static int getMetadataRotation(Context context, Uri uri) {
162         int orientation = getMetadataOrientation(context, uri);
163         switch(orientation) {
164             case ORI_ROTATE_90:
165                 return 90;
166             case ORI_ROTATE_180:
167                 return 180;
168             case ORI_ROTATE_270:
169                 return 270;
170             default:
171                 return 0;
172         }
173     }
174 
175     /**
176      * Takes an orientation and a bitmap, and returns the bitmap transformed
177      * to that orientation.
178      */
orientBitmap(Bitmap bitmap, int ori)179     public static Bitmap orientBitmap(Bitmap bitmap, int ori) {
180         Matrix matrix = new Matrix();
181         int w = bitmap.getWidth();
182         int h = bitmap.getHeight();
183         if (ori == ORI_ROTATE_90 ||
184                 ori == ORI_ROTATE_270 ||
185                 ori == ORI_TRANSPOSE ||
186                 ori == ORI_TRANSVERSE) {
187             int tmp = w;
188             w = h;
189             h = tmp;
190         }
191         switch (ori) {
192             case ORI_ROTATE_90:
193                 matrix.setRotate(90, w / 2f, h / 2f);
194                 break;
195             case ORI_ROTATE_180:
196                 matrix.setRotate(180, w / 2f, h / 2f);
197                 break;
198             case ORI_ROTATE_270:
199                 matrix.setRotate(270, w / 2f, h / 2f);
200                 break;
201             case ORI_FLIP_HOR:
202                 matrix.preScale(-1, 1);
203                 break;
204             case ORI_FLIP_VERT:
205                 matrix.preScale(1, -1);
206                 break;
207             case ORI_TRANSPOSE:
208                 matrix.setRotate(90, w / 2f, h / 2f);
209                 matrix.preScale(1, -1);
210                 break;
211             case ORI_TRANSVERSE:
212                 matrix.setRotate(270, w / 2f, h / 2f);
213                 matrix.preScale(1, -1);
214                 break;
215             case ORI_NORMAL:
216             default:
217                 return bitmap;
218         }
219         return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
220                 bitmap.getHeight(), matrix, true);
221     }
222 
223     /**
224      * Returns the bounds of the bitmap stored at a given Url.
225      */
loadBitmapBounds(Context context, Uri uri)226     public static Rect loadBitmapBounds(Context context, Uri uri) {
227         BitmapFactory.Options o = new BitmapFactory.Options();
228         loadBitmap(context, uri, o);
229         return new Rect(0, 0, o.outWidth, o.outHeight);
230     }
231 
232     /**
233      * Loads a bitmap that has been downsampled using sampleSize from a given url.
234      */
loadDownsampledBitmap(Context context, Uri uri, int sampleSize)235     public static Bitmap loadDownsampledBitmap(Context context, Uri uri, int sampleSize) {
236         BitmapFactory.Options options = new BitmapFactory.Options();
237         options.inMutable = true;
238         options.inSampleSize = sampleSize;
239         return loadBitmap(context, uri, options);
240     }
241 
242     /**
243      * Returns the bitmap from the given uri loaded using the given options.
244      * Returns null on failure.
245      */
loadBitmap(Context context, Uri uri, BitmapFactory.Options o)246     public static Bitmap loadBitmap(Context context, Uri uri, BitmapFactory.Options o) {
247         if (uri == null || context == null) {
248             throw new IllegalArgumentException("bad argument to loadBitmap");
249         }
250         InputStream is = null;
251         try {
252             is = context.getContentResolver().openInputStream(uri);
253             return BitmapFactory.decodeStream(is, null, o);
254         } catch (FileNotFoundException e) {
255             Log.e(LOGTAG, "FileNotFoundException for " + uri, e);
256         } finally {
257             Utils.closeSilently(is);
258         }
259         return null;
260     }
261 
262     /**
263      * Loads a bitmap at a given URI that is downsampled so that both sides are
264      * smaller than maxSideLength. The Bitmap's original dimensions are stored
265      * in the rect originalBounds.
266      *
267      * @param uri URI of image to open.
268      * @param context context whose ContentResolver to use.
269      * @param maxSideLength max side length of returned bitmap.
270      * @param originalBounds If not null, set to the actual bounds of the stored bitmap.
271      * @param useMin use min or max side of the original image
272      * @return downsampled bitmap or null if this operation failed.
273      */
loadConstrainedBitmap(Uri uri, Context context, int maxSideLength, Rect originalBounds, boolean useMin)274     public static Bitmap loadConstrainedBitmap(Uri uri, Context context, int maxSideLength,
275             Rect originalBounds, boolean useMin) {
276         if (maxSideLength <= 0 || uri == null || context == null) {
277             throw new IllegalArgumentException("bad argument to getScaledBitmap");
278         }
279         // Get width and height of stored bitmap
280         Rect storedBounds = loadBitmapBounds(context, uri);
281         if (originalBounds != null) {
282             originalBounds.set(storedBounds);
283         }
284         int w = storedBounds.width();
285         int h = storedBounds.height();
286 
287         // If bitmap cannot be decoded, return null
288         if (w <= 0 || h <= 0) {
289             return null;
290         }
291 
292         // Find best downsampling size
293         int imageSide = 0;
294         if (useMin) {
295             imageSide = Math.min(w, h);
296         } else {
297             imageSide = Math.max(w, h);
298         }
299         int sampleSize = 1;
300         while (imageSide > maxSideLength) {
301             imageSide >>>= 1;
302             sampleSize <<= 1;
303         }
304 
305         // Make sure sample size is reasonable
306         if (sampleSize <= 0 ||
307                 0 >= (int) (Math.min(w, h) / sampleSize)) {
308             return null;
309         }
310         return loadDownsampledBitmap(context, uri, sampleSize);
311     }
312 
313     /**
314      * Loads a bitmap at a given URI that is downsampled so that both sides are
315      * smaller than maxSideLength. The Bitmap's original dimensions are stored
316      * in the rect originalBounds.  The output is also transformed to the given
317      * orientation.
318      *
319      * @param uri URI of image to open.
320      * @param context context whose ContentResolver to use.
321      * @param maxSideLength max side length of returned bitmap.
322      * @param orientation  the orientation to transform the bitmap to.
323      * @param originalBounds set to the actual bounds of the stored bitmap.
324      * @return downsampled bitmap or null if this operation failed.
325      */
loadOrientedConstrainedBitmap(Uri uri, Context context, int maxSideLength, int orientation, Rect originalBounds)326     public static Bitmap loadOrientedConstrainedBitmap(Uri uri, Context context, int maxSideLength,
327             int orientation, Rect originalBounds) {
328         Bitmap bmap = loadConstrainedBitmap(uri, context, maxSideLength, originalBounds, false);
329         if (bmap != null) {
330             bmap = orientBitmap(bmap, orientation);
331             if (bmap.getConfig()!= Bitmap.Config.ARGB_8888){
332                 bmap = bmap.copy( Bitmap.Config.ARGB_8888,true);
333             }
334         }
335         return bmap;
336     }
337 
338     /**
339      * Loads a bitmap that is downsampled by at least the input sample size. In
340      * low-memory situations, the bitmap may be downsampled further.
341      */
loadBitmapWithBackouts(Context context, Uri sourceUri, int sampleSize)342     public static Bitmap loadBitmapWithBackouts(Context context, Uri sourceUri, int sampleSize) {
343         boolean noBitmap = true;
344         int num_tries = 0;
345         if (sampleSize <= 0) {
346             sampleSize = 1;
347         }
348         Bitmap bmap = null;
349         while (noBitmap) {
350             try {
351                 // Try to decode, downsample if low-memory.
352                 bmap = loadDownsampledBitmap(context, sourceUri, sampleSize);
353                 noBitmap = false;
354             } catch (java.lang.OutOfMemoryError e) {
355                 // Try with more downsampling before failing for good.
356                 if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
357                     throw e;
358                 }
359                 bmap = null;
360                 System.gc();
361                 sampleSize *= 2;
362             }
363         }
364         return bmap;
365     }
366 
367     /**
368      * Loads an oriented bitmap that is downsampled by at least the input sample
369      * size. In low-memory situations, the bitmap may be downsampled further.
370      */
loadOrientedBitmapWithBackouts(Context context, Uri sourceUri, int sampleSize)371     public static Bitmap loadOrientedBitmapWithBackouts(Context context, Uri sourceUri,
372             int sampleSize) {
373         Bitmap bitmap = loadBitmapWithBackouts(context, sourceUri, sampleSize);
374         if (bitmap == null) {
375             return null;
376         }
377         int orientation = getMetadataOrientation(context, sourceUri);
378         bitmap = orientBitmap(bitmap, orientation);
379         return bitmap;
380     }
381 
382     /**
383      * Loads bitmap from a resource that may be downsampled in low-memory situations.
384      */
decodeResourceWithBackouts(Resources res, BitmapFactory.Options options, int id)385     public static Bitmap decodeResourceWithBackouts(Resources res, BitmapFactory.Options options,
386             int id) {
387         boolean noBitmap = true;
388         int num_tries = 0;
389         if (options.inSampleSize < 1) {
390             options.inSampleSize = 1;
391         }
392         // Stopgap fix for low-memory devices.
393         Bitmap bmap = null;
394         while (noBitmap) {
395             try {
396                 // Try to decode, downsample if low-memory.
397                 bmap = BitmapFactory.decodeResource(
398                         res, id, options);
399                 noBitmap = false;
400             } catch (java.lang.OutOfMemoryError e) {
401                 // Retry before failing for good.
402                 if (++num_tries >= BITMAP_LOAD_BACKOUT_ATTEMPTS) {
403                     throw e;
404                 }
405                 bmap = null;
406                 System.gc();
407                 options.inSampleSize *= 2;
408             }
409         }
410         return bmap;
411     }
412 
getExif(Context context, Uri uri)413     public static List<ExifTag> getExif(Context context, Uri uri) {
414         String path = getLocalPathFromUri(context, uri);
415         if (path != null) {
416             Uri localUri = Uri.parse(path);
417             String mimeType = getMimeType(localUri);
418             if (!JPEG_MIME_TYPE.equals(mimeType)) {
419                 return null;
420             }
421             try {
422                 ExifInterface exif = new ExifInterface();
423                 exif.readExif(path);
424                 List<ExifTag> taglist = exif.getAllTags();
425                 return taglist;
426             } catch (IOException e) {
427                 Log.w(LOGTAG, "Failed to read EXIF tags", e);
428             }
429         }
430         return null;
431     }
432 }
433