1 /*
2  * Copyright (C) 2017 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.wallpaper.module;
17 
18 import android.graphics.Bitmap;
19 import android.graphics.Rect;
20 import android.os.AsyncTask;
21 import android.util.Log;
22 
23 import com.android.wallpaper.asset.Asset;
24 import com.android.wallpaper.asset.Asset.BitmapReceiver;
25 
26 /**
27  * Default implementation of BitmapCropper, which actually crops and scales bitmaps.
28  */
29 public class DefaultBitmapCropper implements BitmapCropper {
30     private static final String TAG = "DefaultBitmapCropper";
31     private static final boolean FILTER_SCALED_BITMAP = true;
32 
33     @Override
cropAndScaleBitmap(Asset asset, float scale, final Rect cropRect, final Callback callback)34     public void cropAndScaleBitmap(Asset asset, float scale, final Rect cropRect,
35                                    final Callback callback) {
36         // Crop rect in pixels of source image.
37         Rect scaledCropRect = new Rect(
38                 Math.round((float) cropRect.left / scale),
39                 Math.round((float) cropRect.top / scale),
40                 Math.round((float) cropRect.right / scale),
41                 Math.round((float) cropRect.bottom / scale));
42 
43         asset.decodeBitmapRegion(scaledCropRect, cropRect.width(), cropRect.height(),
44                 new BitmapReceiver() {
45                     @Override
46                     public void onBitmapDecoded(Bitmap bitmap) {
47 
48                         // Asset provides a bitmap which is appropriate for the target width & height, but since
49                         // it does not guarantee an exact size we need to fit the bitmap to the cropRect.
50                         ScaleBitmapTask task = new ScaleBitmapTask(bitmap, cropRect, callback);
51                         task.execute();
52                     }
53                 });
54     }
55 
56     /**
57      * AsyncTask subclass which creates a new bitmap which is resized to the exact dimensions of a
58      * Rect using Bitmap#createScaledBitmap.
59      */
60     private static class ScaleBitmapTask extends AsyncTask<Void, Void, Boolean> {
61 
62         private final Rect mCropRect;
63         private final Callback mCallback;
64         private Throwable mThrowable;
65 
66         private Bitmap mBitmap;
67 
ScaleBitmapTask(Bitmap bitmap, Rect cropRect, Callback callback)68         public ScaleBitmapTask(Bitmap bitmap, Rect cropRect, Callback callback) {
69             super();
70             mBitmap = bitmap;
71             mCropRect = cropRect;
72             mCallback = callback;
73         }
74 
75         @Override
doInBackground(Void... unused)76         protected Boolean doInBackground(Void... unused) {
77             if (mBitmap == null) {
78                 return false;
79             }
80 
81             try {
82                 // Fit bitmap to exact dimensions of crop rect.
83                 mBitmap = Bitmap.createScaledBitmap(
84                         mBitmap,
85                         mCropRect.width(),
86                         mCropRect.height(),
87                         FILTER_SCALED_BITMAP);
88 
89                 return true;
90             } catch (OutOfMemoryError e) {
91                 Log.w(TAG, "Not enough memory to fit the final cropped and scaled bitmap to size", e);
92                 mThrowable = e;
93                 return false;
94             }
95         }
96 
97         @Override
onPostExecute(Boolean isSuccess)98         protected void onPostExecute(Boolean isSuccess) {
99             if (isSuccess) {
100                 mCallback.onBitmapCropped(mBitmap);
101             } else {
102                 mCallback.onError(mThrowable);
103             }
104         }
105     }
106 }
107