/*
* Copyright (C) 2015 Jorge Ruesga
* Copyright (c) 2010, Sony Ericsson Mobile Communication AB. All rights reserved.
*
* 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.ruesga.android.wallpapers.photophase.utils;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.media.ExifInterface;
import com.ruesga.android.wallpapers.photophase.AndroidHelper;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
* A helper class for deal with Bitmaps
*/
public class BitmapUtils {
/**
* ScalingLogic defines how scaling should be carried out if source and
* destination image has different aspect ratio.
*
* CROP: Scales the image the minimum amount while making sure that at least
* one of the two dimensions fit inside the requested destination area.
* Parts of the source image will be cropped to realize this.
*
* FIT: Scales the image the minimum amount while making sure both
* dimensions fit inside the requested destination area. The resulting
* destination dimensions might be adjusted to a smaller size than
* requested.
*/
public enum ScalingLogic {
CROP, FIT
}
/**
* Method that decodes a bitmap
*
* @param bitmap The bitmap buffer to decode
* @return Bitmap The decoded bitmap
*/
@SuppressWarnings("deprecation")
public static Bitmap decodeBitmap(InputStream bitmap) {
final Options options = new Options();
options.inScaled = false;
options.inDither = true;
options.inPreferQualityOverSpeed = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
return BitmapFactory.decodeStream(bitmap, null, options);
}
/**
* Method that decodes a bitmap
*
* @param file The bitmap file to decode
* @param dstWidth The request width
* @param dstHeight The request height
* @return Bitmap The decoded bitmap
*/
@SuppressWarnings("deprecation")
public static Bitmap decodeBitmap(File file, int dstWidth, int dstHeight) {
// First decode with inJustDecodeBounds=true to check dimensions
final Options options = new Options();
options.inScaled = false;
options.inDither = true;
options.inPreferQualityOverSpeed = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// Deprecated, but still valid for KitKat and lower apis
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// Decode the bitmap with inSampleSize set
options.inSampleSize = calculateBitmapRatio(options, dstWidth, dstHeight);
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (bitmap == null) {
return null;
}
// Test if the bitmap has exif format, and decode properly
Bitmap out = decodeExifBitmap(file, bitmap);
if (!out.equals(bitmap)) {
bitmap.recycle();
}
return out;
}
public static Rect getBitmapDimensions(File file) {
// First decode with inJustDecodeBounds=true to check dimensions
final Options options = new Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
if (options.outWidth != -1 && options.outHeight != -1) {
return new Rect(0, 0, options.outWidth, options.outHeight);
}
// Not an image
return null;
}
/**
* Utility function for decoding an image file. The decoded bitmap will
* be optimized for further scaling to the requested destination dimensions
* and scaling logic.
*
* @param file The file to load
* @param dstWidth Width of destination area
* @param dstHeight Height of destination area
* @return Decoded bitmap
*/
@SuppressWarnings("deprecation")
public static Bitmap createUnscaledBitmap(File file, int dstWidth, int dstHeight) {
// Get the dimensions of the bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
options.inScaled = false;
options.inDither = true;
options.inPreferQualityOverSpeed = false;
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
// Deprecated, but still valid for KitKat and lower apis
options.inPurgeable = true;
options.inInputShareable = true;
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
// Determine how much to scale down the image
int photoWidth = options.outWidth;
int photoHeight = options.outHeight;
// Decode the image file into a Bitmap sized to fill the view
options.inJustDecodeBounds = false;
options.inSampleSize = Math.max(Math.round(photoWidth / dstWidth),
Math.round(photoHeight / dstHeight));
return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
}
/**
* Utility function for creating a scaled version of an existing bitmap
*
* @param unscaledBitmap Bitmap to scale
* @param dstWidth Wanted width of destination bitmap
* @param dstHeight Wanted height of destination bitmap
* @param scalingLogic Logic to use to avoid image stretching
* @return New scaled bitmap object
*/
public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
if (unscaledBitmap.getWidth() == dstWidth && unscaledBitmap.getHeight() == dstHeight) {
return unscaledBitmap;
}
Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
dstWidth, dstHeight, scalingLogic);
Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(),
dstWidth, dstHeight, scalingLogic);
Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(scaledBitmap);
canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));
return scaledBitmap;
}
/**
* Method that decodes an Exif bitmap
*
* @param file The file to decode
* @param src The bitmap reference
* @return Bitmap The decoded bitmap
*/
private static Bitmap decodeExifBitmap(File file, Bitmap src) {
try {
// Try to load the bitmap as a bitmap file
ExifInterface exif = new ExifInterface(file.getAbsolutePath());
int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, 1);
if (orientation == ExifInterface.ORIENTATION_UNDEFINED
|| orientation == ExifInterface.ORIENTATION_NORMAL) {
return src;
}
Matrix matrix = new Matrix();
if (orientation == ExifInterface.ORIENTATION_ROTATE_90) {
matrix.postRotate(90);
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_180) {
matrix.postRotate(180);
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_270) {
matrix.postRotate(270);
} else if (orientation == ExifInterface.ORIENTATION_FLIP_HORIZONTAL) {
matrix.setScale(-1, 1);
matrix.postTranslate(src.getWidth(), 0);
} else if (orientation == ExifInterface.ORIENTATION_FLIP_VERTICAL) {
matrix.setScale(1, -1);
matrix.postTranslate(0, src.getHeight());
}
// Rotate the bitmap
return Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), matrix, true);
} catch (IOException e) {
// Ignore
}
return src;
}
/**
* Method that calculate the bitmap size prior to decode
*
* @param options The bitmap factory options
* @param reqWidth The request width
* @param reqHeight The request height
* @return int The picture ratio
*/
private static int calculateBitmapRatio(Options options, int reqWidth, int reqHeight) {
// Raw height and width of image
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
// Calculate the largest inSampleSize value that is a power of 2 and keeps both
// height and width larger than the requested height and width.
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
/**
* Calculates source rectangle for scaling bitmap
*
* @param srcWidth Width of source image
* @param srcHeight Height of source image
* @param dstWidth Width of destination area
* @param dstHeight Height of destination area
* @param scalingLogic Logic to use to avoid image stretching
* @return Optimal source rectangle
*/
public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
if (scalingLogic == ScalingLogic.CROP) {
final float srcAspect = (float)srcWidth / (float)srcHeight;
final float dstAspect = (float)dstWidth / (float)dstHeight;
if (srcAspect > dstAspect) {
final int srcRectWidth = (int)(srcHeight * dstAspect);
final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
} else {
final int srcRectHeight = (int)(srcWidth / dstAspect);
final int scrRectTop = (srcHeight - srcRectHeight) / 2;
return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
}
} else {
return new Rect(0, 0, srcWidth, srcHeight);
}
}
/**
* Calculates destination rectangle for scaling bitmap
*
* @param srcWidth Width of source image
* @param srcHeight Height of source image
* @param dstWidth Width of destination area
* @param dstHeight Height of destination area
* @param scalingLogic Logic to use to avoid image stretching
* @return Optimal destination rectangle
*/
public static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight,
ScalingLogic scalingLogic) {
if (scalingLogic == ScalingLogic.FIT) {
final float srcAspect = (float)srcWidth / (float)srcHeight;
final float dstAspect = (float)dstWidth / (float)dstHeight;
if (srcAspect > dstAspect) {
return new Rect(0, 0, dstWidth, (int)(dstWidth / srcAspect));
} else {
return new Rect(0, 0, (int)(dstHeight * srcAspect), dstHeight);
}
} else {
return new Rect(0, 0, dstWidth, dstHeight);
}
}
/**
* Check if the bitmap is a power of two
*
* @param bitmap The bitmap to check
* @return boolean if the size is power of two
*/
public static boolean isPowerOfTwo(Bitmap bitmap){
return isPowerOfTwo(bitmap.getWidth(), bitmap.getHeight());
}
/**
* Check if the width and height are power of two
*
* @param w Width
* @param h Height
* @return boolean if the size is power of two
*/
public static boolean isPowerOfTwo(int w, int h){
return isPowerOfTwo(w) && isPowerOfTwo(h);
}
private static boolean isPowerOfTwo(int x) {
while (((x % 2) == 0) && x > 1) {
x /= 2;
}
return (x == 1);
}
/**
* Return the nearest upper power of two size
*
* @param v The original value
* @return The nearest upper power of two
*/
public static int calculateUpperPowerOfTwo(int v) {
v--;
v |= v >>> 1;
v |= v >>> 2;
v |= v >>> 4;
v |= v >>> 8;
v |= v >>> 16;
v++;
return v;
}
public static void adjustRectToMinimumSize(Rect r, int size) {
int w = r.width();
int h = r.height();
if (w > size || h > size) {
if (w == h && w > size) {
r.right = r.bottom = size;
} else if (w < h && w > size) {
r.right = w * size / h;
r.bottom = size;
} else {
r.bottom = h * size / w;
r.right = size;
}
}
}
public static int calculateMaxAvailableSize(Context context) {
if (AndroidHelper.isJellyBeanOrGreater()) {
ActivityManager.MemoryInfo mi = new ActivityManager.MemoryInfo();
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
activityManager.getMemoryInfo(mi);
return (int)((mi.totalMem / 1073741824) * 1024);
}
// The minimum for all android devices
return 1024;
}
public static int byteSizeOf(Bitmap bitmap) {
if (AndroidHelper.isKitKatOrGreater()) {
return bitmap.getAllocationByteCount();
}
return bitmap.getByteCount();
}
}