Home | History | Annotate | Download | only in util
      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.util;
     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.webkit.MimeTypeMap;
     31 
     32 import com.android.camera.debug.Log;
     33 import com.android.camera.exif.ExifInterface;
     34 import com.android.camera.exif.ExifTag;
     35 
     36 import java.io.Closeable;
     37 import java.io.FileNotFoundException;
     38 import java.io.IOException;
     39 import java.io.InputStream;
     40 import java.util.List;
     41 
     42 public final class ImageLoader {
     43 
     44     private static final Log.Tag TAG = new Log.Tag("ImageLoader");
     45 
     46     public static final String JPEG_MIME_TYPE = "image/jpeg";
     47     public static final int DEFAULT_COMPRESS_QUALITY = 95;
     48 
     49     public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT;
     50     public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP;
     51     public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT;
     52     public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM;
     53     public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT;
     54     public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT;
     55     public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP;
     56     public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM;
     57 
     58     private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5;
     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      */
     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 
     74     public static String getLocalPathFromUri(ContentResolver resolver, Uri uri) {
     75         Cursor cursor = resolver.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      */
     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             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(TAG, "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      */
    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      */
    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      */
    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      */
    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      */
    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(TAG, "FileNotFoundException for " + uri, e);
    256         } finally {
    257             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      */
    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 >= (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      */
    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      */
    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      */
    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      */
    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 
    413     public static List<ExifTag> getExif(ContentResolver resolver, Uri uri) {
    414         String path = getLocalPathFromUri(resolver, 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(TAG, "Failed to read EXIF tags", e);
    428             }
    429         }
    430         return null;
    431     }
    432 
    433     private static void closeSilently(Closeable c) {
    434         if (c == null) return;
    435         try {
    436             c.close();
    437         } catch (IOException t) {
    438             Log.w(TAG, "close fail ", t);
    439         }
    440     }
    441 
    442 }
    443