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