Moving some files and methods around

> Removing android.util package
> Moving static methods and classes out of WallpaperCropActivity
> Removing some unused utility methods

Change-Id: I252a0655ddce195189b6b3f0bf92970e5808c9d7
This commit is contained in:
Sunny Goyal
2015-02-12 11:19:39 -08:00
parent 14334bda03
commit 06a4d3f1af
10 changed files with 564 additions and 926 deletions
@@ -16,83 +16,32 @@
package com.android.gallery3d.common;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.util.FloatMath;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.view.WindowManager;
import java.io.ByteArrayOutputStream;
import com.android.gallery3d.exif.ExifInterface;
import com.android.launcher3.WallpaperCropActivity;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
public class BitmapUtils {
private static final String TAG = "BitmapUtils";
private static final int DEFAULT_JPEG_QUALITY = 90;
public static final int UNCONSTRAINED = -1;
private BitmapUtils(){}
/*
* 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 UNCONSTRAINED,
* which indicates no care of the corresponding constraint.
* The functions prefers returning a sample size that
* generates a smaller bitmap, unless minSideLength = 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(int width, int height,
int minSideLength, int maxNumOfPixels) {
int initialSize = computeInitialSampleSize(
width, height, minSideLength, maxNumOfPixels);
return initialSize <= 8
? Utils.nextPowerOf2(initialSize)
: (initialSize + 7) / 8 * 8;
}
private static int computeInitialSampleSize(int w, int h,
int minSideLength, int maxNumOfPixels) {
if (maxNumOfPixels == UNCONSTRAINED
&& minSideLength == UNCONSTRAINED) return 1;
int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
(int) FloatMath.ceil(FloatMath.sqrt((float) (w * h) / maxNumOfPixels));
if (minSideLength == UNCONSTRAINED) {
return lowerBound;
} else {
int sampleSize = Math.min(w / minSideLength, h / minSideLength);
return Math.max(sampleSize, lowerBound);
}
}
// This computes a sample size which makes the longer side at least
// minSideLength long. If that's not possible, return 1.
public static int computeSampleSizeLarger(int w, int h,
int minSideLength) {
int initialSize = Math.max(w / minSideLength, h / minSideLength);
if (initialSize <= 1) return 1;
return initialSize <= 8
? Utils.prevPowerOf2(initialSize)
: initialSize / 8 * 8;
}
// Find the min x that 1 / x >= scale
public static int computeSampleSizeLarger(float scale) {
int initialSize = (int) FloatMath.floor(1f / scale);
int initialSize = (int) Math.floor(1f / scale);
if (initialSize <= 1) return 1;
return initialSize <= 8
@@ -100,15 +49,6 @@ public class BitmapUtils {
: initialSize / 8 * 8;
}
// Find the max x that 1 / x <= scale.
public static int computeSampleSize(float scale) {
Utils.assertTrue(scale > 0);
int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
return initialSize <= 8
? Utils.nextPowerOf2(initialSize)
: (initialSize + 7) / 8 * 8;
}
public static Bitmap resizeBitmapByScale(
Bitmap bitmap, float scale, boolean recycle) {
int width = Math.round(bitmap.getWidth() * scale);
@@ -132,77 +72,104 @@ public class BitmapUtils {
return config;
}
public static Bitmap resizeDownBySideLength(
Bitmap bitmap, int maxLength, boolean recycle) {
int srcWidth = bitmap.getWidth();
int srcHeight = bitmap.getHeight();
float scale = Math.min(
(float) maxLength / srcWidth, (float) maxLength / srcHeight);
if (scale >= 1.0f) return bitmap;
return resizeBitmapByScale(bitmap, scale, recycle);
/**
* As a ratio of screen height, the total distance we want the parallax effect to span
* horizontally
*/
public static float wallpaperTravelToScreenWidthRatio(int width, int height) {
float aspectRatio = width / (float) height;
// At an aspect ratio of 16/10, the wallpaper parallax effect should span 1.5 * screen width
// At an aspect ratio of 10/16, the wallpaper parallax effect should span 1.2 * screen width
// We will use these two data points to extrapolate how much the wallpaper parallax effect
// to span (ie travel) at any aspect ratio:
final float ASPECT_RATIO_LANDSCAPE = 16/10f;
final float ASPECT_RATIO_PORTRAIT = 10/16f;
final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE = 1.5f;
final float WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT = 1.2f;
// To find out the desired width at different aspect ratios, we use the following two
// formulas, where the coefficient on x is the aspect ratio (width/height):
// (16/10)x + y = 1.5
// (10/16)x + y = 1.2
// We solve for x and y and end up with a final formula:
final float x =
(WALLPAPER_WIDTH_TO_SCREEN_RATIO_LANDSCAPE - WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT) /
(ASPECT_RATIO_LANDSCAPE - ASPECT_RATIO_PORTRAIT);
final float y = WALLPAPER_WIDTH_TO_SCREEN_RATIO_PORTRAIT - x * ASPECT_RATIO_PORTRAIT;
return x * aspectRatio + y;
}
public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
int w = bitmap.getWidth();
int h = bitmap.getHeight();
if (w == size && h == size) return bitmap;
private static Point sDefaultWallpaperSize;
// scale the image so that the shorter side equals to the target;
// the longer side will be center-cropped.
float scale = (float) size / Math.min(w, h);
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static Point getDefaultWallpaperSize(Resources res, WindowManager windowManager) {
if (sDefaultWallpaperSize == null) {
Point minDims = new Point();
Point maxDims = new Point();
windowManager.getDefaultDisplay().getCurrentSizeRange(minDims, maxDims);
Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
int width = Math.round(scale * bitmap.getWidth());
int height = Math.round(scale * bitmap.getHeight());
Canvas canvas = new Canvas(target);
canvas.translate((size - width) / 2f, (size - height) / 2f);
canvas.scale(scale, scale);
Paint paint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG);
canvas.drawBitmap(bitmap, 0, 0, paint);
if (recycle) bitmap.recycle();
return target;
}
int maxDim = Math.max(maxDims.x, maxDims.y);
int minDim = Math.max(minDims.x, minDims.y);
public static void recycleSilently(Bitmap bitmap) {
if (bitmap == null) return;
try {
bitmap.recycle();
} catch (Throwable t) {
Log.w(TAG, "unable recycle bitmap", t);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
Point realSize = new Point();
windowManager.getDefaultDisplay().getRealSize(realSize);
maxDim = Math.max(realSize.x, realSize.y);
minDim = Math.min(realSize.x, realSize.y);
}
// We need to ensure that there is enough extra space in the wallpaper
// for the intended parallax effects
final int defaultWidth, defaultHeight;
if (res.getConfiguration().smallestScreenWidthDp >= 720) {
defaultWidth = (int) (maxDim * wallpaperTravelToScreenWidthRatio(maxDim, minDim));
defaultHeight = maxDim;
} else {
defaultWidth = Math.max((int) (minDim * WallpaperCropActivity.WALLPAPER_SCREENS_SPAN), maxDim);
defaultHeight = maxDim;
}
sDefaultWallpaperSize = new Point(defaultWidth, defaultHeight);
}
return sDefaultWallpaperSize;
}
public static Bitmap rotateBitmap(Bitmap source, int rotation, boolean recycle) {
if (rotation == 0) return source;
int w = source.getWidth();
int h = source.getHeight();
Matrix m = new Matrix();
m.postRotate(rotation);
Bitmap bitmap = Bitmap.createBitmap(source, 0, 0, w, h, m, true);
if (recycle) source.recycle();
return bitmap;
public static int getRotationFromExif(Context context, Uri uri) {
return BitmapUtils.getRotationFromExifHelper(null, 0, context, uri);
}
public static byte[] compressToBytes(Bitmap bitmap) {
return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
public static int getRotationFromExif(Resources res, int resId) {
return BitmapUtils.getRotationFromExifHelper(res, resId, null, null);
}
public static byte[] compressToBytes(Bitmap bitmap, int quality) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
bitmap.compress(CompressFormat.JPEG, quality, baos);
return baos.toByteArray();
}
public static boolean isSupportedByRegionDecoder(String mimeType) {
if (mimeType == null) return false;
mimeType = mimeType.toLowerCase();
return mimeType.startsWith("image/") &&
(!mimeType.equals("image/gif") && !mimeType.endsWith("bmp"));
}
public static boolean isRotationSupported(String mimeType) {
if (mimeType == null) return false;
mimeType = mimeType.toLowerCase();
return mimeType.equals("image/jpeg");
private static int getRotationFromExifHelper(Resources res, int resId, Context context, Uri uri) {
ExifInterface ei = new ExifInterface();
InputStream is = null;
BufferedInputStream bis = null;
try {
if (uri != null) {
is = context.getContentResolver().openInputStream(uri);
bis = new BufferedInputStream(is);
ei.readExif(bis);
} else {
is = res.openRawResource(resId);
bis = new BufferedInputStream(is);
ei.readExif(bis);
}
Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION);
if (ori != null) {
return ExifInterface.getRotationForOrientationValue(ori.shortValue());
}
} catch (IOException e) {
Log.w(TAG, "Getting exif data failed", e);
} catch (NullPointerException e) {
// Sometimes the ExifInterface has an internal NPE if Exif data isn't valid
Log.w(TAG, "Getting exif data failed", e);
} finally {
Utils.closeSilently(bis);
Utils.closeSilently(is);
}
return 0;
}
}