ImageUtils.java revision 7aabd796bbcb149db9ac183c1d0aec09fccd42a4
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.ByteArrayInputStream; 35import java.io.ByteArrayOutputStream; 36import java.io.FileNotFoundException; 37import java.io.IOException; 38import java.io.InputStream; 39import java.net.MalformedURLException; 40import java.net.URL; 41 42 43/** 44 * Image utilities 45 */ 46public class ImageUtils { 47 // Logging 48 private static final String TAG = "ImageUtils"; 49 50 /** Minimum class memory class to use full-res photos */ 51 private final static long MIN_NORMAL_CLASS = 32; 52 /** Minimum class memory class to use small photos */ 53 private final static long MIN_SMALL_CLASS = 24; 54 55 public static enum ImageSize { 56 EXTRA_SMALL, 57 SMALL, 58 NORMAL, 59 } 60 61 public static final ImageSize sUseImageSize; 62 static { 63 // On HC and beyond, assume devices are more capable 64 if (Build.VERSION.SDK_INT >= 11) { 65 sUseImageSize = ImageSize.NORMAL; 66 } else { 67 if (PhotoViewActivity.sMemoryClass >= MIN_NORMAL_CLASS) { 68 // We have plenty of memory; use full sized photos 69 sUseImageSize = ImageSize.NORMAL; 70 } else if (PhotoViewActivity.sMemoryClass >= MIN_SMALL_CLASS) { 71 // We have slight less memory; use smaller sized photos 72 sUseImageSize = ImageSize.SMALL; 73 } else { 74 // We have little memory; use very small sized photos 75 sUseImageSize = ImageSize.EXTRA_SMALL; 76 } 77 } 78 } 79 80 /** 81 * @return true if the MimeType type is image 82 */ 83 public static boolean isImageMimeType(String mimeType) { 84 return mimeType != null && mimeType.startsWith("image/"); 85 } 86 87 /** 88 * Create a bitmap from a local URI 89 * 90 * @param resolver The ContentResolver 91 * @param uri The local URI 92 * @param maxSize The maximum size (either width or height) 93 * 94 * @return The new bitmap or null 95 */ 96 public static Bitmap createLocalBitmap(ContentResolver resolver, Uri uri, int maxSize) { 97 // TODO: make this method not download the image for both getImageBounds and decodeStream 98 InputStream inputStream = null; 99 try { 100 final BitmapFactory.Options opts = new BitmapFactory.Options(); 101 final Point bounds = getImageBounds(resolver, uri); 102 inputStream = openInputStream(resolver, uri); 103 if (bounds == null || inputStream == null) { 104 return null; 105 } 106 opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize); 107 108 final Bitmap decodedBitmap = decodeStream(inputStream, null, opts); 109 110 // Correct thumbnail orientation as necessary 111 // TODO: Fix rotation if it's actually a problem 112 //return rotateBitmap(resolver, uri, decodedBitmap); 113 return decodedBitmap; 114 115 } catch (FileNotFoundException exception) { 116 // Do nothing - the photo will appear to be missing 117 } catch (IOException exception) { 118 // Do nothing - the photo will appear to be missing 119 } catch (IllegalArgumentException exception) { 120 // Do nothing - the photo will appear to be missing 121 } finally { 122 try { 123 if (inputStream != null) { 124 inputStream.close(); 125 } 126 } catch (IOException ignore) { 127 } 128 } 129 return null; 130 } 131 132 /** 133 * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect, 134 * BitmapFactory.Options)} that returns {@code null} on {@link 135 * OutOfMemoryError}. 136 * 137 * @param is The input stream that holds the raw data to be decoded into a 138 * bitmap. 139 * @param outPadding If not null, return the padding rect for the bitmap if 140 * it exists, otherwise set padding to [-1,-1,-1,-1]. If 141 * no bitmap is returned (null) then padding is 142 * unchanged. 143 * @param opts null-ok; Options that control downsampling and whether the 144 * image should be completely decoded, or just is size returned. 145 * @return The decoded bitmap, or null if the image data could not be 146 * decoded, or, if opts is non-null, if opts requested only the 147 * size be returned (in opts.outWidth and opts.outHeight) 148 */ 149 public static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) { 150 ByteArrayOutputStream out = null; 151 InputStream byteStream = null; 152 try { 153 out = new ByteArrayOutputStream(); 154 final byte[] buffer = new byte[4096]; 155 int n = is.read(buffer); 156 while (n >= 0) { 157 out.write(buffer, 0, n); 158 n = is.read(buffer); 159 } 160 161 final byte[] bitmapBytes = out.toByteArray(); 162 163 // Determine the orientation for this image 164 final int orientation = Exif.getOrientation(bitmapBytes); 165 166 // Create an InputStream from this byte array 167 byteStream = new ByteArrayInputStream(bitmapBytes); 168 169 final Bitmap originalBitmap = BitmapFactory.decodeStream(byteStream, outPadding, opts); 170 171 if (originalBitmap != null && orientation != 0) { 172 final Matrix matrix = new Matrix(); 173 matrix.postRotate(orientation); 174 return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 175 originalBitmap.getHeight(), matrix, true); 176 } 177 return originalBitmap; 178 } catch (OutOfMemoryError oome) { 179 Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome); 180 return null; 181 } catch (IOException ioe) { 182 Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe); 183 return null; 184 } finally { 185 if (out != null) { 186 try { 187 out.close(); 188 } catch (IOException e) { 189 // Do nothing 190 } 191 } 192 if (byteStream != null) { 193 try { 194 byteStream.close(); 195 } catch (IOException e) { 196 // Do nothing 197 } 198 } 199 } 200 } 201 202 /** 203 * Gets the image bounds 204 * 205 * @param resolver The ContentResolver 206 * @param uri The uri 207 * 208 * @return The image bounds 209 */ 210 private static Point getImageBounds(ContentResolver resolver, Uri uri) 211 throws IOException { 212 final BitmapFactory.Options opts = new BitmapFactory.Options(); 213 InputStream inputStream = null; 214 String scheme = uri.getScheme(); 215 try { 216 opts.inJustDecodeBounds = true; 217 inputStream = openInputStream(resolver, uri); 218 if (inputStream == null) { 219 return null; 220 } 221 decodeStream(inputStream, null, opts); 222 223 return new Point(opts.outWidth, opts.outHeight); 224 } finally { 225 try { 226 if (inputStream != null) { 227 inputStream.close(); 228 } 229 } catch (IOException ignore) { 230 } 231 } 232 } 233 234 private static InputStream openInputStream(ContentResolver resolver, Uri uri) throws 235 FileNotFoundException { 236 String scheme = uri.getScheme(); 237 if("http".equals(scheme) || "https".equals(scheme)) { 238 try { 239 return new URL(uri.toString()).openStream(); 240 } catch (MalformedURLException e) { 241 // Fall-back to the previous behaviour, just in case 242 Log.w(TAG, "Could not convert the uri to url: " + uri.toString()); 243 return resolver.openInputStream(uri); 244 } catch (IOException e) { 245 Log.w(TAG, "Could not open input stream for uri: " + uri.toString()); 246 return null; 247 } 248 } 249 return resolver.openInputStream(uri); 250 } 251} 252