15211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook/*
25211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * Copyright (C) 2011 Google Inc.
35211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * Licensed to The Android Open Source Project.
45211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook *
55211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * Licensed under the Apache License, Version 2.0 (the "License");
65211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * you may not use this file except in compliance with the License.
75211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * You may obtain a copy of the License at
85211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook *
95211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook *      http://www.apache.org/licenses/LICENSE-2.0
105211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook *
115211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * Unless required by applicable law or agreed to in writing, software
125211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * distributed under the License is distributed on an "AS IS" BASIS,
135211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
145211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * See the License for the specific language governing permissions and
155211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * limitations under the License.
165211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook */
175211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
185211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookpackage com.android.ex.photo.util;
195211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
205211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.content.ContentResolver;
215211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.graphics.Bitmap;
225211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.graphics.BitmapFactory;
235211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.graphics.Matrix;
245211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.graphics.Point;
255211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.graphics.Rect;
265211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.net.Uri;
275211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.os.Build;
285211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.util.DisplayMetrics;
295211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport android.util.Log;
305211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
315211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport com.android.ex.photo.PhotoViewActivity;
325211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport com.android.ex.photo.util.Exif;
335211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
345211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport java.io.ByteArrayOutputStream;
355211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport java.io.FileNotFoundException;
365211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport java.io.IOException;
375211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookimport java.io.InputStream;
385211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
395211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook/**
405211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook * Image utilities
415211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook */
425211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrookpublic class ImageUtils {
435211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    // Logging
445211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    private static final String TAG = "ImageUtils";
455211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
465211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    /** Minimum class memory class to use full-res photos */
475211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    private final static long MIN_NORMAL_CLASS = 32;
485211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    /** Minimum class memory class to use small photos */
495211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    private final static long MIN_SMALL_CLASS = 24;
505211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
515211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    public static enum ImageSize {
525211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        EXTRA_SMALL,
535211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        SMALL,
545211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        NORMAL,
555211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    }
565211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
575211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    public static final ImageSize sUseImageSize;
585211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    static {
595211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        // On HC and beyond, assume devices are more capable
605211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        if (Build.VERSION.SDK_INT >= 11) {
615211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            sUseImageSize = ImageSize.NORMAL;
625211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } else {
635211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            if (PhotoViewActivity.sMemoryClass >= MIN_NORMAL_CLASS) {
645211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                // We have plenty of memory; use full sized photos
655211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                sUseImageSize = ImageSize.NORMAL;
665211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            } else if (PhotoViewActivity.sMemoryClass >= MIN_SMALL_CLASS) {
675211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                // We have slight less memory; use smaller sized photos
685211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                sUseImageSize = ImageSize.SMALL;
695211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            } else {
705211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                // We have little memory; use very small sized photos
715211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                sUseImageSize = ImageSize.EXTRA_SMALL;
725211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            }
735211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        }
745211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    }
755211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
765211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    /**
775211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @return true if the MimeType type is image
785211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     */
795211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    public static boolean isImageMimeType(String mimeType) {
805211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        return mimeType != null && mimeType.startsWith("image/");
815211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    }
825211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
835211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    /**
845211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * Create a bitmap from a local URI
855211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *
865211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param resolver The ContentResolver
875211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param uri The local URI
885211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param maxSize The maximum size (either width or height)
895211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *
905211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @return The new bitmap or null
915211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     */
925211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    public static Bitmap createLocalBitmap(ContentResolver resolver, Uri uri, int maxSize) {
935211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        InputStream inputStream = null;
945211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        try {
955211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final BitmapFactory.Options opts = new BitmapFactory.Options();
965211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final Point bounds = getImageBounds(resolver, uri);
975211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
985211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            inputStream = resolver.openInputStream(uri);
995211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize);
1005211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1015211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final Bitmap decodedBitmap = decodeStream(inputStream, null, opts);
1025211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1035211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            // Correct thumbnail orientation as necessary
1041cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            // TODO: Fix rotation if it's actually a problem
1051cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            //return rotateBitmap(resolver, uri, decodedBitmap);
1061cc4b2144a45abb495c8b14f6cfc5a10fb5e8ba8Andrew Sapperstein            return decodedBitmap;
1075211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1085211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } catch (FileNotFoundException exception) {
1095211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            // Do nothing - the photo will appear to be missing
1105211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } catch (IOException exception) {
1115211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            // Do nothing - the photo will appear to be missing
1128085e1fcda882074ed387d3e999c448a84d6eb3bAndrew Sapperstein        } catch (IllegalArgumentException exception) {
1138085e1fcda882074ed387d3e999c448a84d6eb3bAndrew Sapperstein            // Do nothing - the photo will appear to be missing
1145211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } finally {
1155211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            try {
1165211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                if (inputStream != null) {
1175211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                    inputStream.close();
1185211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                }
1195211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            } catch (IOException ignore) {
1205211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            }
1215211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        }
1225211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        return null;
1235211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    }
1245211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1255211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    /**
1265211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect,
1275211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * BitmapFactory.Options)} that returns {@code null} on {@link
1285211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * OutOfMemoryError}.
1295211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *
1305211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param is The input stream that holds the raw data to be decoded into a
1315211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *           bitmap.
1325211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param outPadding If not null, return the padding rect for the bitmap if
1335211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
1345211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *                   no bitmap is returned (null) then padding is
1355211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *                   unchanged.
1365211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param opts null-ok; Options that control downsampling and whether the
1375211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *             image should be completely decoded, or just is size returned.
1385211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @return The decoded bitmap, or null if the image data could not be
1395211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *         decoded, or, if opts is non-null, if opts requested only the
1405211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *         size be returned (in opts.outWidth and opts.outHeight)
1415211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     */
1425211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    public static Bitmap decodeStream(InputStream is, Rect outPadding, BitmapFactory.Options opts) {
1435211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        ByteArrayOutputStream out = null;
1445211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        try {
1455211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            out = new ByteArrayOutputStream();
1465211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final byte[] buffer = new byte[4096];
1475211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            int n = is.read(buffer);
1485211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            while (n >= 0) {
1495211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                out.write(buffer, 0, n);
1505211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                n = is.read(buffer);
1515211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            }
1525211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final byte[] bitmapBytes = out.toByteArray();
1535211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1545211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            // Determine the orientation for this image
1555211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final int orientation = Exif.getOrientation(bitmapBytes);
1565211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            final Bitmap originalBitmap =
1575211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                    BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length, opts);
1585211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1595211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            if (originalBitmap != null && orientation != 0) {
1605211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                final Matrix matrix = new Matrix();
1615211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                matrix.postRotate(orientation);
1625211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(),
1635211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                        originalBitmap.getHeight(), matrix, true);
1645211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            }
1655211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            return originalBitmap;
1665211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } catch (OutOfMemoryError oome) {
1675211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome);
1685211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            return null;
1695211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } catch (IOException ioe) {
1705211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe);
1715211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            return null;
1725211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } finally {
1735211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            if (out != null) {
1745211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                try {
1755211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                    out.close();
1765211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                } catch (IOException e) {
1775211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                    // Do nothing
1785211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                }
1795211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            }
1805211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        }
1815211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    }
1825211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1835211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    /**
1845211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * Gets the image bounds
1855211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *
1865211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param resolver The ContentResolver
1875211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @param uri The uri
1885211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     *
1895211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     * @return The image bounds
1905211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook     */
1915211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    private static Point getImageBounds(ContentResolver resolver, Uri uri)
1925211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            throws IOException {
1935211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        final BitmapFactory.Options opts = new BitmapFactory.Options();
1945211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        InputStream inputStream = null;
1955211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
1965211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        try {
1975211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            opts.inJustDecodeBounds = true;
1985211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            inputStream = resolver.openInputStream(uri);
1995211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            decodeStream(inputStream, null, opts);
2005211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook
2015211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            return new Point(opts.outWidth, opts.outHeight);
2025211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        } finally {
2035211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            try {
2045211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                if (inputStream != null) {
2055211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                    inputStream.close();
2065211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook                }
2075211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            } catch (IOException ignore) {
2085211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook            }
2095211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook        }
2105211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook    }
2115211e4682a26be787e60b1c56f56b113a2fac26cPaul Westbrook}
212