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