package wb.android.image;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.support.annotation.NonNull;
import android.util.Log;
import android.view.View;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import wb.android.storage.StorageManager;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import wb.android.storage.StorageManager;
public class ImageUtils {
private static final String TAG = ImageUtils.class.getSimpleName();
private ImageUtils() {
}
/**
* Converts an image to grayscale
*
* @param bitmap - the {@link Bitmap} to convert to grayscale. The initial copy of which
* will be recycled via the {@link Bitmap#recycle()} method
* @return - a grayscale {@link Bitmap} instance with {@link Bitmap.Config#RGB_565}. The initial
* bitmap instance will be returned if an {@link java.lang.OutOfMemoryError} occurs
*/
public static Bitmap convertToGrayScale(Bitmap bitmap) {
return convertToGrayScale(bitmap, Bitmap.Config.RGB_565);
}
/**
* Converts an image to grayscale
*
* @param bitmap - the {@link Bitmap} to convert to grayscale. The initial copy of which
* will be recycled via the {@link Bitmap#recycle()} method
* @param config - the {@link Bitmap.Config} to create the new image with
* @return - a grayscale {@link Bitmap} instance. The initial
* bitmap instance will be returned if an {@link java.lang.OutOfMemoryError} occurs
*/
public static Bitmap convertToGrayScale(Bitmap bitmap, Bitmap.Config config) {
if (bitmap == null || config == null) {
return bitmap;
}
try {
Bitmap bmpGrayscale = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), config);
Canvas c = new Canvas(bmpGrayscale);
Paint paint = new Paint();
ColorMatrix cm = new ColorMatrix();
cm.setSaturation(0);
ColorMatrixColorFilter f = new ColorMatrixColorFilter(cm);
paint.setColorFilter(f);
c.drawBitmap(bitmap, 0, 0, paint);
if (bitmap != bmpGrayscale) {
bitmap.recycle();
}
return bmpGrayscale;
} catch (OutOfMemoryError e) {
return bitmap;
}
}
/**
* Rotates the {@link Bitmap} to a given orientation
*
* @param bitmap - bitmap to rotate
* @param exifOrientation - the orientation to move to. This should not be a direct
* value but rather one that is gathered via the {@link android.media.ExifInterface#getAttributeInt(String, int)}
* value with the key {@link android.media.ExifInterface#TAG_ORIENTATION}.
* @return - the rotated version of the original {@link Bitmap}. Please note that the initial one will
* be recycled via the {@link Bitmap#recycle()} method if a rotation occurs. The original bitmap will be returned if an {@link java.lang.OutOfMemoryError} occurs
* or it is already in the proper rotation
*/
public static Bitmap rotateBitmap(Bitmap bitmap, int exifOrientation) {
Matrix matrix = new Matrix();
switch (exifOrientation) {
case ExifInterface.ORIENTATION_NORMAL:
return bitmap;
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL:
matrix.setScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_180:
matrix.setRotate(180);
break;
case ExifInterface.ORIENTATION_FLIP_VERTICAL:
matrix.setRotate(180);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_TRANSPOSE:
matrix.setRotate(90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_90:
matrix.setRotate(90);
break;
case ExifInterface.ORIENTATION_TRANSVERSE:
matrix.setRotate(-90);
matrix.postScale(-1, 1);
break;
case ExifInterface.ORIENTATION_ROTATE_270:
matrix.setRotate(-90);
break;
default:
return bitmap;
}
try {
Bitmap bmRotated = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
bitmap.recycle();
return bmRotated;
} catch (OutOfMemoryError e) {
Log.e(TAG, e.toString());
return bitmap;
}
}
/**
* When creating a JPG image from a PNG image, Android automatically makes the entire background
* black if no background was defined (ie just alpha). This method allows us to force any bitmap
* to have a white background, so this oddity will not appear when converting to a format without
* alpha support (eg from PNG -> JPG).
*
* @param source the source {@link Bitmap}, which may have an alpha/transparent background. This
* bitmap will be recycled at the conclusion of this operation
* @return the resultant {@link Bitmap}, which will have a white background
*/
@NonNull
public static Bitmap applyWhiteBackground(@NonNull Bitmap source) {
try {
final Bitmap whiteBackgroundBitmap = Bitmap.createBitmap(source.getWidth(), source.getHeight(), source.getConfig());
final Canvas canvas = new Canvas(whiteBackgroundBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(source, 0, 0, null);
return whiteBackgroundBitmap;
} finally {
source.recycle();
}
}
/**
* Write a compressed version of the bitmap to the specified codec (eg from PNG to JPG). If
* successful, the source bitmap will be recycled and the resultant one will be returned in the
* desired format.
*
* <p>
* Note: not all Formats support all bitmap configs directly, so it is possible that the
* returned bitmap from BitmapFactory could be in a different bitdepth, and/or may have lost
* per-pixel alpha (e.g. JPEG only supports opaque pixels).
* </p>
*
* @param source the source {@link Bitmap} to use. It will be recycled automatically if the
* conversion succeeds
* @param format the format of the compressed image
* @param quality hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning
* compress for max quality. Some formats, like PNG which is lossless, will
* ignore the quality setting
* @throws IOException if the conversion fails
* @return the new {@link Bitmap}
*/
@NonNull
public static Bitmap changeCodec(@NonNull Bitmap source, @NonNull Bitmap.CompressFormat format,
int quality) throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
if (source.compress(format, quality, outputStream)) {
final byte[] byteArray = outputStream.toByteArray();
try {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
} finally {
source.recycle();
}
} else {
throw new IOException("Failed to convert the bitmap codec");
}
} finally {
StorageManager.closeQuietly(outputStream);
}
}
/**
* Write a compressed version of the bitmap to the specified codec (eg from PNG to JPG). If
* successful, the source bitmap will be recycled and the resultant one will be returned in the
* desired format as an {@link OutputStream}
*
* <p>
* Note: not all Formats support all bitmap configs directly, so it is possible that the
* returned bitmap from BitmapFactory could be in a different bitdepth, and/or may have lost
* per-pixel alpha (e.g. JPEG only supports opaque pixels).
* </p>
*
* @param source the source {@link Bitmap} to use. It will be recycled automatically if the
* conversion succeeds
* @param format the format of the compressed image
* @param quality hint to the compressor, 0-100. 0 meaning compress for small size, 100 meaning
* compress for max quality. Some formats, like PNG which is lossless, will
* ignore the quality setting
* @throws IOException if the conversion fails
* @return an {@link IOException}, containing the bitmap with the converted codec
*/
@NonNull
public static OutputStream changeCodecToStream(@NonNull Bitmap source, @NonNull Bitmap.CompressFormat format,
int quality) throws IOException {
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
if (source.compress(format, quality, outputStream)) {
return outputStream;
} else {
throw new IOException("Failed to convert the bitmap codec");
}
}
/**
* Attempts to draw a given {@link View} to a {@link Bitmap}
*
* @param view the desired {@link View} to draw
* @return the {@link Bitmap}
*/
@NonNull
public static Bitmap drawView(@NonNull View view) {
return drawView(view, 1);
}
/**
* Attempts to draw a given {@link View} to a {@link Bitmap}
*
* @param view the desired {@link View} to draw
* @param scaleFactor how much the image should be scaled up/down
* @return the {@link Bitmap}
*/
@NonNull
public static Bitmap drawView(@NonNull View view, float scaleFactor) {
final Bitmap bitmap = Bitmap.createBitmap((int) (view.getWidth() * scaleFactor), (int) (view.getHeight() * scaleFactor), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
return bitmap;
}
}