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