Util.java revision 1efb18228a45952d699af280482e1c51a4582690
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.Activity;
22import android.app.AlertDialog;
23import android.content.DialogInterface;
24import android.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.graphics.Matrix;
27import android.hardware.Camera.Size;
28import android.util.Log;
29import android.view.Display;
30import android.view.Surface;
31import android.view.View;
32import android.view.animation.Animation;
33import android.view.animation.TranslateAnimation;
34
35import java.io.Closeable;
36import java.util.List;
37
38/**
39 * Collection of utility functions used in this package.
40 */
41public class Util {
42    private static final String TAG = "Util";
43    public static final int DIRECTION_LEFT = 0;
44    public static final int DIRECTION_RIGHT = 1;
45    public static final int DIRECTION_UP = 2;
46    public static final int DIRECTION_DOWN = 3;
47
48    public static final String REVIEW_ACTION = "com.android.camera.action.REVIEW";
49
50    private Util() {
51    }
52
53    // Rotates the bitmap by the specified degree.
54    // If a new bitmap is created, the original bitmap is recycled.
55    public static Bitmap rotate(Bitmap b, int degrees) {
56        if (degrees != 0 && b != null) {
57            Matrix m = new Matrix();
58            m.setRotate(degrees,
59                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
60            try {
61                Bitmap b2 = Bitmap.createBitmap(
62                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
63                if (b != b2) {
64                    b.recycle();
65                    b = b2;
66                }
67            } catch (OutOfMemoryError ex) {
68                // We have no memory to rotate. Return the original bitmap.
69            }
70        }
71        return b;
72    }
73
74    /*
75     * Compute the sample size as a function of minSideLength
76     * and maxNumOfPixels.
77     * minSideLength is used to specify that minimal width or height of a
78     * bitmap.
79     * maxNumOfPixels is used to specify the maximal size in pixels that is
80     * tolerable in terms of memory usage.
81     *
82     * The function returns a sample size based on the constraints.
83     * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
84     * which indicates no care of the corresponding constraint.
85     * The functions prefers returning a sample size that
86     * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
87     *
88     * Also, the function rounds up the sample size to a power of 2 or multiple
89     * of 8 because BitmapFactory only honors sample size this way.
90     * For example, BitmapFactory downsamples an image by 2 even though the
91     * request is 3. So we round up the sample size to avoid OOM.
92     */
93    public static int computeSampleSize(BitmapFactory.Options options,
94            int minSideLength, int maxNumOfPixels) {
95        int initialSize = computeInitialSampleSize(options, minSideLength,
96                maxNumOfPixels);
97
98        int roundedSize;
99        if (initialSize <= 8) {
100            roundedSize = 1;
101            while (roundedSize < initialSize) {
102                roundedSize <<= 1;
103            }
104        } else {
105            roundedSize = (initialSize + 7) / 8 * 8;
106        }
107
108        return roundedSize;
109    }
110
111    private static int computeInitialSampleSize(BitmapFactory.Options options,
112            int minSideLength, int maxNumOfPixels) {
113        double w = options.outWidth;
114        double h = options.outHeight;
115
116        int lowerBound = (maxNumOfPixels == IImage.UNCONSTRAINED) ? 1 :
117                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
118        int upperBound = (minSideLength == IImage.UNCONSTRAINED) ? 128 :
119                (int) Math.min(Math.floor(w / minSideLength),
120                Math.floor(h / minSideLength));
121
122        if (upperBound < lowerBound) {
123            // return the larger one when there is no overlapping zone.
124            return lowerBound;
125        }
126
127        if ((maxNumOfPixels == IImage.UNCONSTRAINED) &&
128                (minSideLength == IImage.UNCONSTRAINED)) {
129            return 1;
130        } else if (minSideLength == IImage.UNCONSTRAINED) {
131            return lowerBound;
132        } else {
133            return upperBound;
134        }
135    }
136
137    public static <T>  int indexOf(T [] array, T s) {
138        for (int i = 0; i < array.length; i++) {
139            if (array[i].equals(s)) {
140                return i;
141            }
142        }
143        return -1;
144    }
145
146    public static void closeSilently(Closeable c) {
147        if (c == null) return;
148        try {
149            c.close();
150        } catch (Throwable t) {
151            // do nothing
152        }
153    }
154
155    public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) {
156        try {
157            BitmapFactory.Options options = new BitmapFactory.Options();
158            options.inJustDecodeBounds = true;
159            BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
160                    options);
161            if (options.mCancel || options.outWidth == -1
162                    || options.outHeight == -1) {
163                return null;
164            }
165            options.inSampleSize = computeSampleSize(
166                    options, IImage.UNCONSTRAINED, maxNumOfPixels);
167            options.inJustDecodeBounds = false;
168
169            options.inDither = false;
170            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
171            return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length,
172                    options);
173        } catch (OutOfMemoryError ex) {
174            Log.e(TAG, "Got oom exception ", ex);
175            return null;
176        }
177    }
178
179    public static void Assert(boolean cond) {
180        if (!cond) {
181            throw new AssertionError();
182        }
183    }
184
185    public static void showFatalErrorAndFinish(
186            final Activity activity, String title, String message) {
187        DialogInterface.OnClickListener buttonListener =
188                new DialogInterface.OnClickListener() {
189            public void onClick(DialogInterface dialog, int which) {
190                activity.finish();
191            }
192        };
193        new AlertDialog.Builder(activity)
194                .setCancelable(false)
195                .setIcon(android.R.drawable.ic_dialog_alert)
196                .setTitle(title)
197                .setMessage(message)
198                .setNeutralButton(R.string.details_ok, buttonListener)
199                .show();
200    }
201
202    public static Animation slideOut(View view, int to) {
203        view.setVisibility(View.INVISIBLE);
204        Animation anim;
205        switch (to) {
206            case DIRECTION_LEFT:
207                anim = new TranslateAnimation(0, -view.getWidth(), 0, 0);
208                break;
209            case DIRECTION_RIGHT:
210                anim = new TranslateAnimation(0, view.getWidth(), 0, 0);
211                break;
212            case DIRECTION_UP:
213                anim = new TranslateAnimation(0, 0, 0, -view.getHeight());
214                break;
215            case DIRECTION_DOWN:
216                anim = new TranslateAnimation(0, 0, 0, view.getHeight());
217                break;
218            default:
219                throw new IllegalArgumentException(Integer.toString(to));
220        }
221        anim.setDuration(500);
222        view.startAnimation(anim);
223        return anim;
224    }
225
226    public static Animation slideIn(View view, int from) {
227        view.setVisibility(View.VISIBLE);
228        Animation anim;
229        switch (from) {
230            case DIRECTION_LEFT:
231                anim = new TranslateAnimation(-view.getWidth(), 0, 0, 0);
232                break;
233            case DIRECTION_RIGHT:
234                anim = new TranslateAnimation(view.getWidth(), 0, 0, 0);
235                break;
236            case DIRECTION_UP:
237                anim = new TranslateAnimation(0, 0, -view.getHeight(), 0);
238                break;
239            case DIRECTION_DOWN:
240                anim = new TranslateAnimation(0, 0, view.getHeight(), 0);
241                break;
242            default:
243                throw new IllegalArgumentException(Integer.toString(from));
244        }
245        anim.setDuration(500);
246        view.startAnimation(anim);
247        return anim;
248    }
249
250    public static <T> T checkNotNull(T object) {
251        if (object == null) throw new NullPointerException();
252        return object;
253    }
254
255    public static boolean equals(Object a, Object b) {
256        return (a == b) || (a == null ? false : a.equals(b));
257    }
258
259    public static boolean isPowerOf2(int n) {
260        return (n & -n) == n;
261    }
262
263    public static int nextPowerOf2(int n) {
264        n -= 1;
265        n |= n >>> 16;
266        n |= n >>> 8;
267        n |= n >>> 4;
268        n |= n >>> 2;
269        n |= n >>> 1;
270        return n + 1;
271    }
272
273    public static float distance(float x, float y, float sx, float sy) {
274        float dx = x - sx;
275        float dy = y - sy;
276        return (float) Math.sqrt(dx * dx + dy * dy);
277    }
278
279    public static int clamp(int x, int min, int max) {
280        if (x > max) return max;
281        if (x < min) return min;
282        return x;
283    }
284
285    public static int getDisplayRotation(Activity activity) {
286        int rotation = activity.getWindowManager().getDefaultDisplay()
287                .getRotation();
288        switch (rotation) {
289            case Surface.ROTATION_0: return 0;
290            case Surface.ROTATION_90: return 90;
291            case Surface.ROTATION_180: return 180;
292            case Surface.ROTATION_270: return 270;
293        }
294        return 0;
295    }
296
297    public static void setCameraDisplayOrientation(Activity activity,
298            int cameraId, android.hardware.Camera camera) {
299        android.hardware.Camera.CameraInfo info =
300                new android.hardware.Camera.CameraInfo();
301        android.hardware.Camera.getCameraInfo(cameraId, info);
302        int degrees = getDisplayRotation(activity);
303        int result = (info.orientation - degrees + 360) % 360;
304        camera.setDisplayOrientation(result);
305    }
306
307    public static Size getOptimalPreviewSize(Activity currentActivity,
308            List<Size> sizes, double targetRatio) {
309        final double ASPECT_TOLERANCE = 0.05;
310        if (sizes == null) return null;
311
312        Size optimalSize = null;
313        double minDiff = Double.MAX_VALUE;
314
315        // Because of bugs of overlay and layout, we sometimes will try to
316        // layout the viewfinder in the portrait orientation and thus get the
317        // wrong size of mSurfaceView. When we change the preview size, the
318        // new overlay will be created before the old one closed, which causes
319        // an exception. For now, just get the screen size
320
321        Display display = currentActivity.getWindowManager().getDefaultDisplay();
322        int targetHeight = Math.min(display.getHeight(), display.getWidth());
323
324        if (targetHeight <= 0) {
325            // We don't know the size of SurfaceView, use screen height
326            targetHeight = display.getHeight();
327        }
328
329        // Try to find an size match aspect ratio and size
330        for (Size size : sizes) {
331            double ratio = (double) size.width / size.height;
332            if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
333            if (Math.abs(size.height - targetHeight) < minDiff) {
334                optimalSize = size;
335                minDiff = Math.abs(size.height - targetHeight);
336            }
337        }
338
339        // Cannot find the one match the aspect ratio, ignore the requirement
340        if (optimalSize == null) {
341            Log.v(TAG, "No preview size match the aspect ratio");
342            minDiff = Double.MAX_VALUE;
343            for (Size size : sizes) {
344                if (Math.abs(size.height - targetHeight) < minDiff) {
345                    optimalSize = size;
346                    minDiff = Math.abs(size.height - targetHeight);
347                }
348            }
349        }
350        return optimalSize;
351    }
352}
353