| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dialer.contactphoto; |
| |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Canvas; |
| import android.graphics.Paint; |
| import android.graphics.PorterDuff.Mode; |
| import android.graphics.PorterDuffXfermode; |
| import android.graphics.Rect; |
| import android.graphics.RectF; |
| |
| /** Provides static functions to decode bitmaps at the optimal size */ |
| public class BitmapUtil { |
| |
| private BitmapUtil() {} |
| |
| /** |
| * Returns Width or Height of the picture, depending on which size is smaller. Doesn't actually |
| * decode the picture, so it is pretty efficient to run. |
| */ |
| public static int getSmallerExtentFromBytes(byte[] bytes) { |
| final BitmapFactory.Options options = new BitmapFactory.Options(); |
| |
| // don't actually decode the picture, just return its bounds |
| options.inJustDecodeBounds = true; |
| BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); |
| |
| // test what the best sample size is |
| return Math.min(options.outWidth, options.outHeight); |
| } |
| |
| /** |
| * Finds the optimal sampleSize for loading the picture |
| * |
| * @param originalSmallerExtent Width or height of the picture, whichever is smaller |
| * @param targetExtent Width or height of the target view, whichever is bigger. |
| * <p>If either one of the parameters is 0 or smaller, no sampling is applied |
| */ |
| public static int findOptimalSampleSize(int originalSmallerExtent, int targetExtent) { |
| // If we don't know sizes, we can't do sampling. |
| if (targetExtent < 1) { |
| return 1; |
| } |
| if (originalSmallerExtent < 1) { |
| return 1; |
| } |
| |
| // Test what the best sample size is. To do that, we find the sample size that gives us |
| // the best trade-off between resulting image size and memory requirement. We allow |
| // the down-sampled image to be 20% smaller than the target size. That way we can get around |
| // unfortunate cases where e.g. a 720 picture is requested for 362 and not down-sampled at |
| // all. Why 20%? Why not. Prove me wrong. |
| int extent = originalSmallerExtent; |
| int sampleSize = 1; |
| while ((extent >> 1) >= targetExtent * 0.8f) { |
| sampleSize <<= 1; |
| extent >>= 1; |
| } |
| |
| return sampleSize; |
| } |
| |
| /** Decodes the bitmap with the given sample size */ |
| public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) { |
| final BitmapFactory.Options options; |
| if (sampleSize <= 1) { |
| options = null; |
| } else { |
| options = new BitmapFactory.Options(); |
| options.inSampleSize = sampleSize; |
| } |
| return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options); |
| } |
| |
| /** |
| * Given an input bitmap, scales it to the given width/height and makes it round. |
| * |
| * @param input {@link Bitmap} to scale and crop |
| * @param targetWidth desired output width |
| * @param targetHeight desired output height |
| * @return output bitmap scaled to the target width/height and cropped to an oval. The cropping |
| * algorithm will try to fit as much of the input into the output as possible, while |
| * preserving the target width/height ratio. |
| */ |
| public static Bitmap getRoundedBitmap(Bitmap input, int targetWidth, int targetHeight) { |
| if (input == null) { |
| return null; |
| } |
| final Bitmap.Config inputConfig = input.getConfig(); |
| final Bitmap result = |
| Bitmap.createBitmap( |
| targetWidth, targetHeight, inputConfig != null ? inputConfig : Bitmap.Config.ARGB_8888); |
| final Canvas canvas = new Canvas(result); |
| final Paint paint = new Paint(); |
| canvas.drawARGB(0, 0, 0, 0); |
| paint.setAntiAlias(true); |
| final RectF dst = new RectF(0, 0, targetWidth, targetHeight); |
| canvas.drawOval(dst, paint); |
| |
| // Specifies that only pixels present in the destination (i.e. the drawn oval) should |
| // be overwritten with pixels from the input bitmap. |
| paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN)); |
| |
| final int inputWidth = input.getWidth(); |
| final int inputHeight = input.getHeight(); |
| |
| // Choose the largest scale factor that will fit inside the dimensions of the |
| // input bitmap. |
| final float scaleBy = |
| Math.min((float) inputWidth / targetWidth, (float) inputHeight / targetHeight); |
| |
| final int xCropAmountHalved = (int) (scaleBy * targetWidth / 2); |
| final int yCropAmountHalved = (int) (scaleBy * targetHeight / 2); |
| |
| final Rect src = |
| new Rect( |
| inputWidth / 2 - xCropAmountHalved, |
| inputHeight / 2 - yCropAmountHalved, |
| inputWidth / 2 + xCropAmountHalved, |
| inputHeight / 2 + yCropAmountHalved); |
| |
| canvas.drawBitmap(input, src, dst, paint); |
| return result; |
| } |
| } |