| 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)); |
| } |
| }; |
| } |
| |
| } |