1/* 2 * Copyright (C) 2011 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.ex.photo.util; 19 20import android.content.ContentResolver; 21import android.graphics.Bitmap; 22import android.graphics.BitmapFactory; 23import android.graphics.Matrix; 24import android.graphics.Point; 25import android.graphics.Rect; 26import android.net.Uri; 27import android.os.Build; 28import android.util.DisplayMetrics; 29import android.util.Log; 30 31import com.android.ex.photo.PhotoViewActivity; 32import com.android.ex.photo.util.Exif; 33 34import java.io.ByteArrayOutputStream; 35import java.io.FileNotFoundException; 36import java.io.IOException; 37import java.io.InputStream; 38 39/** 40 * Image utilities 41 */ 42public class ImageUtils { 43 // Logging 44 private static final String TAG = "ImageUtils"; 45 46 /** Minimum class memory class to use full-res photos */ 47 private final static long MIN_NORMAL_CLASS = 32; 48 /** Minimum class memory class to use small photos */ 49 private final static long MIN_SMALL_CLASS = 24; 50 51 public static enum ImageSize { 52 EXTRA_SMALL, 53 SMALL, 54 NORMAL, 55 } 56 57 public static final ImageSize sUseImageSize; 58 static { 59 // On HC and beyond, assume devices are more capable 60 if (Build.VERSION.SDK_INT >= 11) { 61 sUseImageSize = ImageSize.NORMAL; 62 } else { 63 if (PhotoViewActivity.sMemoryClass >= MIN_NORMAL_CLASS) { 64 // We have plenty of memory; use full sized photos 65 sUseImageSize = ImageSize.NORMAL; 66 } else if (PhotoViewActivity.sMemoryClass >= MIN_SMALL_CLASS) { 67 // We have slight less memory; use smaller sized photos 68 sUseImageSize = ImageSize.SMALL; 69 } else { 70 // We have little memory; use very small sized photos 71 sUseImageSize = ImageSize.EXTRA_SMALL; 72 } 73 } 74 } 75 76 /** 77 * @return true if the MimeType type is image 78 */ 79 public static boolean isImageMimeType(String mimeType) { 80 return mimeType != null && mimeType.startsWith("image/"); 81 } 82 83 /** 84 * Create a bitmap from a local URI 85 * 86 * @param resolver The ContentResolver 87 * @param uri The local URI 88 * @param maxSize The maximum size (either width or height) 89 * 90 * @return The new bitmap or null 91 */ 92 public static Bitmap createLocalBitmap(ContentResolver resolver, Uri uri, int maxSize) { 93 InputStream inputStream = null; 94 try { 95 final BitmapFactory.Options opts = new BitmapFactory.Options(); 96 final Point bounds = getImageBounds(resolver, uri); 97 98 inputStream = resolver.openInputStream(uri); 99 opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize); 100 101 final Bitmap decodedBitmap = decodeStream(inputStream, null, opts); 102 103 // Correct thumbnail orientation as necessary 104 // TODO: Fix rotation if it's actually a problem 105 //return rotateBitmap(resolver, uri, decodedBitmap); 106 return decodedBitmap; 107 108 } catch (FileNotFoundException exception) { 109 // Do nothing - the photo will appear to be missing 110 } catch (IOException exception) { 111 // Do nothing - the photo will appear to be missing 112 } catch (IllegalArgumentException exception) { 113 // Do nothing - the photo will appear to be missing 114 } finally { 115 try { 116 if (inputStream != null) { 117 inputStream.close(); 118 } 119 } catch (IOException ignore) { 120 } 121 } 122 return null; 123 } 124 125 /** 126 * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect, 127 * BitmapFactory.Options)} that returns {@code null} on {@link 128 * OutOfMemoryError}. 129 * 130 * @param is The input stream that holds the raw data to be decoded into a 131 * bitmap. 132 * @param outPadding If not null, return the padding rect for the bitmap if 133 * it exists, otherwise set padding to [-1,-1,-1,-1]. If 134 * no bitmap is returned (null) then padding is 135 * unchanged. 136 * @param opts null-ok; Options that control downsampling and whether the 137 * image should be completely decoded, or just is size returned. 138 * @return The decoded bitmap, or null if the image data could not be 139 * decoded, or, if opts is non-null, if opts requested only the 140 * size be returned (in opts.outWidth and opts.outHeight) 141 */ 142 public static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) { 143 ByteArrayOutputStream out = null; 144 try { 145 out = new ByteArrayOutputStream(); 146 final byte[] buffer = new byte[4096]; 147 int n = is.read(buffer); 148 while (n >= 0) { 149 out.write(buffer, 0, n); 150 n = is.read(buffer); 151 } 152 final byte[] bitmapBytes = out.toByteArray(); 153 154 // Determine the orientation for this image 155 final int orientation = Exif.getOrientation(bitmapBytes); 156 final Bitmap originalBitmap = 157 BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, opts); 158 159 if (originalBitmap != null && orientation != 0) { 160 final Matrix matrix = new Matrix(); 161 matrix.postRotate(orientation); 162 return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 163 originalBitmap.getHeight(), matrix, true); 164 } 165 return originalBitmap; 166 } catch (OutOfMemoryError oome) { 167 Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome); 168 return null; 169 } catch (IOException ioe) { 170 Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe); 171 return null; 172 } finally { 173 if (out != null) { 174 try { 175 out.close(); 176 } catch (IOException e) { 177 // Do nothing 178 } 179 } 180 } 181 } 182 183 /** 184 * Gets the image bounds 185 * 186 * @param resolver The ContentResolver 187 * @param uri The uri 188 * 189 * @return The image bounds 190 */ 191 private static Point getImageBounds(ContentResolver resolver, Uri uri) 192 throws IOException { 193 final BitmapFactory.Options opts = new BitmapFactory.Options(); 194 InputStream inputStream = null; 195 196 try { 197 opts.inJustDecodeBounds = true; 198 inputStream = resolver.openInputStream(uri); 199 decodeStream(inputStream, null, opts); 200 201 return new Point(opts.outWidth, opts.outHeight); 202 } finally { 203 try { 204 if (inputStream != null) { 205 inputStream.close(); 206 } 207 } catch (IOException ignore) { 208 } 209 } 210 } 211} 212