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