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 com.android.gallery.R;
     20 
     21 import android.app.Activity;
     22 import android.app.AlertDialog;
     23 import android.app.ProgressDialog;
     24 import android.content.ContentResolver;
     25 import android.content.DialogInterface;
     26 import android.content.Intent;
     27 import android.graphics.Bitmap;
     28 import android.graphics.BitmapFactory;
     29 import android.graphics.Canvas;
     30 import android.graphics.Matrix;
     31 import android.graphics.Rect;
     32 import android.net.Uri;
     33 import android.os.Handler;
     34 import android.os.ParcelFileDescriptor;
     35 import android.util.Log;
     36 import android.view.View;
     37 import android.view.View.OnClickListener;
     38 import android.view.animation.Animation;
     39 import android.view.animation.TranslateAnimation;
     40 
     41 import com.android.camera.gallery.IImage;
     42 
     43 import java.io.Closeable;
     44 import java.io.FileDescriptor;
     45 import java.io.IOException;
     46 
     47 /**
     48  * Collection of utility functions used in this package.
     49  */
     50 public class Util {
     51     private static final String TAG = "Util";
     52     public static final int DIRECTION_LEFT = 0;
     53     public static final int DIRECTION_RIGHT = 1;
     54     public static final int DIRECTION_UP = 2;
     55     public static final int DIRECTION_DOWN = 3;
     56 
     57     private static OnClickListener sNullOnClickListener;
     58 
     59     private Util() {
     60     }
     61 
     62     // Rotates the bitmap by the specified degree.
     63     // If a new bitmap is created, the original bitmap is recycled.
     64     public static Bitmap rotate(Bitmap b, int degrees) {
     65         if (degrees != 0 && b != null) {
     66             Matrix m = new Matrix();
     67             m.setRotate(degrees,
     68                     (float) b.getWidth() / 2, (float) b.getHeight() / 2);
     69             try {
     70                 Bitmap b2 = Bitmap.createBitmap(
     71                         b, 0, 0, b.getWidth(), b.getHeight(), m, true);
     72                 if (b != b2) {
     73                     b.recycle();
     74                     b = b2;
     75                 }
     76             } catch (OutOfMemoryError ex) {
     77                 // We have no memory to rotate. Return the original bitmap.
     78             }
     79         }
     80         return b;
     81     }
     82 
     83     /*
     84      * Compute the sample size as a function of minSideLength
     85      * and maxNumOfPixels.
     86      * minSideLength is used to specify that minimal width or height of a
     87      * bitmap.
     88      * maxNumOfPixels is used to specify the maximal size in pixels that is
     89      * tolerable in terms of memory usage.
     90      *
     91      * The function returns a sample size based on the constraints.
     92      * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
     93      * which indicates no care of the corresponding constraint.
     94      * The functions prefers returning a sample size that
     95      * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
     96      *
     97      * Also, the function rounds up the sample size to a power of 2 or multiple
     98      * of 8 because BitmapFactory only honors sample size this way.
     99      * For example, BitmapFactory downsamples an image by 2 even though the
    100      * request is 3. So we round up the sample size to avoid OOM.
    101      */
    102     public static int computeSampleSize(BitmapFactory.Options options,
    103             int minSideLength, int maxNumOfPixels) {
    104         int initialSize = computeInitialSampleSize(options, minSideLength,
    105                 maxNumOfPixels);
    106 
    107         int roundedSize;
    108         if (initialSize <= 8) {
    109             roundedSize = 1;
    110             while (roundedSize < initialSize) {
    111                 roundedSize <<= 1;
    112             }
    113         } else {
    114             roundedSize = (initialSize + 7) / 8 * 8;
    115         }
    116 
    117         return roundedSize;
    118     }
    119 
    120     private static int computeInitialSampleSize(BitmapFactory.Options options,
    121             int minSideLength, int maxNumOfPixels) {
    122         double w = options.outWidth;
    123         double h = options.outHeight;
    124 
    125         int lowerBound = (maxNumOfPixels == IImage.UNCONSTRAINED) ? 1 :
    126                 (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
    127         int upperBound = (minSideLength == IImage.UNCONSTRAINED) ? 128 :
    128                 (int) Math.min(Math.floor(w / minSideLength),
    129                 Math.floor(h / minSideLength));
    130 
    131         if (upperBound < lowerBound) {
    132             // return the larger one when there is no overlapping zone.
    133             return lowerBound;
    134         }
    135 
    136         if ((maxNumOfPixels == IImage.UNCONSTRAINED) &&
    137                 (minSideLength == IImage.UNCONSTRAINED)) {
    138             return 1;
    139         } else if (minSideLength == IImage.UNCONSTRAINED) {
    140             return lowerBound;
    141         } else {
    142             return upperBound;
    143         }
    144     }
    145 
    146     // Whether we should recycle the input (unless the output is the input).
    147     public static final boolean RECYCLE_INPUT = true;
    148     public static final boolean NO_RECYCLE_INPUT = false;
    149 
    150     public static Bitmap transform(Matrix scaler,
    151                                    Bitmap source,
    152                                    int targetWidth,
    153                                    int targetHeight,
    154                                    boolean scaleUp,
    155                                    boolean recycle) {
    156         int deltaX = source.getWidth() - targetWidth;
    157         int deltaY = source.getHeight() - targetHeight;
    158         if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
    159             /*
    160              * In this case the bitmap is smaller, at least in one dimension,
    161              * than the target.  Transform it by placing as much of the image
    162              * as possible into the target and leaving the top/bottom or
    163              * left/right (or both) black.
    164              */
    165             Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
    166                     Bitmap.Config.ARGB_8888);
    167             Canvas c = new Canvas(b2);
    168 
    169             int deltaXHalf = Math.max(0, deltaX / 2);
    170             int deltaYHalf = Math.max(0, deltaY / 2);
    171             Rect src = new Rect(
    172                     deltaXHalf,
    173                     deltaYHalf,
    174                     deltaXHalf + Math.min(targetWidth, source.getWidth()),
    175                     deltaYHalf + Math.min(targetHeight, source.getHeight()));
    176             int dstX = (targetWidth  - src.width())  / 2;
    177             int dstY = (targetHeight - src.height()) / 2;
    178             Rect dst = new Rect(
    179                     dstX,
    180                     dstY,
    181                     targetWidth - dstX,
    182                     targetHeight - dstY);
    183             c.drawBitmap(source, src, dst, null);
    184             if (recycle) {
    185                 source.recycle();
    186             }
    187             return b2;
    188         }
    189         float bitmapWidthF = source.getWidth();
    190         float bitmapHeightF = source.getHeight();
    191 
    192         float bitmapAspect = bitmapWidthF / bitmapHeightF;
    193         float viewAspect   = (float) targetWidth / targetHeight;
    194 
    195         if (bitmapAspect > viewAspect) {
    196             float scale = targetHeight / bitmapHeightF;
    197             if (scale < .9F || scale > 1F) {
    198                 scaler.setScale(scale, scale);
    199             } else {
    200                 scaler = null;
    201             }
    202         } else {
    203             float scale = targetWidth / bitmapWidthF;
    204             if (scale < .9F || scale > 1F) {
    205                 scaler.setScale(scale, scale);
    206             } else {
    207                 scaler = null;
    208             }
    209         }
    210 
    211         Bitmap b1;
    212         if (scaler != null) {
    213             // this is used for minithumb and crop, so we want to filter here.
    214             b1 = Bitmap.createBitmap(source, 0, 0,
    215                     source.getWidth(), source.getHeight(), scaler, true);
    216         } else {
    217             b1 = source;
    218         }
    219 
    220         if (recycle && b1 != source) {
    221             source.recycle();
    222         }
    223 
    224         int dx1 = Math.max(0, b1.getWidth() - targetWidth);
    225         int dy1 = Math.max(0, b1.getHeight() - targetHeight);
    226 
    227         Bitmap b2 = Bitmap.createBitmap(
    228                 b1,
    229                 dx1 / 2,
    230                 dy1 / 2,
    231                 targetWidth,
    232                 targetHeight);
    233 
    234         if (b2 != b1) {
    235             if (recycle || b1 != source) {
    236                 b1.recycle();
    237             }
    238         }
    239 
    240         return b2;
    241     }
    242 
    243     public static <T>  int indexOf(T [] array, T s) {
    244         for (int i = 0; i < array.length; i++) {
    245             if (array[i].equals(s)) {
    246                 return i;
    247             }
    248         }
    249         return -1;
    250     }
    251 
    252     public static void closeSilently(Closeable c) {
    253         if (c == null) return;
    254         try {
    255             c.close();
    256         } catch (Throwable t) {
    257             // do nothing
    258         }
    259     }
    260 
    261     public static void closeSilently(ParcelFileDescriptor c) {
    262         if (c == null) return;
    263         try {
    264             c.close();
    265         } catch (Throwable t) {
    266             // do nothing
    267         }
    268     }
    269 
    270     /**
    271      * Make a bitmap from a given Uri.
    272      *
    273      * @param uri
    274      */
    275     public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
    276             Uri uri, ContentResolver cr, boolean useNative) {
    277         ParcelFileDescriptor input = null;
    278         try {
    279             input = cr.openFileDescriptor(uri, "r");
    280             BitmapFactory.Options options = null;
    281             if (useNative) {
    282                 options = createNativeAllocOptions();
    283             }
    284             return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, input,
    285                     options);
    286         } catch (IOException ex) {
    287             return null;
    288         } finally {
    289             closeSilently(input);
    290         }
    291     }
    292 
    293     public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
    294             ParcelFileDescriptor pfd, boolean useNative) {
    295         BitmapFactory.Options options = null;
    296         if (useNative) {
    297             options = createNativeAllocOptions();
    298         }
    299         return makeBitmap(minSideLength, maxNumOfPixels, null, null, pfd,
    300                 options);
    301     }
    302 
    303     public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
    304             Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
    305             BitmapFactory.Options options) {
    306         try {
    307             if (pfd == null) pfd = makeInputStream(uri, cr);
    308             if (pfd == null) return null;
    309             if (options == null) options = new BitmapFactory.Options();
    310 
    311             FileDescriptor fd = pfd.getFileDescriptor();
    312             options.inJustDecodeBounds = true;
    313             BitmapManager.instance().decodeFileDescriptor(fd, options);
    314             if (options.mCancel || options.outWidth == -1
    315                     || options.outHeight == -1) {
    316                 return null;
    317             }
    318             options.inSampleSize = computeSampleSize(
    319                     options, minSideLength, maxNumOfPixels);
    320             options.inJustDecodeBounds = false;
    321 
    322             options.inDither = false;
    323             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    324             return BitmapManager.instance().decodeFileDescriptor(fd, options);
    325         } catch (OutOfMemoryError ex) {
    326             Log.e(TAG, "Got oom exception ", ex);
    327             return null;
    328         } finally {
    329             closeSilently(pfd);
    330         }
    331     }
    332 
    333     private static ParcelFileDescriptor makeInputStream(
    334             Uri uri, ContentResolver cr) {
    335         try {
    336             return cr.openFileDescriptor(uri, "r");
    337         } catch (IOException ex) {
    338             return null;
    339         }
    340     }
    341 
    342     public static synchronized OnClickListener getNullOnClickListener() {
    343         if (sNullOnClickListener == null) {
    344             sNullOnClickListener = new OnClickListener() {
    345                 public void onClick(View v) {
    346                 }
    347             };
    348         }
    349         return sNullOnClickListener;
    350     }
    351 
    352     public static void Assert(boolean cond) {
    353         if (!cond) {
    354             throw new AssertionError();
    355         }
    356     }
    357 
    358     public static boolean equals(String a, String b) {
    359         // return true if both string are null or the content equals
    360         return a == b || a.equals(b);
    361     }
    362 
    363     private static class BackgroundJob
    364             extends MonitoredActivity.LifeCycleAdapter implements Runnable {
    365 
    366         private final MonitoredActivity mActivity;
    367         private final ProgressDialog mDialog;
    368         private final Runnable mJob;
    369         private final Handler mHandler;
    370         private final Runnable mCleanupRunner = new Runnable() {
    371             public void run() {
    372                 mActivity.removeLifeCycleListener(BackgroundJob.this);
    373                 if (mDialog.getWindow() != null) mDialog.dismiss();
    374             }
    375         };
    376 
    377         public BackgroundJob(MonitoredActivity activity, Runnable job,
    378                 ProgressDialog dialog, Handler handler) {
    379             mActivity = activity;
    380             mDialog = dialog;
    381             mJob = job;
    382             mActivity.addLifeCycleListener(this);
    383             mHandler = handler;
    384         }
    385 
    386         public void run() {
    387             try {
    388                 mJob.run();
    389             } finally {
    390                 mHandler.post(mCleanupRunner);
    391             }
    392         }
    393 
    394 
    395         @Override
    396         public void onActivityDestroyed(MonitoredActivity activity) {
    397             // We get here only when the onDestroyed being called before
    398             // the mCleanupRunner. So, run it now and remove it from the queue
    399             mCleanupRunner.run();
    400             mHandler.removeCallbacks(mCleanupRunner);
    401         }
    402 
    403         @Override
    404         public void onActivityStopped(MonitoredActivity activity) {
    405             mDialog.hide();
    406         }
    407 
    408         @Override
    409         public void onActivityStarted(MonitoredActivity activity) {
    410             mDialog.show();
    411         }
    412     }
    413 
    414     public static void startBackgroundJob(MonitoredActivity activity,
    415             String title, String message, Runnable job, Handler handler) {
    416         // Make the progress dialog uncancelable, so that we can gurantee
    417         // the thread will be done before the activity getting destroyed.
    418         ProgressDialog dialog = ProgressDialog.show(
    419                 activity, title, message, true, false);
    420         new Thread(new BackgroundJob(activity, job, dialog, handler)).start();
    421     }
    422 
    423     // Returns an intent which is used for "set as" menu items.
    424     public static Intent createSetAsIntent(IImage image) {
    425         Uri u = image.fullSizeImageUri();
    426         Intent intent = new Intent(Intent.ACTION_ATTACH_DATA);
    427         intent.setDataAndType(u, image.getMimeType());
    428         intent.putExtra("mimeType", image.getMimeType());
    429         return intent;
    430     }
    431 
    432     // Returns Options that set the puregeable flag for Bitmap decode.
    433     public static BitmapFactory.Options createNativeAllocOptions() {
    434         return new BitmapFactory.Options();
    435     }
    436 }
    437