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.dialer.contactphoto;
18 
19 import android.graphics.Bitmap;
20 import android.graphics.BitmapFactory;
21 import android.graphics.Canvas;
22 import android.graphics.Paint;
23 import android.graphics.PorterDuff.Mode;
24 import android.graphics.PorterDuffXfermode;
25 import android.graphics.Rect;
26 import android.graphics.RectF;
27 
28 /** Provides static functions to decode bitmaps at the optimal size */
29 public class BitmapUtil {
30 
BitmapUtil()31   private BitmapUtil() {}
32 
33   /**
34    * Returns Width or Height of the picture, depending on which size is smaller. Doesn't actually
35    * decode the picture, so it is pretty efficient to run.
36    */
getSmallerExtentFromBytes(byte[] bytes)37   public static int getSmallerExtentFromBytes(byte[] bytes) {
38     final BitmapFactory.Options options = new BitmapFactory.Options();
39 
40     // don't actually decode the picture, just return its bounds
41     options.inJustDecodeBounds = true;
42     BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
43 
44     // test what the best sample size is
45     return Math.min(options.outWidth, options.outHeight);
46   }
47 
48   /**
49    * Finds the optimal sampleSize for loading the picture
50    *
51    * @param originalSmallerExtent Width or height of the picture, whichever is smaller
52    * @param targetExtent Width or height of the target view, whichever is bigger.
53    *     <p>If either one of the parameters is 0 or smaller, no sampling is applied
54    */
findOptimalSampleSize(int originalSmallerExtent, int targetExtent)55   public static int findOptimalSampleSize(int originalSmallerExtent, int targetExtent) {
56     // If we don't know sizes, we can't do sampling.
57     if (targetExtent < 1) {
58       return 1;
59     }
60     if (originalSmallerExtent < 1) {
61       return 1;
62     }
63 
64     // Test what the best sample size is. To do that, we find the sample size that gives us
65     // the best trade-off between resulting image size and memory requirement. We allow
66     // the down-sampled image to be 20% smaller than the target size. That way we can get around
67     // unfortunate cases where e.g. a 720 picture is requested for 362 and not down-sampled at
68     // all. Why 20%? Why not. Prove me wrong.
69     int extent = originalSmallerExtent;
70     int sampleSize = 1;
71     while ((extent >> 1) >= targetExtent * 0.8f) {
72       sampleSize <<= 1;
73       extent >>= 1;
74     }
75 
76     return sampleSize;
77   }
78 
79   /** Decodes the bitmap with the given sample size */
decodeBitmapFromBytes(byte[] bytes, int sampleSize)80   public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) {
81     final BitmapFactory.Options options;
82     if (sampleSize <= 1) {
83       options = null;
84     } else {
85       options = new BitmapFactory.Options();
86       options.inSampleSize = sampleSize;
87     }
88     return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
89   }
90 
91   /**
92    * Given an input bitmap, scales it to the given width/height and makes it round.
93    *
94    * @param input {@link Bitmap} to scale and crop
95    * @param targetWidth desired output width
96    * @param targetHeight desired output height
97    * @return output bitmap scaled to the target width/height and cropped to an oval. The cropping
98    *     algorithm will try to fit as much of the input into the output as possible, while
99    *     preserving the target width/height ratio.
100    */
getRoundedBitmap(Bitmap input, int targetWidth, int targetHeight)101   public static Bitmap getRoundedBitmap(Bitmap input, int targetWidth, int targetHeight) {
102     if (input == null) {
103       return null;
104     }
105     final Bitmap.Config inputConfig = input.getConfig();
106     final Bitmap result =
107         Bitmap.createBitmap(
108             targetWidth, targetHeight, inputConfig != null ? inputConfig : Bitmap.Config.ARGB_8888);
109     final Canvas canvas = new Canvas(result);
110     final Paint paint = new Paint();
111     canvas.drawARGB(0, 0, 0, 0);
112     paint.setAntiAlias(true);
113     final RectF dst = new RectF(0, 0, targetWidth, targetHeight);
114     canvas.drawOval(dst, paint);
115 
116     // Specifies that only pixels present in the destination (i.e. the drawn oval) should
117     // be overwritten with pixels from the input bitmap.
118     paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
119 
120     final int inputWidth = input.getWidth();
121     final int inputHeight = input.getHeight();
122 
123     // Choose the largest scale factor that will fit inside the dimensions of the
124     // input bitmap.
125     final float scaleBy =
126         Math.min((float) inputWidth / targetWidth, (float) inputHeight / targetHeight);
127 
128     final int xCropAmountHalved = (int) (scaleBy * targetWidth / 2);
129     final int yCropAmountHalved = (int) (scaleBy * targetHeight / 2);
130 
131     final Rect src =
132         new Rect(
133             inputWidth / 2 - xCropAmountHalved,
134             inputHeight / 2 - yCropAmountHalved,
135             inputWidth / 2 + xCropAmountHalved,
136             inputHeight / 2 + yCropAmountHalved);
137 
138     canvas.drawBitmap(input, src, dst, paint);
139     return result;
140   }
141 }
142