Util.java revision 35f8af0ff228adfeec6bb40844d362f6dd62dab6
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     * Create a video thumbnail for a video. May return null if the video is
247     * corrupt.
248     *
249     * @param filePath
250     */
251    public static Bitmap createVideoThumbnail(String filePath) {
252        Bitmap bitmap = null;
253        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
254        try {
255            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
256            retriever.setDataSource(filePath);
257            bitmap = retriever.captureFrame();
258        } catch (IllegalArgumentException ex) {
259            // Assume this is a corrupt video file
260        } catch (RuntimeException ex) {
261            // Assume this is a corrupt video file.
262        } finally {
263            try {
264                retriever.release();
265            } catch (RuntimeException ex) {
266                // Ignore failures while cleaning up.
267            }
268        }
269        return bitmap;
270    }
271
272    public static <T>  int indexOf(T [] array, T s) {
273        for (int i = 0; i < array.length; i++) {
274            if (array[i].equals(s)) {
275                return i;
276            }
277        }
278        return -1;
279    }
280
281    public static void closeSilently(Closeable c) {
282        if (c == null) return;
283        try {
284            c.close();
285        } catch (Throwable t) {
286            // do nothing
287        }
288    }
289
290    public static void closeSilently(ParcelFileDescriptor c) {
291        if (c == null) return;
292        try {
293            c.close();
294        } catch (Throwable t) {
295            // do nothing
296        }
297    }
298
299    /**
300     * Make a bitmap from a given Uri.
301     *
302     * @param uri
303     */
304    public static Bitmap makeBitmap(int targetWidthOrHeight, Uri uri,
305            ContentResolver cr) {
306        ParcelFileDescriptor input = null;
307        try {
308            input = cr.openFileDescriptor(uri, "r");
309            return makeBitmap(targetWidthOrHeight, uri, cr, input, null);
310        } catch (IOException ex) {
311            return null;
312        } finally {
313            closeSilently(input);
314        }
315    }
316
317    public static Bitmap makeBitmap(int targetWidthHeight, Uri uri,
318            ContentResolver cr, ParcelFileDescriptor pfd,
319            BitmapFactory.Options options) {
320        Bitmap b = null;
321        try {
322            if (pfd == null) pfd = makeInputStream(uri, cr);
323            if (pfd == null) return null;
324            if (options == null) options = new BitmapFactory.Options();
325
326            FileDescriptor fd = pfd.getFileDescriptor();
327            options.inSampleSize = 1;
328            if (targetWidthHeight != -1) {
329                options.inJustDecodeBounds = true;
330                BitmapManager.instance().decodeFileDescriptor(fd, options);
331                if (options.mCancel || options.outWidth == -1
332                        || options.outHeight == -1) {
333                    return null;
334                }
335                options.inSampleSize =
336                        computeSampleSize(options, targetWidthHeight);
337                options.inJustDecodeBounds = false;
338            }
339
340            options.inDither = false;
341            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
342            b = BitmapManager.instance().decodeFileDescriptor(fd, options);
343        } catch (OutOfMemoryError ex) {
344            Log.e(TAG, "Got oom exception ", ex);
345            return null;
346        } finally {
347            closeSilently(pfd);
348        }
349        return b;
350    }
351
352    private static ParcelFileDescriptor makeInputStream(
353            Uri uri, ContentResolver cr) {
354        try {
355            return cr.openFileDescriptor(uri, "r");
356        } catch (IOException ex) {
357            return null;
358        }
359    }
360
361    public static void debugWhere(String tag, String msg) {
362        Log.d(tag, msg + " --- stack trace begins: ");
363        StackTraceElement elements[] = Thread.currentThread().getStackTrace();
364        // skip first 3 element, they are not related to the caller
365        for (int i = 3, n = elements.length; i < n; ++i) {
366            StackTraceElement st = elements[i];
367            String message = String.format("    at %s.%s(%s:%s)",
368                    st.getClassName(), st.getMethodName(), st.getFileName(),
369                    st.getLineNumber());
370            Log.d(tag, message);
371        }
372        Log.d(tag, msg + " --- stack trace ends.");
373    }
374
375    public static <T> void showProgressDialog(final Context context,
376            String title, String message, PriorityTask<T> task) {
377        final ProgressDialog dialog =
378                ProgressDialog.show(context, title, message);
379
380        task.addCallback(new PriorityTask.Callback<T>() {
381
382            public void onCanceled(PriorityTask<T> t) {
383                dialog.dismiss();
384            }
385
386            public void onFail(PriorityTask<T> t, Throwable error) {
387                dialog.dismiss();
388            }
389
390            public void onResultAvailable(PriorityTask<T> t, T result) {
391                dialog.dismiss();
392            }
393        });
394    }
395
396    public static synchronized OnClickListener getNullOnClickListener() {
397        if (sNullOnClickListener == null) {
398            sNullOnClickListener = new OnClickListener() {
399                public void onClick(View v) {
400                }
401            };
402        }
403        return sNullOnClickListener;
404    }
405
406    public static void Assert(boolean cond) {
407        if (!cond) {
408            throw new AssertionError();
409        }
410    }
411
412    public static Bitmap makeRoundedCorner(Bitmap thumb, int rx, int ry) {
413        if (thumb == null) return null;
414        int width = thumb.getWidth();
415        int height = thumb.getHeight();
416
417        Bitmap result = Bitmap.createBitmap(
418                width, height, Bitmap.Config.ARGB_8888);
419        Canvas canvas = new Canvas(result);
420        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
421        Path path = new Path();
422        path.addRoundRect(new RectF(
423                0, 0, width, height), rx, ry, Path.Direction.CCW);
424        canvas.clipPath(path);
425        canvas.drawBitmap(thumb, 0, 0, paint);
426        return result;
427    }
428
429    public static boolean equals(String a, String b) {
430        // return true if both string are null or the content equals
431        return a == b || a.equals(b);
432    }
433}
434