Util.java revision 58e94eddd371a9bb7c04936dbeb99615fd1a0c2c
1/*
2 * Copyright (C) 2009 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;
18
19import com.android.camera.gallery.IImage;
20
21import android.content.ContentResolver;
22import android.graphics.Bitmap;
23import android.graphics.BitmapFactory;
24import android.graphics.Canvas;
25import android.graphics.Matrix;
26import android.graphics.Rect;
27import android.media.MediaMetadataRetriever;
28import android.net.Uri;
29import android.os.ParcelFileDescriptor;
30import android.util.Log;
31
32import java.io.ByteArrayOutputStream;
33import java.io.Closeable;
34import java.io.FileDescriptor;
35import java.io.IOException;
36
37/**
38 * Collection of utility functions used in this package.
39 */
40public class Util {
41    private static final boolean VERBOSE = false;
42    private static final String TAG = "db.Util";
43
44    private Util() {
45    }
46
47    // Rotates the bitmap by the specified degree.
48    // If a new bitmap is created, the original bitmap is recycled.
49    public static Bitmap rotate(Bitmap b, int degrees) {
50        if (degrees != 0 && b != null) {
51            Matrix m = new Matrix();
52            m.setRotate(degrees,
53                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
54            try {
55                Bitmap b2 = Bitmap.createBitmap(
56                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
57                if (b != b2) {
58                    b.recycle();
59                    b = b2;
60                }
61            } catch (OutOfMemoryError ex) {
62                // We have no memory to rotate. Return the original bitmap.
63            }
64        }
65        return b;
66    }
67
68    /*
69     * Compute the sample size as a function of the image size and the target.
70     * Scale the image down so that both the width and height are just above the
71     * target. If this means that one of the dimension goes from above the
72     * target to below the target (e.g. given a width of 480 and an image width
73     * of 600 but sample size of 2 -- i.e. new width 300 -- bump the sample size
74     * down by 1.
75     */
76    public static int computeSampleSize(
77            BitmapFactory.Options options, int target) {
78        int w = options.outWidth;
79        int h = options.outHeight;
80
81        int candidateW = w / target;
82        int candidateH = h / target;
83        int candidate = Math.max(candidateW, candidateH);
84
85        if (candidate == 0) return 1;
86
87        if (candidate > 1) {
88            if ((w > target) && (w / candidate) < target) candidate -= 1;
89        }
90
91        if (candidate > 1) {
92            if ((h > target) && (h / candidate) < target) candidate -= 1;
93        }
94
95        return candidate;
96    }
97
98    public static Bitmap transform(Matrix scaler,
99                                   Bitmap source,
100                                   int targetWidth,
101                                   int targetHeight,
102                                   boolean scaleUp) {
103        int deltaX = source.getWidth() - targetWidth;
104        int deltaY = source.getHeight() - targetHeight;
105        if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
106            /*
107             * In this case the bitmap is smaller, at least in one dimension,
108             * than the target.  Transform it by placing as much of the image
109             * as possible into the target and leaving the top/bottom or
110             * left/right (or both) black.
111             */
112            Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
113                    Bitmap.Config.ARGB_8888);
114            Canvas c = new Canvas(b2);
115
116            int deltaXHalf = Math.max(0, deltaX / 2);
117            int deltaYHalf = Math.max(0, deltaY / 2);
118            Rect src = new Rect(
119                    deltaXHalf,
120                    deltaYHalf,
121                    deltaXHalf + Math.min(targetWidth, source.getWidth()),
122                    deltaYHalf + Math.min(targetHeight, source.getHeight()));
123            int dstX = (targetWidth  - src.width())  / 2;
124            int dstY = (targetHeight - src.height()) / 2;
125            Rect dst = new Rect(
126                    dstX,
127                    dstY,
128                    targetWidth - dstX,
129                    targetHeight - dstY);
130            c.drawBitmap(source, src, dst, null);
131            return b2;
132        }
133        float bitmapWidthF = source.getWidth();
134        float bitmapHeightF = source.getHeight();
135
136        float bitmapAspect = bitmapWidthF / bitmapHeightF;
137        float viewAspect   = (float) targetWidth / targetHeight;
138
139        if (bitmapAspect > viewAspect) {
140            float scale = targetHeight / bitmapHeightF;
141            if (scale < .9F || scale > 1F) {
142                scaler.setScale(scale, scale);
143            } else {
144                scaler = null;
145            }
146        } else {
147            float scale = targetWidth / bitmapWidthF;
148            if (scale < .9F || scale > 1F) {
149                scaler.setScale(scale, scale);
150            } else {
151                scaler = null;
152            }
153        }
154
155        Bitmap b1;
156        if (scaler != null) {
157            // this is used for minithumb and crop, so we want to filter here.
158            b1 = Bitmap.createBitmap(source, 0, 0,
159                    source.getWidth(), source.getHeight(), scaler, true);
160        } else {
161            b1 = source;
162        }
163
164        int dx1 = Math.max(0, b1.getWidth() - targetWidth);
165        int dy1 = Math.max(0, b1.getHeight() - targetHeight);
166
167        Bitmap b2 = Bitmap.createBitmap(
168                b1,
169                dx1 / 2,
170                dy1 / 2,
171                targetWidth,
172                targetHeight);
173
174        if (b1 != source) {
175            b1.recycle();
176        }
177
178        return b2;
179    }
180
181    /**
182     * Creates a centered bitmap of the desired size. Recycles the input.
183     * @param source
184     */
185    public static Bitmap extractMiniThumb(
186            Bitmap source, int width, int height) {
187        return Util.extractMiniThumb(source, width, height, true);
188    }
189
190    public static Bitmap extractMiniThumb(
191            Bitmap source, int width, int height, boolean recycle) {
192        if (source == null) {
193            return null;
194        }
195
196        float scale;
197        if (source.getWidth() < source.getHeight()) {
198            scale = width / (float) source.getWidth();
199        } else {
200            scale = height / (float) source.getHeight();
201        }
202        Matrix matrix = new Matrix();
203        matrix.setScale(scale, scale);
204        Bitmap miniThumbnail = transform(matrix, source, width, height, false);
205
206        if (recycle && miniThumbnail != source) {
207            source.recycle();
208        }
209        return miniThumbnail;
210    }
211
212    /**
213     * Creates a byte[] for a given bitmap of the desired size. Recycles the
214     * input bitmap.
215     */
216    public static byte[] miniThumbData(Bitmap source) {
217        if (source == null) return null;
218
219        Bitmap miniThumbnail = extractMiniThumb(
220                source, IImage.MINI_THUMB_TARGET_SIZE,
221                IImage.MINI_THUMB_TARGET_SIZE);
222
223        ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream();
224        miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
225        miniThumbnail.recycle();
226
227        try {
228            miniOutStream.close();
229            byte [] data = miniOutStream.toByteArray();
230            return data;
231        } catch (java.io.IOException ex) {
232            Log.e(TAG, "got exception ex " + ex);
233        }
234        return null;
235    }
236
237    /**
238     * @return true if the mimetype is a video mimetype.
239     */
240    public static boolean isVideoMimeType(String mimeType) {
241        return mimeType.startsWith("video/");
242    }
243
244    /**
245     * Create a video thumbnail for a video. May return null if the video is
246     * corrupt.
247     *
248     * @param filePath
249     */
250    public static Bitmap createVideoThumbnail(String filePath) {
251        Bitmap bitmap = null;
252        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
253        try {
254            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
255            retriever.setDataSource(filePath);
256            bitmap = retriever.captureFrame();
257        } catch (IllegalArgumentException ex) {
258            // Assume this is a corrupt video file
259        } catch (RuntimeException ex) {
260            // Assume this is a corrupt video file.
261        } finally {
262            try {
263                retriever.release();
264            } catch (RuntimeException ex) {
265                // Ignore failures while cleaning up.
266            }
267        }
268        return bitmap;
269    }
270
271    public static int indexOf(String [] array, String s) {
272        for (int i = 0; i < array.length; i++) {
273            if (array[i].equals(s)) {
274                return i;
275            }
276        }
277        return -1;
278    }
279
280    public static void closeSiliently(Closeable c) {
281        if (c == null) return;
282        try {
283            c.close();
284        } catch (Throwable t) {
285            // do nothing
286        }
287    }
288
289    public static void closeSiliently(ParcelFileDescriptor c) {
290        if (c == null) return;
291        try {
292            c.close();
293        } catch (Throwable t) {
294            // do nothing
295        }
296    }
297
298    /**
299     * Make a bitmap from a given Uri.
300     *
301     * @param uri
302     */
303    public static Bitmap makeBitmap(int targetWidthOrHeight, Uri uri,
304            ContentResolver cr) {
305        ParcelFileDescriptor input = null;
306        try {
307            input = cr.openFileDescriptor(uri, "r");
308            return makeBitmap(targetWidthOrHeight, uri, cr, input, null);
309        } catch (IOException ex) {
310            return null;
311        } finally {
312            closeSiliently(input);
313        }
314    }
315
316    public static Bitmap makeBitmap(int targetWidthHeight, Uri uri,
317            ContentResolver cr, ParcelFileDescriptor pfd,
318            BitmapFactory.Options options) {
319        Bitmap b = null;
320        try {
321            if (pfd == null) pfd = makeInputStream(uri, cr);
322            if (pfd == null) return null;
323            if (options == null) options = new BitmapFactory.Options();
324
325            FileDescriptor fd = pfd.getFileDescriptor();
326            options.inSampleSize = 1;
327            if (targetWidthHeight != -1) {
328                options.inJustDecodeBounds = true;
329                BitmapManager.instance().decodeFileDescriptor(fd, options);
330                if (options.mCancel || options.outWidth == -1
331                        || options.outHeight == -1) {
332                    return null;
333                }
334                options.inSampleSize =
335                        computeSampleSize(options, targetWidthHeight);
336                options.inJustDecodeBounds = false;
337            }
338
339            options.inDither = false;
340            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
341            b = BitmapManager.instance().decodeFileDescriptor(fd, options);
342        } catch (OutOfMemoryError ex) {
343            Log.e(TAG, "Got oom exception ", ex);
344            return null;
345        } finally {
346            closeSiliently(pfd);
347        }
348        return b;
349    }
350
351    private static ParcelFileDescriptor makeInputStream(
352            Uri uri, ContentResolver cr) {
353        try {
354            return cr.openFileDescriptor(uri, "r");
355        } catch (IOException ex) {
356            return null;
357        }
358    }
359}
360