/* * 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.mail.utils; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; /** * Provides static functions to decode bitmaps at the optimal size */ public class BitmapUtil { private static final String TAG = LogTag.getLogTag(); private static final boolean DEBUG = false; private BitmapUtil() { } /** * Decode an image into a Bitmap, using sub-sampling if the hinted dimensions call for it. * Does not crop to fit the hinted dimensions. * * @param src an encoded image * @param w hint width in px * @param h hint height in px * @return a decoded Bitmap that is not exactly sized to the hinted dimensions. */ public static Bitmap decodeByteArray(byte[] src, int w, int h) { try { // calculate sample size based on w/h final BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = true; BitmapFactory.decodeByteArray(src, 0, src.length, opts); if (opts.mCancel || opts.outWidth == -1 || opts.outHeight == -1) { return null; } opts.inSampleSize = Math.min(opts.outWidth / w, opts.outHeight / h); opts.inJustDecodeBounds = false; return BitmapFactory.decodeByteArray(src, 0, src.length, opts); } catch (Throwable t) { LogUtils.w(TAG, t, "BitmapUtils unable to decode image"); return null; } } /** * Decode an image into a Bitmap, using sub-sampling if the desired dimensions call for it. * Also applies a center-crop a la {@link android.widget.ImageView.ScaleType#CENTER_CROP}. * * @param src an encoded image * @param w desired width in px * @param h desired height in px * @return an exactly-sized decoded Bitmap that is center-cropped. */ public static Bitmap decodeByteArrayWithCenterCrop(byte[] src, int w, int h) { try { final Bitmap decoded = decodeByteArray(src, w, h); return centerCrop(decoded, w, h); } catch (Throwable t) { LogUtils.w(TAG, t, "BitmapUtils unable to crop image"); return null; } } /** * Returns a new Bitmap copy with a center-crop effect a la * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. May return the input bitmap if no * scaling is necessary. * * @param src original bitmap of any size * @param w desired width in px * @param h desired height in px * @return a copy of src conforming to the given width and height, or src itself if it already * matches the given width and height */ public static Bitmap centerCrop(final Bitmap src, final int w, final int h) { return crop(src, w, h, 0.5f, 0.5f); } /** * Returns a new Bitmap copy with a crop effect depending on the crop anchor given. 0.5f is like * {@link android.widget.ImageView.ScaleType#CENTER_CROP}. The crop anchor will be be nudged * so the entire cropped bitmap will fit inside the src. May return the input bitmap if no * scaling is necessary. * * * Example of changing verticalCenterPercent: * _________ _________ * | | | | * | | |_________| * | | | |/___0.3f * |---------| |_________|\ * | |<---0.5f | | * |---------| | | * | | | | * | | | | * |_________| |_________| * * @param src original bitmap of any size * @param w desired width in px * @param h desired height in px * @param horizontalCenterPercent determines which part of the src to crop from. Range from 0 * .0f to 1.0f. The value determines which part of the src * maps to the horizontal center of the resulting bitmap. * @param verticalCenterPercent determines which part of the src to crop from. Range from 0 * .0f to 1.0f. The value determines which part of the src maps * to the vertical center of the resulting bitmap. * @return a copy of src conforming to the given width and height, or src itself if it already * matches the given width and height */ public static Bitmap crop(final Bitmap src, final int w, final int h, final float horizontalCenterPercent, final float verticalCenterPercent) { if (horizontalCenterPercent < 0 || horizontalCenterPercent > 1 || verticalCenterPercent < 0 || verticalCenterPercent > 1) { throw new IllegalArgumentException( "horizontalCenterPercent and verticalCenterPercent must be between 0.0f and " + "1.0f, inclusive."); } final int srcWidth = src.getWidth(); final int srcHeight = src.getHeight(); // exit early if no resize/crop needed if (w == srcWidth && h == srcHeight) { return src; } final Matrix m = new Matrix(); final float scale = Math.max( (float) w / srcWidth, (float) h / srcHeight); m.setScale(scale, scale); final int srcCroppedW, srcCroppedH; int srcX, srcY; srcCroppedW = Math.round(w / scale); srcCroppedH = Math.round(h / scale); srcX = (int) (srcWidth * horizontalCenterPercent - srcCroppedW / 2); srcY = (int) (srcHeight * verticalCenterPercent - srcCroppedH / 2); // Nudge srcX and srcY to be within the bounds of src srcX = Math.max(Math.min(srcX, srcWidth - srcCroppedW), 0); srcY = Math.max(Math.min(srcY, srcHeight - srcCroppedH), 0); final Bitmap cropped = Bitmap.createBitmap(src, srcX, srcY, srcCroppedW, srcCroppedH, m, true /* filter */); if (DEBUG) LogUtils.i(TAG, "BitmapUtils IN centerCrop, srcW/H=%s/%s desiredW/H=%s/%s srcX/Y=%s/%s" + " innerW/H=%s/%s scale=%s resultW/H=%s/%s", srcWidth, srcHeight, w, h, srcX, srcY, srcCroppedW, srcCroppedH, scale, cropped.getWidth(), cropped.getHeight()); if (DEBUG && (w != cropped.getWidth() || h != cropped.getHeight())) { LogUtils.e(TAG, new Error(), "BitmapUtils last center crop violated assumptions."); } return cropped; } /** * Frames the input bitmap in a circle. */ public static Bitmap frameBitmapInCircle(Bitmap input) { if (input == null) { return null; } // Crop the image if not squared. int inputWidth = input.getWidth(); int inputHeight = input.getHeight(); int targetX, targetY, targetSize; if (inputWidth >= inputHeight) { targetX = inputWidth / 2 - inputHeight / 2; targetY = 0; targetSize = inputHeight; } else { targetX = 0; targetY = inputHeight / 2 - inputWidth / 2; targetSize = inputWidth; } // Create an output bitmap and a canvas to draw on it. Bitmap output = Bitmap.createBitmap(targetSize, targetSize, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(output); // Create a black paint to draw the mask. Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLACK); // Draw a circle. canvas.drawCircle(targetSize / 2, targetSize / 2, targetSize / 2, paint); // Replace the black parts of the mask with the input image. paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN)); canvas.drawBitmap(input, targetX /* left */, targetY /* top */, paint); return output; } }