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