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