ImageUtils.java revision 237ded1a59b1fc148a3ffcf78d6f9d34af5c170c
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 opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize); 104 105 final Bitmap decodedBitmap = decodeStream(inputStream, null, opts); 106 107 // Correct thumbnail orientation as necessary 108 // TODO: Fix rotation if it's actually a problem 109 //return rotateBitmap(resolver, uri, decodedBitmap); 110 return decodedBitmap; 111 112 } catch (FileNotFoundException exception) { 113 // Do nothing - the photo will appear to be missing 114 } catch (IOException exception) { 115 // Do nothing - the photo will appear to be missing 116 } catch (IllegalArgumentException exception) { 117 // Do nothing - the photo will appear to be missing 118 } finally { 119 try { 120 if (inputStream != null) { 121 inputStream.close(); 122 } 123 } catch (IOException ignore) { 124 } 125 } 126 return null; 127 } 128 129 /** 130 * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect, 131 * BitmapFactory.Options)} that returns {@code null} on {@link 132 * OutOfMemoryError}. 133 * 134 * @param is The input stream that holds the raw data to be decoded into a 135 * bitmap. 136 * @param outPadding If not null, return the padding rect for the bitmap if 137 * it exists, otherwise set padding to [-1,-1,-1,-1]. If 138 * no bitmap is returned (null) then padding is 139 * unchanged. 140 * @param opts null-ok; Options that control downsampling and whether the 141 * image should be completely decoded, or just is size returned. 142 * @return The decoded bitmap, or null if the image data could not be 143 * decoded, or, if opts is non-null, if opts requested only the 144 * size be returned (in opts.outWidth and opts.outHeight) 145 */ 146 public static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) { 147 ByteArrayOutputStream out = null; 148 InputStream byteStream = null; 149 try { 150 out = new ByteArrayOutputStream(); 151 final byte[] buffer = new byte[4096]; 152 int n = is.read(buffer); 153 while (n >= 0) { 154 out.write(buffer, 0, n); 155 n = is.read(buffer); 156 } 157 158 final byte[] bitmapBytes = out.toByteArray(); 159 160 // Determine the orientation for this image 161 final int orientation = Exif.getOrientation(bitmapBytes); 162 163 // Create an InputStream from this byte array 164 byteStream = new ByteArrayInputStream(bitmapBytes); 165 166 final Bitmap originalBitmap = BitmapFactory.decodeStream(byteStream, outPadding, opts); 167 168 if (originalBitmap != null && orientation != 0) { 169 final Matrix matrix = new Matrix(); 170 matrix.postRotate(orientation); 171 return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(), 172 originalBitmap.getHeight(), matrix, true); 173 } 174 return originalBitmap; 175 } catch (OutOfMemoryError oome) { 176 Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome); 177 return null; 178 } catch (IOException ioe) { 179 Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe); 180 return null; 181 } finally { 182 if (out != null) { 183 try { 184 out.close(); 185 } catch (IOException e) { 186 // Do nothing 187 } 188 } 189 if (byteStream != null) { 190 try { 191 byteStream.close(); 192 } catch (IOException e) { 193 // Do nothing 194 } 195 } 196 } 197 } 198 199 /** 200 * Gets the image bounds 201 * 202 * @param resolver The ContentResolver 203 * @param uri The uri 204 * 205 * @return The image bounds 206 */ 207 private static Point getImageBounds(ContentResolver resolver, Uri uri) 208 throws IOException { 209 final BitmapFactory.Options opts = new BitmapFactory.Options(); 210 InputStream inputStream = null; 211 String scheme = uri.getScheme(); 212 try { 213 opts.inJustDecodeBounds = true; 214 inputStream = openInputStream(resolver, uri); 215 decodeStream(inputStream, null, opts); 216 217 return new Point(opts.outWidth, opts.outHeight); 218 } finally { 219 try { 220 if (inputStream != null) { 221 inputStream.close(); 222 } 223 } catch (IOException ignore) { 224 } 225 } 226 } 227 228 private static InputStream openInputStream(ContentResolver resolver, Uri uri) throws 229 FileNotFoundException { 230 String scheme = uri.getScheme(); 231 if("http".equals(scheme) || "https".equals(scheme)) { 232 try { 233 return new URL(uri.toString()).openStream(); 234 } catch (MalformedURLException e) { 235 // Fall-back to the previous behaviour, just in case 236 Log.w(TAG, "Could not convert the uri to url: " + uri.toString()); 237 return resolver.openInputStream(uri); 238 } catch (IOException e) { 239 Log.w(TAG, "Could not open input stream for uri: " + uri.toString()); 240 return null; 241 } 242 } 243 return resolver.openInputStream(uri); 244 } 245} 246