blob: 72588ad6450998c83eccbccb676534b28ee47577 [file] [log] [blame]
package com.android.wallpaperpicker.common;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapRegionDecoder;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.util.Log;
import com.android.gallery3d.common.ExifOrientation;
import com.android.gallery3d.common.Utils;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* An abstraction over input stream creation. Also contains some utility methods
* for various bitmap operations.
*/
public abstract class InputStreamProvider {
private static final String TAG = "InputStreamProvider";
/**
* Tries to create a new stream or returns null on failure.
*/
public InputStream newStream() {
try {
return newStreamNotNull();
} catch (IOException e) {
return null;
}
}
/**
* Tries to create a new stream or throws an exception on failure.
*/
public abstract InputStream newStreamNotNull() throws IOException;
/**
* Returns the size of the image, if the stream represents an image.
*/
public Point getImageBounds() {
InputStream is = newStream();
if (is != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(is, null, options);
Utils.closeSilently(is);
if (options.outWidth != 0 && options.outHeight != 0) {
return new Point(options.outWidth, options.outHeight);
}
}
return null;
}
public Bitmap readCroppedBitmap(RectF cropBounds, int outWidth, int outHeight, int rotation) {
// Find crop bounds (scaled to original image size)
Rect roundedTrueCrop = new Rect();
Matrix rotateMatrix = new Matrix();
Point bounds = getImageBounds();
if (bounds == null) {
Log.w(TAG, "cannot get bounds for image");
return null;
}
if (rotation > 0) {
rotateMatrix.setRotate(rotation);
Matrix inverseRotateMatrix = new Matrix();
inverseRotateMatrix.setRotate(-rotation);
cropBounds.roundOut(roundedTrueCrop);
cropBounds.set(roundedTrueCrop);
float[] rotatedBounds = new float[] { bounds.x, bounds.y };
rotateMatrix.mapPoints(rotatedBounds);
rotatedBounds[0] = Math.abs(rotatedBounds[0]);
rotatedBounds[1] = Math.abs(rotatedBounds[1]);
cropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
inverseRotateMatrix.mapRect(cropBounds);
cropBounds.offset(bounds.x/2, bounds.y/2);
}
cropBounds.roundOut(roundedTrueCrop);
if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
Log.w(TAG, "crop has bad values for full size image");
return null;
}
// See how much we're reducing the size of the image
int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / outWidth,
roundedTrueCrop.height() / outHeight));
// Attempt to open a region decoder
InputStream is = null;
BitmapRegionDecoder decoder = null;
try {
is = newStreamNotNull();
decoder = BitmapRegionDecoder.newInstance(is, false);
} catch (IOException e) {
Log.w(TAG, "cannot open region decoder", e);
} finally {
Utils.closeSilently(is);
is = null;
}
Bitmap crop = null;
if (decoder != null) {
// Do region decoding to get crop bitmap
BitmapFactory.Options options = new BitmapFactory.Options();
if (scaleDownSampleSize > 1) {
options.inSampleSize = scaleDownSampleSize;
}
crop = decoder.decodeRegion(roundedTrueCrop, options);
decoder.recycle();
}
if (crop == null) {
// BitmapRegionDecoder has failed, try to crop in-memory
is = newStream();
Bitmap fullSize = null;
if (is != null) {
BitmapFactory.Options options = new BitmapFactory.Options();
if (scaleDownSampleSize > 1) {
options.inSampleSize = scaleDownSampleSize;
}
fullSize = BitmapFactory.decodeStream(is, null, options);
Utils.closeSilently(is);
}
if (fullSize != null) {
// Find out the true sample size that was used by the decoder
scaleDownSampleSize = bounds.x / fullSize.getWidth();
cropBounds.left /= scaleDownSampleSize;
cropBounds.top /= scaleDownSampleSize;
cropBounds.bottom /= scaleDownSampleSize;
cropBounds.right /= scaleDownSampleSize;
cropBounds.roundOut(roundedTrueCrop);
// Adjust values to account for issues related to rounding
if (roundedTrueCrop.width() > fullSize.getWidth()) {
// Adjust the width
roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
}
if (roundedTrueCrop.right > fullSize.getWidth()) {
// Adjust the left and right values.
roundedTrueCrop.offset(-(roundedTrueCrop.right - fullSize.getWidth()), 0);
}
if (roundedTrueCrop.height() > fullSize.getHeight()) {
// Adjust the height
roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
}
if (roundedTrueCrop.bottom > fullSize.getHeight()) {
// Adjust the top and bottom values.
roundedTrueCrop.offset(0, -(roundedTrueCrop.bottom - fullSize.getHeight()));
}
crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
roundedTrueCrop.top, roundedTrueCrop.width(),
roundedTrueCrop.height());
}
}
if (crop == null) {
return null;
}
if (outWidth > 0 && outHeight > 0 || rotation > 0) {
float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
rotateMatrix.mapPoints(dimsAfter);
dimsAfter[0] = Math.abs(dimsAfter[0]);
dimsAfter[1] = Math.abs(dimsAfter[1]);
if (!(outWidth > 0 && outHeight > 0)) {
outWidth = Math.round(dimsAfter[0]);
outHeight = Math.round(dimsAfter[1]);
}
RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
RectF returnRect = new RectF(0, 0, outWidth, outHeight);
Matrix m = new Matrix();
if (rotation == 0) {
m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
} else {
Matrix m1 = new Matrix();
m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
Matrix m2 = new Matrix();
m2.setRotate(rotation);
Matrix m3 = new Matrix();
m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
Matrix m4 = new Matrix();
m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
Matrix c1 = new Matrix();
c1.setConcat(m2, m1);
Matrix c2 = new Matrix();
c2.setConcat(m4, m3);
m.setConcat(c2, c1);
}
Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
(int) returnRect.height(), Bitmap.Config.ARGB_8888);
if (tmp != null) {
Canvas c = new Canvas(tmp);
Paint p = new Paint();
p.setFilterBitmap(true);
c.drawBitmap(crop, m, p);
crop = tmp;
}
}
return crop;
}
public int getRotationFromExif(Context context) {
InputStream is = null;
try {
is = newStreamNotNull();
return ExifOrientation.readRotation(new BufferedInputStream(is), context);
} catch (IOException | NullPointerException e) {
Log.w(TAG, "Getting exif data failed", e);
} finally {
Utils.closeSilently(is);
}
return 0;
}
public static InputStreamProvider fromUri(final Context context, final Uri uri) {
return new InputStreamProvider() {
@Override
public InputStream newStreamNotNull() throws IOException {
return new BufferedInputStream(context.getContentResolver().openInputStream(uri));
}
};
}
public static InputStreamProvider fromResource(final Resources resources, final int resId) {
return new InputStreamProvider() {
@Override
public InputStream newStreamNotNull() {
return new BufferedInputStream(resources.openRawResource(resId));
}
};
}
public static InputStreamProvider fromBytes(final byte[] bytes) {
return new InputStreamProvider() {
@Override
public InputStream newStreamNotNull() {
return new BufferedInputStream(new ByteArrayInputStream(bytes));
}
};
}
}