Home | History | Annotate | Download | only in common
      1 package com.android.wallpaperpicker.common;
      2 
      3 import android.content.Context;
      4 import android.content.res.Resources;
      5 import android.graphics.Bitmap;
      6 import android.graphics.BitmapFactory;
      7 import android.graphics.BitmapRegionDecoder;
      8 import android.graphics.Canvas;
      9 import android.graphics.Matrix;
     10 import android.graphics.Paint;
     11 import android.graphics.Point;
     12 import android.graphics.Rect;
     13 import android.graphics.RectF;
     14 import android.net.Uri;
     15 import android.util.Log;
     16 
     17 import com.android.gallery3d.common.ExifOrientation;
     18 import com.android.gallery3d.common.Utils;
     19 
     20 import java.io.BufferedInputStream;
     21 import java.io.ByteArrayInputStream;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 
     25 /**
     26  * An abstraction over input stream creation. Also contains some utility methods
     27  * for various bitmap operations.
     28  */
     29 public abstract class InputStreamProvider {
     30 
     31     private static final String TAG = "InputStreamProvider";
     32 
     33     /**
     34      * Tries to create a new stream or returns null on failure.
     35      */
     36     public InputStream newStream() {
     37         try {
     38             return newStreamNotNull();
     39         } catch (IOException e) {
     40             return null;
     41         }
     42     }
     43 
     44     /**
     45      * Tries to create a new stream or throws an exception on failure.
     46      */
     47     public abstract InputStream newStreamNotNull() throws IOException;
     48 
     49     /**
     50      * Returns the size of the image, if the stream represents an image.
     51      */
     52     public Point getImageBounds() {
     53         InputStream is = newStream();
     54         if (is != null) {
     55             BitmapFactory.Options options = new BitmapFactory.Options();
     56             options.inJustDecodeBounds = true;
     57             BitmapFactory.decodeStream(is, null, options);
     58             Utils.closeSilently(is);
     59             if (options.outWidth != 0 && options.outHeight != 0) {
     60                 return new Point(options.outWidth, options.outHeight);
     61             }
     62         }
     63         return null;
     64     }
     65 
     66     public Bitmap readCroppedBitmap(RectF cropBounds, int outWidth, int outHeight, int rotation) {
     67         // Find crop bounds (scaled to original image size)
     68         Rect roundedTrueCrop = new Rect();
     69         Matrix rotateMatrix = new Matrix();
     70         Point bounds = getImageBounds();
     71         if (bounds == null) {
     72             Log.w(TAG, "cannot get bounds for image");
     73             return null;
     74         }
     75 
     76         if (rotation > 0) {
     77             rotateMatrix.setRotate(rotation);
     78 
     79             Matrix inverseRotateMatrix = new Matrix();
     80             inverseRotateMatrix.setRotate(-rotation);
     81 
     82             cropBounds.roundOut(roundedTrueCrop);
     83             cropBounds.set(roundedTrueCrop);
     84 
     85             float[] rotatedBounds = new float[] { bounds.x, bounds.y };
     86             rotateMatrix.mapPoints(rotatedBounds);
     87             rotatedBounds[0] = Math.abs(rotatedBounds[0]);
     88             rotatedBounds[1] = Math.abs(rotatedBounds[1]);
     89 
     90             cropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
     91             inverseRotateMatrix.mapRect(cropBounds);
     92             cropBounds.offset(bounds.x/2, bounds.y/2);
     93         }
     94 
     95         cropBounds.roundOut(roundedTrueCrop);
     96         if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
     97             Log.w(TAG, "crop has bad values for full size image");
     98             return null;
     99         }
    100 
    101         // See how much we're reducing the size of the image
    102         int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / outWidth,
    103                 roundedTrueCrop.height() / outHeight));
    104         // Attempt to open a region decoder
    105         InputStream is = null;
    106         BitmapRegionDecoder decoder = null;
    107         try {
    108             is = newStreamNotNull();
    109             decoder = BitmapRegionDecoder.newInstance(is, false);
    110         } catch (IOException e) {
    111             Log.w(TAG, "cannot open region decoder", e);
    112         } finally {
    113             Utils.closeSilently(is);
    114             is = null;
    115         }
    116 
    117         Bitmap crop = null;
    118         if (decoder != null) {
    119             // Do region decoding to get crop bitmap
    120             BitmapFactory.Options options = new BitmapFactory.Options();
    121             if (scaleDownSampleSize > 1) {
    122                 options.inSampleSize = scaleDownSampleSize;
    123             }
    124             crop = decoder.decodeRegion(roundedTrueCrop, options);
    125             decoder.recycle();
    126         }
    127 
    128         if (crop == null) {
    129             // BitmapRegionDecoder has failed, try to crop in-memory
    130             is = newStream();
    131             Bitmap fullSize = null;
    132             if (is != null) {
    133                 BitmapFactory.Options options = new BitmapFactory.Options();
    134                 if (scaleDownSampleSize > 1) {
    135                     options.inSampleSize = scaleDownSampleSize;
    136                 }
    137                 fullSize = BitmapFactory.decodeStream(is, null, options);
    138                 Utils.closeSilently(is);
    139             }
    140             if (fullSize != null) {
    141                 // Find out the true sample size that was used by the decoder
    142                 scaleDownSampleSize = bounds.x / fullSize.getWidth();
    143                 cropBounds.left /= scaleDownSampleSize;
    144                 cropBounds.top /= scaleDownSampleSize;
    145                 cropBounds.bottom /= scaleDownSampleSize;
    146                 cropBounds.right /= scaleDownSampleSize;
    147                 cropBounds.roundOut(roundedTrueCrop);
    148 
    149                 // Adjust values to account for issues related to rounding
    150                 if (roundedTrueCrop.width() > fullSize.getWidth()) {
    151                     // Adjust the width
    152                     roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
    153                 }
    154                 if (roundedTrueCrop.right > fullSize.getWidth()) {
    155                     // Adjust the left and right values.
    156                     roundedTrueCrop.offset(-(roundedTrueCrop.right - fullSize.getWidth()), 0);
    157                 }
    158                 if (roundedTrueCrop.height() > fullSize.getHeight()) {
    159                     // Adjust the height
    160                     roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
    161                 }
    162                 if (roundedTrueCrop.bottom > fullSize.getHeight()) {
    163                     // Adjust the top and bottom values.
    164                     roundedTrueCrop.offset(0, -(roundedTrueCrop.bottom - fullSize.getHeight()));
    165                 }
    166 
    167                 crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
    168                         roundedTrueCrop.top, roundedTrueCrop.width(),
    169                         roundedTrueCrop.height());
    170             }
    171         }
    172 
    173         if (crop == null) {
    174             return null;
    175         }
    176         if (outWidth > 0 && outHeight > 0 || rotation > 0) {
    177             float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
    178             rotateMatrix.mapPoints(dimsAfter);
    179             dimsAfter[0] = Math.abs(dimsAfter[0]);
    180             dimsAfter[1] = Math.abs(dimsAfter[1]);
    181 
    182             if (!(outWidth > 0 && outHeight > 0)) {
    183                 outWidth = Math.round(dimsAfter[0]);
    184                 outHeight = Math.round(dimsAfter[1]);
    185             }
    186 
    187             RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
    188             RectF returnRect = new RectF(0, 0, outWidth, outHeight);
    189 
    190             Matrix m = new Matrix();
    191             if (rotation == 0) {
    192                 m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    193             } else {
    194                 Matrix m1 = new Matrix();
    195                 m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
    196                 Matrix m2 = new Matrix();
    197                 m2.setRotate(rotation);
    198                 Matrix m3 = new Matrix();
    199                 m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
    200                 Matrix m4 = new Matrix();
    201                 m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    202 
    203                 Matrix c1 = new Matrix();
    204                 c1.setConcat(m2, m1);
    205                 Matrix c2 = new Matrix();
    206                 c2.setConcat(m4, m3);
    207                 m.setConcat(c2, c1);
    208             }
    209 
    210             Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
    211                     (int) returnRect.height(), Bitmap.Config.ARGB_8888);
    212             if (tmp != null) {
    213                 Canvas c = new Canvas(tmp);
    214                 Paint p = new Paint();
    215                 p.setFilterBitmap(true);
    216                 c.drawBitmap(crop, m, p);
    217                 crop = tmp;
    218             }
    219         }
    220         return crop;
    221     }
    222 
    223     public int getRotationFromExif(Context context) {
    224         InputStream is = null;
    225         try {
    226             is = newStreamNotNull();
    227             return ExifOrientation.readRotation(new BufferedInputStream(is), context);
    228         } catch (IOException | NullPointerException e) {
    229             Log.w(TAG, "Getting exif data failed", e);
    230         } finally {
    231             Utils.closeSilently(is);
    232         }
    233         return 0;
    234     }
    235 
    236     public static InputStreamProvider fromUri(final Context context, final Uri uri) {
    237         return new InputStreamProvider() {
    238             @Override
    239             public InputStream newStreamNotNull() throws IOException {
    240                 return new BufferedInputStream(context.getContentResolver().openInputStream(uri));
    241             }
    242         };
    243     }
    244 
    245     public static InputStreamProvider fromResource(final Resources resources, final int resId) {
    246         return new InputStreamProvider() {
    247             @Override
    248             public InputStream newStreamNotNull() {
    249                 return new BufferedInputStream(resources.openRawResource(resId));
    250             }
    251         };
    252     }
    253 
    254     public static InputStreamProvider fromBytes(final byte[] bytes) {
    255         return new InputStreamProvider() {
    256             @Override
    257             public InputStream newStreamNotNull() {
    258                 return new BufferedInputStream(new ByteArrayInputStream(bytes));
    259             }
    260         };
    261     }
    262 
    263 }
    264