Util.java revision bbc2f2f15a865cf0330cd78a6ea67177c5863b3b
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.app.ProgressDialog;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.graphics.Canvas;
27import android.graphics.Matrix;
28import android.graphics.Paint;
29import android.graphics.Path;
30import android.graphics.Rect;
31import android.graphics.RectF;
32import android.media.MediaMetadataRetriever;
33import android.net.Uri;
34import android.os.ParcelFileDescriptor;
35import android.util.Log;
36import android.view.View;
37import android.view.View.OnClickListener;
38
39import java.io.ByteArrayOutputStream;
40import java.io.Closeable;
41import java.io.FileDescriptor;
42import java.io.IOException;
43
44/**
45 * Collection of utility functions used in this package.
46 */
47public class Util {
48    private static final String TAG = "db.Util";
49
50    private static OnClickListener sNullOnClickListener;
51
52    private Util() {
53    }
54
55    // Rotates the bitmap by the specified degree.
56    // If a new bitmap is created, the original bitmap is recycled.
57    public static Bitmap rotate(Bitmap b, int degrees) {
58        if (degrees != 0 && b != null) {
59            Matrix m = new Matrix();
60            m.setRotate(degrees,
61                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
62            try {
63                Bitmap b2 = Bitmap.createBitmap(
64                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
65                if (b != b2) {
66                    b.recycle();
67                    b = b2;
68                }
69            } catch (OutOfMemoryError ex) {
70                // We have no memory to rotate. Return the original bitmap.
71            }
72        }
73        return b;
74    }
75
76    /*
77     * Compute the sample size as a function of the image size and the target.
78     * Scale the image down so that both the width and height are just above the
79     * target. If this means that one of the dimension goes from above the
80     * target to below the target (e.g. given a width of 480 and an image width
81     * of 600 but sample size of 2 -- i.e. new width 300 -- bump the sample size
82     * down by 1.
83     */
84    public static int computeSampleSize(
85            BitmapFactory.Options options, int target) {
86        int w = options.outWidth;
87        int h = options.outHeight;
88
89        int candidateW = w / target;
90        int candidateH = h / target;
91        int candidate = Math.max(candidateW, candidateH);
92
93        if (candidate == 0) return 1;
94
95        if (candidate > 1) {
96            if ((w > target) && (w / candidate) < target) candidate -= 1;
97        }
98
99        if (candidate > 1) {
100            if ((h > target) && (h / candidate) < target) candidate -= 1;
101        }
102
103        return candidate;
104    }
105
106    public static Bitmap transform(Matrix scaler,
107                                   Bitmap source,
108                                   int targetWidth,
109                                   int targetHeight,
110                                   boolean scaleUp) {
111        int deltaX = source.getWidth() - targetWidth;
112        int deltaY = source.getHeight() - targetHeight;
113        if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
114            /*
115             * In this case the bitmap is smaller, at least in one dimension,
116             * than the target.  Transform it by placing as much of the image
117             * as possible into the target and leaving the top/bottom or
118             * left/right (or both) black.
119             */
120            Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
121                    Bitmap.Config.ARGB_8888);
122            Canvas c = new Canvas(b2);
123
124            int deltaXHalf = Math.max(0, deltaX / 2);
125            int deltaYHalf = Math.max(0, deltaY / 2);
126            Rect src = new Rect(
127                    deltaXHalf,
128                    deltaYHalf,
129                    deltaXHalf + Math.min(targetWidth, source.getWidth()),
130                    deltaYHalf + Math.min(targetHeight, source.getHeight()));
131            int dstX = (targetWidth  - src.width())  / 2;
132            int dstY = (targetHeight - src.height()) / 2;
133            Rect dst = new Rect(
134                    dstX,
135                    dstY,
136                    targetWidth - dstX,
137                    targetHeight - dstY);
138            c.drawBitmap(source, src, dst, null);
139            return b2;
140        }
141        float bitmapWidthF = source.getWidth();
142        float bitmapHeightF = source.getHeight();
143
144        float bitmapAspect = bitmapWidthF / bitmapHeightF;
145        float viewAspect   = (float) targetWidth / targetHeight;
146
147        if (bitmapAspect > viewAspect) {
148            float scale = targetHeight / bitmapHeightF;
149            if (scale < .9F || scale > 1F) {
150                scaler.setScale(scale, scale);
151            } else {
152                scaler = null;
153            }
154        } else {
155            float scale = targetWidth / bitmapWidthF;
156            if (scale < .9F || scale > 1F) {
157                scaler.setScale(scale, scale);
158            } else {
159                scaler = null;
160            }
161        }
162
163        Bitmap b1;
164        if (scaler != null) {
165            // this is used for minithumb and crop, so we want to filter here.
166            b1 = Bitmap.createBitmap(source, 0, 0,
167                    source.getWidth(), source.getHeight(), scaler, true);
168        } else {
169            b1 = source;
170        }
171
172        int dx1 = Math.max(0, b1.getWidth() - targetWidth);
173        int dy1 = Math.max(0, b1.getHeight() - targetHeight);
174
175        Bitmap b2 = Bitmap.createBitmap(
176                b1,
177                dx1 / 2,
178                dy1 / 2,
179                targetWidth,
180                targetHeight);
181
182        if (b1 != source) {
183            b1.recycle();
184        }
185
186        return b2;
187    }
188
189    /**
190     * Creates a centered bitmap of the desired size. Recycles the input.
191     * @param source
192     */
193    public static Bitmap extractMiniThumb(
194            Bitmap source, int width, int height) {
195        return Util.extractMiniThumb(source, width, height, true);
196    }
197
198    public static Bitmap extractMiniThumb(
199            Bitmap source, int width, int height, boolean recycle) {
200        if (source == null) {
201            return null;
202        }
203
204        float scale;
205        if (source.getWidth() < source.getHeight()) {
206            scale = width / (float) source.getWidth();
207        } else {
208            scale = height / (float) source.getHeight();
209        }
210        Matrix matrix = new Matrix();
211        matrix.setScale(scale, scale);
212        Bitmap miniThumbnail = transform(matrix, source, width, height, false);
213
214        if (recycle && miniThumbnail != source) {
215            source.recycle();
216        }
217        return miniThumbnail;
218    }
219
220    /**
221     * Creates a byte[] for a given bitmap of the desired size. Recycles the
222     * input bitmap.
223     */
224    public static byte[] miniThumbData(Bitmap source) {
225        if (source == null) return null;
226
227        Bitmap miniThumbnail = extractMiniThumb(
228                source, IImage.MINI_THUMB_TARGET_SIZE,
229                IImage.MINI_THUMB_TARGET_SIZE);
230
231        ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream();
232        miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
233        miniThumbnail.recycle();
234
235        try {
236            miniOutStream.close();
237            byte [] data = miniOutStream.toByteArray();
238            return data;
239        } catch (java.io.IOException ex) {
240            Log.e(TAG, "got exception ex " + ex);
241        }
242        return null;
243    }
244
245    /**
246     * @return true if the mimetype is a video mimetype.
247     */
248    public static boolean isVideoMimeType(String mimeType) {
249        return mimeType.startsWith("video/");
250    }
251
252    /**
253     * Create a video thumbnail for a video. May return null if the video is
254     * corrupt.
255     *
256     * @param filePath
257     */
258    public static Bitmap createVideoThumbnail(String filePath) {
259        Bitmap bitmap = null;
260        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
261        try {
262            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
263            retriever.setDataSource(filePath);
264            bitmap = retriever.captureFrame();
265        } catch (IllegalArgumentException ex) {
266            // Assume this is a corrupt video file
267        } catch (RuntimeException ex) {
268            // Assume this is a corrupt video file.
269        } finally {
270            try {
271                retriever.release();
272            } catch (RuntimeException ex) {
273                // Ignore failures while cleaning up.
274            }
275        }
276        return bitmap;
277    }
278
279    public static <T>  int indexOf(T [] array, T s) {
280        for (int i = 0; i < array.length; i++) {
281            if (array[i].equals(s)) {
282                return i;
283            }
284        }
285        return -1;
286    }
287
288    public static void closeSilently(Closeable c) {
289        if (c == null) return;
290        try {
291            c.close();
292        } catch (Throwable t) {
293            // do nothing
294        }
295    }
296
297    public static void closeSilently(ParcelFileDescriptor c) {
298        if (c == null) return;
299        try {
300            c.close();
301        } catch (Throwable t) {
302            // do nothing
303        }
304    }
305
306    /**
307     * Make a bitmap from a given Uri.
308     *
309     * @param uri
310     */
311    public static Bitmap makeBitmap(int targetWidthOrHeight, Uri uri,
312            ContentResolver cr) {
313        ParcelFileDescriptor input = null;
314        try {
315            input = cr.openFileDescriptor(uri, "r");
316            return makeBitmap(targetWidthOrHeight, uri, cr, input, null);
317        } catch (IOException ex) {
318            return null;
319        } finally {
320            closeSilently(input);
321        }
322    }
323
324    public static Bitmap makeBitmap(int targetWidthHeight, Uri uri,
325            ContentResolver cr, ParcelFileDescriptor pfd,
326            BitmapFactory.Options options) {
327        Bitmap b = null;
328        try {
329            if (pfd == null) pfd = makeInputStream(uri, cr);
330            if (pfd == null) return null;
331            if (options == null) options = new BitmapFactory.Options();
332
333            FileDescriptor fd = pfd.getFileDescriptor();
334            options.inSampleSize = 1;
335            if (targetWidthHeight != -1) {
336                options.inJustDecodeBounds = true;
337                BitmapManager.instance().decodeFileDescriptor(fd, options);
338                if (options.mCancel || options.outWidth == -1
339                        || options.outHeight == -1) {
340                    return null;
341                }
342                options.inSampleSize =
343                        computeSampleSize(options, targetWidthHeight);
344                options.inJustDecodeBounds = false;
345            }
346
347            options.inDither = false;
348            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
349            b = BitmapManager.instance().decodeFileDescriptor(fd, options);
350        } catch (OutOfMemoryError ex) {
351            Log.e(TAG, "Got oom exception ", ex);
352            return null;
353        } finally {
354            closeSilently(pfd);
355        }
356        return b;
357    }
358
359    private static ParcelFileDescriptor makeInputStream(
360            Uri uri, ContentResolver cr) {
361        try {
362            return cr.openFileDescriptor(uri, "r");
363        } catch (IOException ex) {
364            return null;
365        }
366    }
367
368    public static void debugWhere(String tag, String msg) {
369        Log.d(tag, msg + " --- stack trace begins: ");
370        StackTraceElement elements[] = Thread.currentThread().getStackTrace();
371        // skip first 3 element, they are not related to the caller
372        for (int i = 3, n = elements.length; i < n; ++i) {
373            StackTraceElement st = elements[i];
374            String message = String.format("    at %s.%s(%s:%s)",
375                    st.getClassName(), st.getMethodName(), st.getFileName(),
376                    st.getLineNumber());
377            Log.d(tag, message);
378        }
379        Log.d(tag, msg + " --- stack trace ends.");
380    }
381
382    public static <T> void showProgressDialog(final Context context,
383            String title, String message, PriorityTask<T> task) {
384        final ProgressDialog dialog =
385                ProgressDialog.show(context, title, message);
386
387        task.addCallback(new PriorityTask.Callback<T>() {
388
389            public void onCanceled(PriorityTask<T> t) {
390                dialog.dismiss();
391            }
392
393            public void onFail(PriorityTask<T> t, Throwable error) {
394                dialog.dismiss();
395            }
396
397            public void onResultAvailable(PriorityTask<T> t, T result) {
398                dialog.dismiss();
399            }
400        });
401    }
402
403    public static synchronized OnClickListener getNullOnClickListener() {
404        if (sNullOnClickListener == null) {
405            sNullOnClickListener = new OnClickListener() {
406                public void onClick(View v) {
407                }
408            };
409        }
410        return sNullOnClickListener;
411    }
412
413    public static void Assert(boolean cond) {
414        if (!cond) {
415            throw new AssertionError();
416        }
417    }
418
419    public static Bitmap makeRoundedCorner(Bitmap thumb, int rx, int ry) {
420        if (thumb == null) return null;
421        int width = thumb.getWidth();
422        int height = thumb.getHeight();
423
424        Bitmap result = Bitmap.createBitmap(
425                width, height, Bitmap.Config.ARGB_8888);
426        Canvas canvas = new Canvas(result);
427        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
428        Path path = new Path();
429        path.addRoundRect(new RectF(
430                0, 0, width, height), rx, ry, Path.Direction.CCW);
431        canvas.clipPath(path);
432        canvas.drawBitmap(thumb, 0, 0, paint);
433        return result;
434    }
435}
436