/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.lightbox.android.camera; import java.io.Closeable; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.hardware.Camera; import android.os.Build; import android.util.FloatMath; import android.util.Log; import android.view.Surface; import android.view.View; import android.view.animation.Animation; import android.view.animation.TranslateAnimation; import com.lightbox.android.camera.gallery.IImage; /** * Collection of utility functions used in this package. */ public class Util { private static final String TAG = "Util"; public static final int DIRECTION_LEFT = 0; public static final int DIRECTION_RIGHT = 1; public static final int DIRECTION_UP = 2; public static final int DIRECTION_DOWN = 3; public static final String REVIEW_ACTION = "com.cooliris.media.action.REVIEW"; private Util() { } // Rotates the bitmap by the specified degree. // If a new bitmap is created, the original bitmap is recycled. public static Bitmap rotate(Bitmap b, int degrees) { return rotateAndMirror(b, degrees, false); } // Rotates and/or mirrors the bitmap. If a new bitmap is created, the // original bitmap is recycled. public static Bitmap rotateAndMirror(Bitmap b, int degrees, boolean mirror) { if ((degrees != 0 || mirror) && b != null) { Matrix m = new Matrix(); m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2); if (mirror) { m.postScale(-1, 1); degrees = (degrees + 360) % 360; if (degrees == 0 || degrees == 180) { m.postTranslate((float) b.getWidth(), 0); } else if (degrees == 90 || degrees == 270) { m.postTranslate((float) b.getHeight(), 0); } else { throw new IllegalArgumentException("Invalid degrees=" + degrees); } } try { Bitmap b2 = Bitmap.createBitmap( b, 0, 0, b.getWidth(), b.getHeight(), m, true); if (b != b2) { b.recycle(); b = b2; } } catch (OutOfMemoryError ex) { // We have no memory to rotate. Return the original bitmap. } } return b; } public static Bitmap flipHorizontally(Bitmap b) { Matrix m = new Matrix(); m.postScale(-1, 1); try { Bitmap b2 = Bitmap.createBitmap( b, 0, 0, b.getWidth(), b.getHeight(), m, true); if (b != b2) { b.recycle(); b = b2; } } catch (OutOfMemoryError ex) { // We have no memory to rotate. Return the original bitmap. } return b; } /* * Compute the sample size as a function of minSideLength * and maxNumOfPixels. * minSideLength is used to specify that minimal width or height of a * bitmap. * maxNumOfPixels is used to specify the maximal size in pixels that is * tolerable in terms of memory usage. * * The function returns a sample size based on the constraints. * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED, * which indicates no care of the corresponding constraint. * The functions prefers returning a sample size that * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED. * * Also, the function rounds up the sample size to a power of 2 or multiple * of 8 because BitmapFactory only honors sample size this way. * For example, BitmapFactory downsamples an image by 2 even though the * request is 3. So we round up the sample size to avoid OOM. */ public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels); int roundedSize; if (initialSize <= 8) { roundedSize = 1; while (roundedSize < initialSize) { roundedSize <<= 1; } } else { roundedSize = (initialSize + 7) / 8 * 8; } return roundedSize; } private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) { double w = options.outWidth; double h = options.outHeight; int lowerBound = (maxNumOfPixels == IImage.UNCONSTRAINED) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels)); int upperBound = (minSideLength == IImage.UNCONSTRAINED) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength)); if (upperBound < lowerBound) { // return the larger one when there is no overlapping zone. return lowerBound; } if ((maxNumOfPixels == IImage.UNCONSTRAINED) && (minSideLength == IImage.UNCONSTRAINED)) { return 1; } else if (minSideLength == IImage.UNCONSTRAINED) { return lowerBound; } else { return upperBound; } } public static <T> int indexOf(T [] array, T s) { for (int i = 0; i < array.length; i++) { if (array[i].equals(s)) { return i; } } return -1; } public static void closeSilently(Closeable c) { if (c == null) return; try { c.close(); } catch (Throwable t) { // do nothing } } public static Bitmap makeBitmap(byte[] jpegData, int maxNumOfPixels) { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); if (options.mCancel || options.outWidth == -1 || options.outHeight == -1) { return null; } options.inSampleSize = computeSampleSize( options, IImage.UNCONSTRAINED, maxNumOfPixels); options.inJustDecodeBounds = false; options.inDither = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; return BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length, options); } catch (OutOfMemoryError ex) { Log.e(TAG, "Got oom exception ", ex); return null; } } public static void Assert(boolean cond) { if (!cond) { throw new AssertionError(); } } public static void showFatalErrorAndFinish( final Activity activity, String title, String message) { DialogInterface.OnClickListener buttonListener = new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { activity.finish(); } }; new AlertDialog.Builder(activity) .setCancelable(false) .setIcon(android.R.drawable.ic_dialog_alert) .setTitle(title) .setMessage(message) .setNeutralButton(R.string.details_ok, buttonListener) .show(); } public static Animation slideOut(View view, int to) { view.setVisibility(View.INVISIBLE); Animation anim; switch (to) { case DIRECTION_LEFT: anim = new TranslateAnimation(0, -view.getWidth(), 0, 0); break; case DIRECTION_RIGHT: anim = new TranslateAnimation(0, view.getWidth(), 0, 0); break; case DIRECTION_UP: anim = new TranslateAnimation(0, 0, 0, -view.getHeight()); break; case DIRECTION_DOWN: anim = new TranslateAnimation(0, 0, 0, view.getHeight()); break; default: throw new IllegalArgumentException(Integer.toString(to)); } anim.setDuration(500); view.startAnimation(anim); return anim; } public static Animation slideIn(View view, int from) { view.setVisibility(View.VISIBLE); Animation anim; switch (from) { case DIRECTION_LEFT: anim = new TranslateAnimation(-view.getWidth(), 0, 0, 0); break; case DIRECTION_RIGHT: anim = new TranslateAnimation(view.getWidth(), 0, 0, 0); break; case DIRECTION_UP: anim = new TranslateAnimation(0, 0, -view.getHeight(), 0); break; case DIRECTION_DOWN: anim = new TranslateAnimation(0, 0, view.getHeight(), 0); break; default: throw new IllegalArgumentException(Integer.toString(from)); } anim.setDuration(500); view.startAnimation(anim); return anim; } public static <T> T checkNotNull(T object) { if (object == null) throw new NullPointerException(); return object; } public static boolean equals(Object a, Object b) { return (a == b) || (a == null ? false : a.equals(b)); } public static boolean isPowerOf2(int n) { return (n & -n) == n; } public static int nextPowerOf2(int n) { n -= 1; n |= n >>> 16; n |= n >>> 8; n |= n >>> 4; n |= n >>> 2; n |= n >>> 1; return n + 1; } public static float distance(float x, float y, float sx, float sy) { float dx = x - sx; float dy = y - sy; return (float) FloatMath.sqrt(dx * dx + dy * dy); } public static int clamp(int x, int min, int max) { if (x > max) return max; if (x < min) return min; return x; } public static int getDisplayRotation(Activity activity) { int rotation = 0; if (Build.VERSION.SDK_INT >= 0x00000008 /*Build.VERSION_CODES.FROYO*/) { rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); } else { rotation = activity.getWindowManager().getDefaultDisplay().getOrientation(); } switch (rotation) { case Surface.ROTATION_0: return 0; case Surface.ROTATION_90: return 90; case Surface.ROTATION_180: return 180; case Surface.ROTATION_270: return 270; } return 0; } public static void setCameraDisplayOrientation(Activity activity, int cameraId, Camera camera) { // See android.hardware.Camera.setCameraDisplayOrientation for // documentation. Camera.CameraInfo info = new Camera.CameraInfo(); Camera.getCameraInfo(cameraId, info); int degrees = getDisplayRotation(activity); int result; if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { result = (info.orientation + degrees) % 360; result = (360 - result) % 360; // compensate the mirror } else { // back-facing result = (info.orientation - degrees + 360) % 360; } camera.setDisplayOrientation(result); } // decode Y, U, and V values on the YUV 420 buffer described as YCbCr_422_SP // by Android public static void decodeYUV(int[] out, byte[] fg, int width, int height) throws NullPointerException, IllegalArgumentException { int sz = width * height; if (out == null) throw new NullPointerException("buffer out is null"); if (out.length < sz) throw new IllegalArgumentException("buffer out size " + out.length + " < minimum " + sz); if (fg == null) throw new NullPointerException("buffer 'fg' is null"); if (fg.length < sz) throw new IllegalArgumentException("buffer fg size " + fg.length + " < minimum " + sz * 3 / 2); int i, j; int Y, Cr = 0, Cb = 0; for (j = 0; j < height; j++) { int pixPtr = j * width; final int jDiv2 = j >> 1; for (i = 0; i < width; i++) { Y = fg[pixPtr]; if (Y < 0) Y += 255; if ((i & 0x1) != 1) { final int cOff = sz + jDiv2 * width + (i >> 1) * 2; Cb = fg[cOff]; if (Cb < 0) Cb += 127; else Cb -= 128; Cr = fg[cOff + 1]; if (Cr < 0) Cr += 127; else Cr -= 128; } int R = Y + Cr + (Cr >> 2) + (Cr >> 3) + (Cr >> 5); if (R < 0) R = 0; else if (R > 255) R = 255; int G = Y - (Cb >> 2) + (Cb >> 4) + (Cb >> 5) - (Cr >> 1) + (Cr >> 3) + (Cr >> 4) + (Cr >> 5); if (G < 0) G = 0; else if (G > 255) G = 255; int B = Y + Cb + (Cb >> 1) + (Cb >> 2) + (Cb >> 6); if (B < 0) B = 0; else if (B > 255) B = 255; out[pixPtr++] = 0xff000000 + (B << 16) + (G << 8) + R; } } } }