• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 
17 package com.android.mail.utils;
18 import android.graphics.Bitmap;
19 import android.graphics.BitmapFactory;
20 import android.graphics.Canvas;
21 import android.graphics.Color;
22 import android.graphics.Matrix;
23 import android.graphics.Paint;
24 import android.graphics.PorterDuff;
25 import android.graphics.PorterDuffXfermode;
26 
27 /**
28  * Provides static functions to decode bitmaps at the optimal size
29  */
30 public class BitmapUtil {
31 
32     private static final String TAG = LogTag.getLogTag();
33     private static final boolean DEBUG = false;
34 
BitmapUtil()35     private BitmapUtil() {
36     }
37 
38     /**
39      * Decode an image into a Bitmap, using sub-sampling if the hinted dimensions call for it.
40      * Does not crop to fit the hinted dimensions.
41      *
42      * @param src an encoded image
43      * @param w hint width in px
44      * @param h hint height in px
45      * @return a decoded Bitmap that is not exactly sized to the hinted dimensions.
46      */
decodeByteArray(byte[] src, int w, int h)47     public static Bitmap decodeByteArray(byte[] src, int w, int h) {
48         try {
49             // calculate sample size based on w/h
50             final BitmapFactory.Options opts = new BitmapFactory.Options();
51             opts.inJustDecodeBounds = true;
52             BitmapFactory.decodeByteArray(src, 0, src.length, opts);
53             if (opts.mCancel || opts.outWidth == -1 || opts.outHeight == -1) {
54                 return null;
55             }
56             opts.inSampleSize = Math.min(opts.outWidth / w, opts.outHeight / h);
57             opts.inJustDecodeBounds = false;
58             return BitmapFactory.decodeByteArray(src, 0, src.length, opts);
59         } catch (Throwable t) {
60             LogUtils.w(TAG, t, "BitmapUtils unable to decode image");
61             return null;
62         }
63     }
64 
65     /**
66      * Decode an image into a Bitmap, using sub-sampling if the desired dimensions call for it.
67      * Also applies a center-crop a la {@link android.widget.ImageView.ScaleType#CENTER_CROP}.
68      *
69      * @param src an encoded image
70      * @param w desired width in px
71      * @param h desired height in px
72      * @return an exactly-sized decoded Bitmap that is center-cropped.
73      */
decodeByteArrayWithCenterCrop(byte[] src, int w, int h)74     public static Bitmap decodeByteArrayWithCenterCrop(byte[] src, int w, int h) {
75         try {
76             final Bitmap decoded = decodeByteArray(src, w, h);
77             return centerCrop(decoded, w, h);
78 
79         } catch (Throwable t) {
80             LogUtils.w(TAG, t, "BitmapUtils unable to crop image");
81             return null;
82         }
83     }
84 
85     /**
86      * Returns a new Bitmap copy with a center-crop effect a la
87      * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. May return the input bitmap if no
88      * scaling is necessary.
89      *
90      * @param src original bitmap of any size
91      * @param w desired width in px
92      * @param h desired height in px
93      * @return a copy of src conforming to the given width and height, or src itself if it already
94      *         matches the given width and height
95      */
centerCrop(final Bitmap src, final int w, final int h)96     public static Bitmap centerCrop(final Bitmap src, final int w, final int h) {
97         return crop(src, w, h, 0.5f, 0.5f);
98     }
99 
100     /**
101      * Returns a new Bitmap copy with a crop effect depending on the crop anchor given. 0.5f is like
102      * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. The crop anchor will be be nudged
103      * so the entire cropped bitmap will fit inside the src. May return the input bitmap if no
104      * scaling is necessary.
105      *
106      *
107      * Example of changing verticalCenterPercent:
108      *   _________            _________
109      *  |         |          |         |
110      *  |         |          |_________|
111      *  |         |          |         |/___0.3f
112      *  |---------|          |_________|\
113      *  |         |<---0.5f  |         |
114      *  |---------|          |         |
115      *  |         |          |         |
116      *  |         |          |         |
117      *  |_________|          |_________|
118      *
119      * @param src original bitmap of any size
120      * @param w desired width in px
121      * @param h desired height in px
122      * @param horizontalCenterPercent determines which part of the src to crop from. Range from 0
123      *                                .0f to 1.0f. The value determines which part of the src
124      *                                maps to the horizontal center of the resulting bitmap.
125      * @param verticalCenterPercent determines which part of the src to crop from. Range from 0
126      *                              .0f to 1.0f. The value determines which part of the src maps
127      *                              to the vertical center of the resulting bitmap.
128      * @return a copy of src conforming to the given width and height, or src itself if it already
129      *         matches the given width and height
130      */
crop(final Bitmap src, final int w, final int h, final float horizontalCenterPercent, final float verticalCenterPercent)131     public static Bitmap crop(final Bitmap src, final int w, final int h,
132             final float horizontalCenterPercent, final float verticalCenterPercent) {
133         if (horizontalCenterPercent < 0 || horizontalCenterPercent > 1 || verticalCenterPercent < 0
134                 || verticalCenterPercent > 1) {
135             throw new IllegalArgumentException(
136                     "horizontalCenterPercent and verticalCenterPercent must be between 0.0f and "
137                             + "1.0f, inclusive.");
138         }
139         final int srcWidth = src.getWidth();
140         final int srcHeight = src.getHeight();
141 
142         // exit early if no resize/crop needed
143         if (w == srcWidth && h == srcHeight) {
144             return src;
145         }
146 
147         final Matrix m = new Matrix();
148         final float scale = Math.max(
149                 (float) w / srcWidth,
150                 (float) h / srcHeight);
151         m.setScale(scale, scale);
152 
153         final int srcCroppedW, srcCroppedH;
154         int srcX, srcY;
155 
156         srcCroppedW = Math.round(w / scale);
157         srcCroppedH = Math.round(h / scale);
158         srcX = (int) (srcWidth * horizontalCenterPercent - srcCroppedW / 2);
159         srcY = (int) (srcHeight * verticalCenterPercent - srcCroppedH / 2);
160 
161         // Nudge srcX and srcY to be within the bounds of src
162         srcX = Math.max(Math.min(srcX, srcWidth - srcCroppedW), 0);
163         srcY = Math.max(Math.min(srcY, srcHeight - srcCroppedH), 0);
164 
165         final Bitmap cropped = Bitmap.createBitmap(src, srcX, srcY, srcCroppedW, srcCroppedH, m,
166                 true /* filter */);
167 
168         if (DEBUG) LogUtils.i(TAG,
169                 "BitmapUtils IN centerCrop, srcW/H=%s/%s desiredW/H=%s/%s srcX/Y=%s/%s" +
170                 " innerW/H=%s/%s scale=%s resultW/H=%s/%s",
171                 srcWidth, srcHeight, w, h, srcX, srcY, srcCroppedW, srcCroppedH, scale,
172                 cropped.getWidth(), cropped.getHeight());
173         if (DEBUG && (w != cropped.getWidth() || h != cropped.getHeight())) {
174             LogUtils.e(TAG, new Error(), "BitmapUtils last center crop violated assumptions.");
175         }
176 
177         return cropped;
178     }
179 
180     /**
181      * Frames the input bitmap in a circle.
182      */
frameBitmapInCircle(Bitmap input)183     public static Bitmap frameBitmapInCircle(Bitmap input) {
184         if (input == null) {
185             return null;
186         }
187 
188         // Crop the image if not squared.
189         int inputWidth = input.getWidth();
190         int inputHeight = input.getHeight();
191         int targetX, targetY, targetSize;
192         if (inputWidth >= inputHeight) {
193             targetX = inputWidth / 2 - inputHeight / 2;
194             targetY = 0;
195             targetSize = inputHeight;
196         } else {
197             targetX = 0;
198             targetY = inputHeight / 2 - inputWidth / 2;
199             targetSize = inputWidth;
200         }
201 
202         // Create an output bitmap and a canvas to draw on it.
203         Bitmap output = Bitmap.createBitmap(targetSize, targetSize, Bitmap.Config.ARGB_8888);
204         Canvas canvas = new Canvas(output);
205 
206         // Create a black paint to draw the mask.
207         Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
208         paint.setColor(Color.BLACK);
209 
210         // Draw a circle.
211         canvas.drawCircle(targetSize / 2, targetSize / 2, targetSize / 2, paint);
212 
213         // Replace the black parts of the mask with the input image.
214         paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
215         canvas.drawBitmap(input, targetX /* left */, targetY /* top */, paint);
216 
217         return output;
218     }
219 }
220