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