1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera.data;
18
19import android.graphics.Bitmap;
20import android.graphics.BitmapFactory;
21import android.graphics.Matrix;
22import android.graphics.Point;
23import android.media.MediaMetadataRetriever;
24
25import com.android.camera.debug.Log;
26
27import java.io.FileInputStream;
28import java.io.FileNotFoundException;
29import java.io.IOException;
30import java.io.InputStream;
31
32import javax.microedition.khronos.opengles.GL11;
33
34/**
35 * An utility class for data in content provider.
36 */
37public class LocalDataUtil {
38
39    private static final Log.Tag TAG = new Log.Tag("LocalDataUtil");
40
41    /**
42     * @param mimeType The MIME type to check.
43     * @return Whether the MIME is a video type.
44     */
45    public static boolean isMimeTypeVideo(String mimeType) {
46        return mimeType.startsWith("video/");
47    }
48
49    /**
50     * @param mimeType The MIME type to check.
51     * @return Whether the MIME is a image type.
52     */
53    public static boolean isMimeTypeImage(String mimeType) {
54        return mimeType.startsWith("image/");
55    }
56
57    /**
58     * Decodes the dimension of a bitmap.
59     *
60     * @param path The path to the bitmap.
61     * @return The decoded width/height is stored in Point.x/Point.y
62     *         respectively.
63     */
64    public static Point decodeBitmapDimension(String path) {
65        Point size = null;
66        InputStream is = null;
67        try {
68            is = new FileInputStream(path);
69            size = decodeBitmapDimension(is);
70        } catch (FileNotFoundException e) {
71            e.printStackTrace();
72        } finally {
73            if (is != null) {
74                try {
75                    is.close();
76                } catch (IOException e) { }
77            }
78        }
79        return size;
80    }
81
82    /**
83     * Decodes the dimension of a bitmap.
84     *
85     * @param is An input stream with the data of the bitmap.
86     * @return The decoded width/height is stored in Point.x/Point.y
87     *         respectively.
88     */
89    public static Point decodeBitmapDimension(InputStream is) {
90        Point size = null;
91        BitmapFactory.Options justBoundsOpts = new BitmapFactory.Options();
92        justBoundsOpts.inJustDecodeBounds = true;
93        BitmapFactory.decodeStream(is, null, justBoundsOpts);
94        if (justBoundsOpts.outWidth > 0 && justBoundsOpts.outHeight > 0) {
95            size = new Point(justBoundsOpts.outWidth, justBoundsOpts.outHeight);
96        } else {
97            Log.e(TAG, "Bitmap dimension decoding failed");
98        }
99        return size;
100    }
101
102    /**
103     * Load the thumbnail of an image from an {@link java.io.InputStream}.
104     *
105     * @param stream The input stream of the image.
106     * @param imageWidth Image width.
107     * @param imageHeight Image height.
108     * @param widthBound The bound of the width of the decoded image.
109     * @param heightBound The bound of the height of the decoded image.
110     * @param orientation The orientation of the image. The image will be rotated
111     *                    clockwise in degrees.
112     * @param maximumPixels The bound for the number of pixels of the decoded image.
113     * @return {@code null} if the decoding failed.
114     */
115    public static Bitmap loadImageThumbnailFromStream(InputStream stream, int imageWidth,
116            int imageHeight, int widthBound, int heightBound, int orientation,
117            int maximumPixels) {
118
119        /** 32K buffer. */
120        byte[] decodeBuffer = new byte[32 * 1024];
121
122        if (orientation % 180 != 0) {
123            int dummy = imageHeight;
124            imageHeight = imageWidth;
125            imageWidth = dummy;
126        }
127
128        // Generate Bitmap of maximum size that fits into widthBound x heightBound.
129        // Algorithm: start with full size and step down in powers of 2.
130        int targetWidth = imageWidth;
131        int targetHeight = imageHeight;
132        int sampleSize = 1;
133        while (targetHeight > heightBound || targetWidth > widthBound ||
134                targetHeight > GL11.GL_MAX_TEXTURE_SIZE || targetWidth > GL11.GL_MAX_TEXTURE_SIZE ||
135                targetHeight * targetWidth > maximumPixels) {
136            sampleSize <<= 1;
137            targetWidth = imageWidth / sampleSize;
138            targetHeight = imageWidth / sampleSize;
139        }
140
141        // For large (> MAXIMUM_TEXTURE_SIZE) high aspect ratio (panorama)
142        // Bitmap requests:
143        //   Step 1: ask for double size.
144        //   Step 2: scale maximum edge down to MAXIMUM_TEXTURE_SIZE.
145        //
146        // Here's the step 1: double size.
147        if ((heightBound > GL11.GL_MAX_TEXTURE_SIZE || widthBound > GL11.GL_MAX_TEXTURE_SIZE) &&
148                targetWidth * targetHeight < maximumPixels / 4 && sampleSize > 1) {
149            sampleSize >>= 2;
150        }
151
152        BitmapFactory.Options opts = new BitmapFactory.Options();
153        opts.inSampleSize = sampleSize;
154        opts.inTempStorage = decodeBuffer;
155        Bitmap b = BitmapFactory.decodeStream(stream, null, opts);
156
157        if (b == null) {
158            return null;
159        }
160
161        // Step 2: scale maximum edge down to maximum texture size.
162        // If Bitmap maximum edge > MAXIMUM_TEXTURE_SIZE, which can happen for panoramas,
163        // scale to fit in MAXIMUM_TEXTURE_SIZE.
164        if (b.getWidth() > GL11.GL_MAX_TEXTURE_SIZE || b.getHeight() >
165                GL11.GL_MAX_TEXTURE_SIZE) {
166            int maxEdge = Math.max(b.getWidth(), b.getHeight());
167            b = Bitmap.createScaledBitmap(b, b.getWidth() * GL11.GL_MAX_TEXTURE_SIZE / maxEdge,
168                    b.getHeight() * GL11.GL_MAX_TEXTURE_SIZE / maxEdge, false);
169        }
170
171        // Not called often because most modes save image data non-rotated.
172        if (orientation != 0 && b != null) {
173            Matrix m = new Matrix();
174            m.setRotate(orientation);
175            b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
176        }
177
178        return b;
179    }
180
181    /**
182     * Loads the thumbnail of a video.
183     *
184     * @param path The path to the video file.
185     * @return {@code null} if the loading failed.
186     */
187    public static Bitmap loadVideoThumbnail(String path) {
188        Bitmap bitmap = null;
189        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
190        try {
191            retriever.setDataSource(path);
192            byte[] data = retriever.getEmbeddedPicture();
193            if (data != null) {
194                bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
195            }
196            if (bitmap == null) {
197                bitmap = retriever.getFrameAtTime();
198            }
199        } catch (IllegalArgumentException e) {
200            Log.e(TAG, "MediaMetadataRetriever.setDataSource() fail:" + e.getMessage());
201        }
202        retriever.release();
203        return bitmap;
204    }
205}
206