Home | History | Annotate | Download | only in common
      1 /**
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.gallery3d.common;
     17 
     18 import android.app.WallpaperManager;
     19 import android.content.Context;
     20 import android.content.res.Resources;
     21 import android.graphics.Bitmap;
     22 import android.graphics.Bitmap.CompressFormat;
     23 import android.graphics.BitmapFactory;
     24 import android.graphics.BitmapRegionDecoder;
     25 import android.graphics.Canvas;
     26 import android.graphics.Matrix;
     27 import android.graphics.Paint;
     28 import android.graphics.Point;
     29 import android.graphics.Rect;
     30 import android.graphics.RectF;
     31 import android.net.Uri;
     32 import android.os.AsyncTask;
     33 import android.util.Log;
     34 
     35 import java.io.BufferedInputStream;
     36 import java.io.ByteArrayInputStream;
     37 import java.io.ByteArrayOutputStream;
     38 import java.io.FileNotFoundException;
     39 import java.io.IOException;
     40 import java.io.InputStream;
     41 
     42 public class BitmapCropTask extends AsyncTask<Void, Void, Boolean> {
     43 
     44     public interface OnBitmapCroppedHandler {
     45         public void onBitmapCropped(byte[] imageBytes);
     46     }
     47 
     48     private static final int DEFAULT_COMPRESS_QUALITY = 90;
     49     private static final String LOGTAG = "BitmapCropTask";
     50 
     51     Uri mInUri = null;
     52     Context mContext;
     53     String mInFilePath;
     54     byte[] mInImageBytes;
     55     int mInResId = 0;
     56     RectF mCropBounds = null;
     57     int mOutWidth, mOutHeight;
     58     int mRotation;
     59     boolean mSetWallpaper;
     60     boolean mSaveCroppedBitmap;
     61     Bitmap mCroppedBitmap;
     62     Runnable mOnEndRunnable;
     63     Resources mResources;
     64     BitmapCropTask.OnBitmapCroppedHandler mOnBitmapCroppedHandler;
     65     boolean mNoCrop;
     66 
     67     public BitmapCropTask(Context c, String filePath,
     68             RectF cropBounds, int rotation, int outWidth, int outHeight,
     69             boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
     70         mContext = c;
     71         mInFilePath = filePath;
     72         init(cropBounds, rotation,
     73                 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
     74     }
     75 
     76     public BitmapCropTask(byte[] imageBytes,
     77             RectF cropBounds, int rotation, int outWidth, int outHeight,
     78             boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
     79         mInImageBytes = imageBytes;
     80         init(cropBounds, rotation,
     81                 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
     82     }
     83 
     84     public BitmapCropTask(Context c, Uri inUri,
     85             RectF cropBounds, int rotation, int outWidth, int outHeight,
     86             boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
     87         mContext = c;
     88         mInUri = inUri;
     89         init(cropBounds, rotation,
     90                 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
     91     }
     92 
     93     public BitmapCropTask(Context c, Resources res, int inResId,
     94             RectF cropBounds, int rotation, int outWidth, int outHeight,
     95             boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
     96         mContext = c;
     97         mInResId = inResId;
     98         mResources = res;
     99         init(cropBounds, rotation,
    100                 outWidth, outHeight, setWallpaper, saveCroppedBitmap, onEndRunnable);
    101     }
    102 
    103     private void init(RectF cropBounds, int rotation, int outWidth, int outHeight,
    104             boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable) {
    105         mCropBounds = cropBounds;
    106         mRotation = rotation;
    107         mOutWidth = outWidth;
    108         mOutHeight = outHeight;
    109         mSetWallpaper = setWallpaper;
    110         mSaveCroppedBitmap = saveCroppedBitmap;
    111         mOnEndRunnable = onEndRunnable;
    112     }
    113 
    114     public void setOnBitmapCropped(BitmapCropTask.OnBitmapCroppedHandler handler) {
    115         mOnBitmapCroppedHandler = handler;
    116     }
    117 
    118     public void setNoCrop(boolean value) {
    119         mNoCrop = value;
    120     }
    121 
    122     public void setOnEndRunnable(Runnable onEndRunnable) {
    123         mOnEndRunnable = onEndRunnable;
    124     }
    125 
    126     // Helper to setup input stream
    127     private InputStream regenerateInputStream() {
    128         if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) {
    129             Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " +
    130                     "image byte array given");
    131         } else {
    132             try {
    133                 if (mInUri != null) {
    134                     return new BufferedInputStream(
    135                             mContext.getContentResolver().openInputStream(mInUri));
    136                 } else if (mInFilePath != null) {
    137                     return mContext.openFileInput(mInFilePath);
    138                 } else if (mInImageBytes != null) {
    139                     return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes));
    140                 } else {
    141                     return new BufferedInputStream(mResources.openRawResource(mInResId));
    142                 }
    143             } catch (FileNotFoundException e) {
    144                 Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e);
    145             }
    146         }
    147         return null;
    148     }
    149 
    150     public Point getImageBounds() {
    151         InputStream is = regenerateInputStream();
    152         if (is != null) {
    153             BitmapFactory.Options options = new BitmapFactory.Options();
    154             options.inJustDecodeBounds = true;
    155             BitmapFactory.decodeStream(is, null, options);
    156             Utils.closeSilently(is);
    157             if (options.outWidth != 0 && options.outHeight != 0) {
    158                 return new Point(options.outWidth, options.outHeight);
    159             }
    160         }
    161         return null;
    162     }
    163 
    164     public void setCropBounds(RectF cropBounds) {
    165         mCropBounds = cropBounds;
    166     }
    167 
    168     public Bitmap getCroppedBitmap() {
    169         return mCroppedBitmap;
    170     }
    171     public boolean cropBitmap() {
    172         boolean failure = false;
    173 
    174 
    175         WallpaperManager wallpaperManager = null;
    176         if (mSetWallpaper) {
    177             wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext());
    178         }
    179 
    180 
    181         if (mSetWallpaper && mNoCrop) {
    182             try {
    183                 InputStream is = regenerateInputStream();
    184                 if (is != null) {
    185                     wallpaperManager.setStream(is);
    186                     Utils.closeSilently(is);
    187                 }
    188             } catch (IOException e) {
    189                 Log.w(LOGTAG, "cannot write stream to wallpaper", e);
    190                 failure = true;
    191             }
    192             return !failure;
    193         } else {
    194             // Find crop bounds (scaled to original image size)
    195             Rect roundedTrueCrop = new Rect();
    196             Matrix rotateMatrix = new Matrix();
    197             Matrix inverseRotateMatrix = new Matrix();
    198 
    199             Point bounds = getImageBounds();
    200             if (mRotation > 0) {
    201                 rotateMatrix.setRotate(mRotation);
    202                 inverseRotateMatrix.setRotate(-mRotation);
    203 
    204                 mCropBounds.roundOut(roundedTrueCrop);
    205                 mCropBounds = new RectF(roundedTrueCrop);
    206 
    207                 if (bounds == null) {
    208                     Log.w(LOGTAG, "cannot get bounds for image");
    209                     failure = true;
    210                     return false;
    211                 }
    212 
    213                 float[] rotatedBounds = new float[] { bounds.x, bounds.y };
    214                 rotateMatrix.mapPoints(rotatedBounds);
    215                 rotatedBounds[0] = Math.abs(rotatedBounds[0]);
    216                 rotatedBounds[1] = Math.abs(rotatedBounds[1]);
    217 
    218                 mCropBounds.offset(-rotatedBounds[0]/2, -rotatedBounds[1]/2);
    219                 inverseRotateMatrix.mapRect(mCropBounds);
    220                 mCropBounds.offset(bounds.x/2, bounds.y/2);
    221 
    222             }
    223 
    224             mCropBounds.roundOut(roundedTrueCrop);
    225 
    226             if (roundedTrueCrop.width() <= 0 || roundedTrueCrop.height() <= 0) {
    227                 Log.w(LOGTAG, "crop has bad values for full size image");
    228                 failure = true;
    229                 return false;
    230             }
    231 
    232             // See how much we're reducing the size of the image
    233             int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth,
    234                     roundedTrueCrop.height() / mOutHeight));
    235             // Attempt to open a region decoder
    236             BitmapRegionDecoder decoder = null;
    237             InputStream is = null;
    238             try {
    239                 is = regenerateInputStream();
    240                 if (is == null) {
    241                     Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString());
    242                     failure = true;
    243                     return false;
    244                 }
    245                 decoder = BitmapRegionDecoder.newInstance(is, false);
    246                 Utils.closeSilently(is);
    247             } catch (IOException e) {
    248                 Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e);
    249             } finally {
    250                Utils.closeSilently(is);
    251                is = null;
    252             }
    253 
    254             Bitmap crop = null;
    255             if (decoder != null) {
    256                 // Do region decoding to get crop bitmap
    257                 BitmapFactory.Options options = new BitmapFactory.Options();
    258                 if (scaleDownSampleSize > 1) {
    259                     options.inSampleSize = scaleDownSampleSize;
    260                 }
    261                 crop = decoder.decodeRegion(roundedTrueCrop, options);
    262                 decoder.recycle();
    263             }
    264 
    265             if (crop == null) {
    266                 // BitmapRegionDecoder has failed, try to crop in-memory
    267                 is = regenerateInputStream();
    268                 Bitmap fullSize = null;
    269                 if (is != null) {
    270                     BitmapFactory.Options options = new BitmapFactory.Options();
    271                     if (scaleDownSampleSize > 1) {
    272                         options.inSampleSize = scaleDownSampleSize;
    273                     }
    274                     fullSize = BitmapFactory.decodeStream(is, null, options);
    275                     Utils.closeSilently(is);
    276                 }
    277                 if (fullSize != null) {
    278                     // Find out the true sample size that was used by the decoder
    279                     scaleDownSampleSize = bounds.x / fullSize.getWidth();
    280                     mCropBounds.left /= scaleDownSampleSize;
    281                     mCropBounds.top /= scaleDownSampleSize;
    282                     mCropBounds.bottom /= scaleDownSampleSize;
    283                     mCropBounds.right /= scaleDownSampleSize;
    284                     mCropBounds.roundOut(roundedTrueCrop);
    285 
    286                     // Adjust values to account for issues related to rounding
    287                     if (roundedTrueCrop.width() > fullSize.getWidth()) {
    288                         // Adjust the width
    289                         roundedTrueCrop.right = roundedTrueCrop.left + fullSize.getWidth();
    290                     }
    291                     if (roundedTrueCrop.right > fullSize.getWidth()) {
    292                         // Adjust the left value
    293                         int adjustment = roundedTrueCrop.left -
    294                                 Math.max(0, roundedTrueCrop.right - roundedTrueCrop.width());
    295                         roundedTrueCrop.left -= adjustment;
    296                         roundedTrueCrop.right -= adjustment;
    297                     }
    298                     if (roundedTrueCrop.height() > fullSize.getHeight()) {
    299                         // Adjust the height
    300                         roundedTrueCrop.bottom = roundedTrueCrop.top + fullSize.getHeight();
    301                     }
    302                     if (roundedTrueCrop.bottom > fullSize.getHeight()) {
    303                         // Adjust the top value
    304                         int adjustment = roundedTrueCrop.top -
    305                                 Math.max(0, roundedTrueCrop.bottom - roundedTrueCrop.height());
    306                         roundedTrueCrop.top -= adjustment;
    307                         roundedTrueCrop.bottom -= adjustment;
    308                     }
    309 
    310                     crop = Bitmap.createBitmap(fullSize, roundedTrueCrop.left,
    311                             roundedTrueCrop.top, roundedTrueCrop.width(),
    312                             roundedTrueCrop.height());
    313                 }
    314             }
    315 
    316             if (crop == null) {
    317                 Log.w(LOGTAG, "cannot decode file: " + mInUri.toString());
    318                 failure = true;
    319                 return false;
    320             }
    321             if (mOutWidth > 0 && mOutHeight > 0 || mRotation > 0) {
    322                 float[] dimsAfter = new float[] { crop.getWidth(), crop.getHeight() };
    323                 rotateMatrix.mapPoints(dimsAfter);
    324                 dimsAfter[0] = Math.abs(dimsAfter[0]);
    325                 dimsAfter[1] = Math.abs(dimsAfter[1]);
    326 
    327                 if (!(mOutWidth > 0 && mOutHeight > 0)) {
    328                     mOutWidth = Math.round(dimsAfter[0]);
    329                     mOutHeight = Math.round(dimsAfter[1]);
    330                 }
    331 
    332                 RectF cropRect = new RectF(0, 0, dimsAfter[0], dimsAfter[1]);
    333                 RectF returnRect = new RectF(0, 0, mOutWidth, mOutHeight);
    334 
    335                 Matrix m = new Matrix();
    336                 if (mRotation == 0) {
    337                     m.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    338                 } else {
    339                     Matrix m1 = new Matrix();
    340                     m1.setTranslate(-crop.getWidth() / 2f, -crop.getHeight() / 2f);
    341                     Matrix m2 = new Matrix();
    342                     m2.setRotate(mRotation);
    343                     Matrix m3 = new Matrix();
    344                     m3.setTranslate(dimsAfter[0] / 2f, dimsAfter[1] / 2f);
    345                     Matrix m4 = new Matrix();
    346                     m4.setRectToRect(cropRect, returnRect, Matrix.ScaleToFit.FILL);
    347 
    348                     Matrix c1 = new Matrix();
    349                     c1.setConcat(m2, m1);
    350                     Matrix c2 = new Matrix();
    351                     c2.setConcat(m4, m3);
    352                     m.setConcat(c2, c1);
    353                 }
    354 
    355                 Bitmap tmp = Bitmap.createBitmap((int) returnRect.width(),
    356                         (int) returnRect.height(), Bitmap.Config.ARGB_8888);
    357                 if (tmp != null) {
    358                     Canvas c = new Canvas(tmp);
    359                     Paint p = new Paint();
    360                     p.setFilterBitmap(true);
    361                     c.drawBitmap(crop, m, p);
    362                     crop = tmp;
    363                 }
    364             }
    365 
    366             if (mSaveCroppedBitmap) {
    367                 mCroppedBitmap = crop;
    368             }
    369 
    370             // Compress to byte array
    371             ByteArrayOutputStream tmpOut = new ByteArrayOutputStream(2048);
    372             if (crop.compress(CompressFormat.JPEG, DEFAULT_COMPRESS_QUALITY, tmpOut)) {
    373                 // If we need to set to the wallpaper, set it
    374                 if (mSetWallpaper && wallpaperManager != null) {
    375                     try {
    376                         byte[] outByteArray = tmpOut.toByteArray();
    377                         wallpaperManager.setStream(new ByteArrayInputStream(outByteArray));
    378                         if (mOnBitmapCroppedHandler != null) {
    379                             mOnBitmapCroppedHandler.onBitmapCropped(outByteArray);
    380                         }
    381                     } catch (IOException e) {
    382                         Log.w(LOGTAG, "cannot write stream to wallpaper", e);
    383                         failure = true;
    384                     }
    385                 }
    386             } else {
    387                 Log.w(LOGTAG, "cannot compress bitmap");
    388                 failure = true;
    389             }
    390         }
    391         return !failure; // True if any of the operations failed
    392     }
    393 
    394     @Override
    395     protected Boolean doInBackground(Void... params) {
    396         return cropBitmap();
    397     }
    398 
    399     @Override
    400     protected void onPostExecute(Boolean result) {
    401         if (mOnEndRunnable != null) {
    402             mOnEndRunnable.run();
    403         }
    404     }
    405 }