OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / packages / apps / Gallery / src / com / android / camera / Util.java
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         BitmapFactory.Options options = new BitmapFactory.Options();
435         options.inNativeAlloc = true;
436         return options;
437     }
438 }