• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onBitmapCropped(byte[] imageBytes)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 
BitmapCropTask(Context c, String filePath, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable)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 
BitmapCropTask(byte[] imageBytes, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable)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 
BitmapCropTask(Context c, Uri inUri, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable)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 
BitmapCropTask(Context c, Resources res, int inResId, RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable)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 
init(RectF cropBounds, int rotation, int outWidth, int outHeight, boolean setWallpaper, boolean saveCroppedBitmap, Runnable onEndRunnable)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 
setOnBitmapCropped(BitmapCropTask.OnBitmapCroppedHandler handler)114     public void setOnBitmapCropped(BitmapCropTask.OnBitmapCroppedHandler handler) {
115         mOnBitmapCroppedHandler = handler;
116     }
117 
setNoCrop(boolean value)118     public void setNoCrop(boolean value) {
119         mNoCrop = value;
120     }
121 
setOnEndRunnable(Runnable onEndRunnable)122     public void setOnEndRunnable(Runnable onEndRunnable) {
123         mOnEndRunnable = onEndRunnable;
124     }
125 
126     // Helper to setup input stream
regenerateInputStream()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 
getImageBounds()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 
setCropBounds(RectF cropBounds)164     public void setCropBounds(RectF cropBounds) {
165         mCropBounds = cropBounds;
166     }
167 
getCroppedBitmap()168     public Bitmap getCroppedBitmap() {
169         return mCroppedBitmap;
170     }
cropBitmap()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
doInBackground(Void... params)395     protected Boolean doInBackground(Void... params) {
396         return cropBitmap();
397     }
398 
399     @Override
onPostExecute(Boolean result)400     protected void onPostExecute(Boolean result) {
401         if (mOnEndRunnable != null) {
402             mOnEndRunnable.run();
403         }
404     }
405 }