2 * Copyright (C) 2009 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.camera;
19 import com.android.gallery.R;
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;
41 import com.android.camera.gallery.IImage;
43 import java.io.Closeable;
44 import java.io.FileDescriptor;
45 import java.io.IOException;
48 * Collection of utility functions used in this package.
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;
57 private static OnClickListener sNullOnClickListener;
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();
68 (float) b.getWidth() / 2, (float) b.getHeight() / 2);
70 Bitmap b2 = Bitmap.createBitmap(
71 b, 0, 0, b.getWidth(), b.getHeight(), m, true);
76 } catch (OutOfMemoryError ex) {
77 // We have no memory to rotate. Return the original bitmap.
84 * Compute the sample size as a function of minSideLength
86 * minSideLength is used to specify that minimal width or height of a
88 * maxNumOfPixels is used to specify the maximal size in pixels that is
89 * tolerable in terms of memory usage.
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.
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.
102 public static int computeSampleSize(BitmapFactory.Options options,
103 int minSideLength, int maxNumOfPixels) {
104 int initialSize = computeInitialSampleSize(options, minSideLength,
108 if (initialSize <= 8) {
110 while (roundedSize < initialSize) {
114 roundedSize = (initialSize + 7) / 8 * 8;
120 private static int computeInitialSampleSize(BitmapFactory.Options options,
121 int minSideLength, int maxNumOfPixels) {
122 double w = options.outWidth;
123 double h = options.outHeight;
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));
131 if (upperBound < lowerBound) {
132 // return the larger one when there is no overlapping zone.
136 if ((maxNumOfPixels == IImage.UNCONSTRAINED) &&
137 (minSideLength == IImage.UNCONSTRAINED)) {
139 } else if (minSideLength == IImage.UNCONSTRAINED) {
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;
150 public static Bitmap transform(Matrix scaler,
156 int deltaX = source.getWidth() - targetWidth;
157 int deltaY = source.getHeight() - targetHeight;
158 if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
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.
165 Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
166 Bitmap.Config.ARGB_8888);
167 Canvas c = new Canvas(b2);
169 int deltaXHalf = Math.max(0, deltaX / 2);
170 int deltaYHalf = Math.max(0, deltaY / 2);
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;
182 targetHeight - dstY);
183 c.drawBitmap(source, src, dst, null);
189 float bitmapWidthF = source.getWidth();
190 float bitmapHeightF = source.getHeight();
192 float bitmapAspect = bitmapWidthF / bitmapHeightF;
193 float viewAspect = (float) targetWidth / targetHeight;
195 if (bitmapAspect > viewAspect) {
196 float scale = targetHeight / bitmapHeightF;
197 if (scale < .9F || scale > 1F) {
198 scaler.setScale(scale, scale);
203 float scale = targetWidth / bitmapWidthF;
204 if (scale < .9F || scale > 1F) {
205 scaler.setScale(scale, scale);
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);
220 if (recycle && b1 != source) {
224 int dx1 = Math.max(0, b1.getWidth() - targetWidth);
225 int dy1 = Math.max(0, b1.getHeight() - targetHeight);
227 Bitmap b2 = Bitmap.createBitmap(
235 if (recycle || b1 != source) {
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)) {
252 public static void closeSilently(Closeable c) {
253 if (c == null) return;
256 } catch (Throwable t) {
261 public static void closeSilently(ParcelFileDescriptor c) {
262 if (c == null) return;
265 } catch (Throwable t) {
271 * Make a bitmap from a given Uri.
275 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
276 Uri uri, ContentResolver cr, boolean useNative) {
277 ParcelFileDescriptor input = null;
279 input = cr.openFileDescriptor(uri, "r");
280 BitmapFactory.Options options = null;
282 options = createNativeAllocOptions();
284 return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, input,
286 } catch (IOException ex) {
289 closeSilently(input);
293 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
294 ParcelFileDescriptor pfd, boolean useNative) {
295 BitmapFactory.Options options = null;
297 options = createNativeAllocOptions();
299 return makeBitmap(minSideLength, maxNumOfPixels, null, null, pfd,
303 public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
304 Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
305 BitmapFactory.Options options) {
307 if (pfd == null) pfd = makeInputStream(uri, cr);
308 if (pfd == null) return null;
309 if (options == null) options = new BitmapFactory.Options();
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) {
318 options.inSampleSize = computeSampleSize(
319 options, minSideLength, maxNumOfPixels);
320 options.inJustDecodeBounds = false;
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);
333 private static ParcelFileDescriptor makeInputStream(
334 Uri uri, ContentResolver cr) {
336 return cr.openFileDescriptor(uri, "r");
337 } catch (IOException ex) {
342 public static synchronized OnClickListener getNullOnClickListener() {
343 if (sNullOnClickListener == null) {
344 sNullOnClickListener = new OnClickListener() {
345 public void onClick(View v) {
349 return sNullOnClickListener;
352 public static void Assert(boolean cond) {
354 throw new AssertionError();
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);
363 private static class BackgroundJob
364 extends MonitoredActivity.LifeCycleAdapter implements Runnable {
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() {
372 mActivity.removeLifeCycleListener(BackgroundJob.this);
373 if (mDialog.getWindow() != null) mDialog.dismiss();
377 public BackgroundJob(MonitoredActivity activity, Runnable job,
378 ProgressDialog dialog, Handler handler) {
379 mActivity = activity;
382 mActivity.addLifeCycleListener(this);
390 mHandler.post(mCleanupRunner);
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);
404 public void onActivityStopped(MonitoredActivity activity) {
409 public void onActivityStarted(MonitoredActivity activity) {
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();
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());
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;