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