1 /* 2 * Copyright (C) 2014 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.internal.util; 18 19 import android.graphics.Bitmap; 20 import android.graphics.Bitmap.Config; 21 import android.graphics.Canvas; 22 import android.graphics.Matrix; 23 import android.graphics.Paint; 24 import android.graphics.PorterDuff; 25 import android.graphics.drawable.BitmapDrawable; 26 import android.graphics.drawable.Drawable; 27 28 /** 29 * Utility class for image analysis and processing. 30 * 31 * @hide 32 */ 33 public class ImageUtils { 34 35 // Amount (max is 255) that two channels can differ before the color is no longer "gray". 36 private static final int TOLERANCE = 20; 37 38 // Alpha amount for which values below are considered transparent. 39 private static final int ALPHA_TOLERANCE = 50; 40 41 // Size of the smaller bitmap we're actually going to scan. 42 private static final int COMPACT_BITMAP_SIZE = 64; // pixels 43 44 private int[] mTempBuffer; 45 private Bitmap mTempCompactBitmap; 46 private Canvas mTempCompactBitmapCanvas; 47 private Paint mTempCompactBitmapPaint; 48 private final Matrix mTempMatrix = new Matrix(); 49 50 /** 51 * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect 52 * gray". 53 * 54 * Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than 55 * COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements 56 * will survive the squeezing process, contaminating the result with color. 57 */ isGrayscale(Bitmap bitmap)58 public boolean isGrayscale(Bitmap bitmap) { 59 int height = bitmap.getHeight(); 60 int width = bitmap.getWidth(); 61 62 // shrink to a more manageable (yet hopefully no more or less colorful) size 63 if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) { 64 if (mTempCompactBitmap == null) { 65 mTempCompactBitmap = Bitmap.createBitmap( 66 COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Bitmap.Config.ARGB_8888 67 ); 68 mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap); 69 mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 70 mTempCompactBitmapPaint.setFilterBitmap(true); 71 } 72 mTempMatrix.reset(); 73 mTempMatrix.setScale( 74 (float) COMPACT_BITMAP_SIZE / width, 75 (float) COMPACT_BITMAP_SIZE / height, 76 0, 0); 77 mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase 78 mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint); 79 bitmap = mTempCompactBitmap; 80 width = height = COMPACT_BITMAP_SIZE; 81 } 82 83 final int size = height*width; 84 ensureBufferSize(size); 85 bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); 86 for (int i = 0; i < size; i++) { 87 if (!isGrayscale(mTempBuffer[i])) { 88 return false; 89 } 90 } 91 return true; 92 } 93 94 /** 95 * Makes sure that {@code mTempBuffer} has at least length {@code size}. 96 */ ensureBufferSize(int size)97 private void ensureBufferSize(int size) { 98 if (mTempBuffer == null || mTempBuffer.length < size) { 99 mTempBuffer = new int[size]; 100 } 101 } 102 103 /** 104 * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect 105 * gray"; if all three channels are approximately equal, this will return true. 106 * 107 * Note that really transparent colors are always grayscale. 108 */ isGrayscale(int color)109 public static boolean isGrayscale(int color) { 110 int alpha = 0xFF & (color >> 24); 111 if (alpha < ALPHA_TOLERANCE) { 112 return true; 113 } 114 115 int r = 0xFF & (color >> 16); 116 int g = 0xFF & (color >> 8); 117 int b = 0xFF & color; 118 119 return Math.abs(r - g) < TOLERANCE 120 && Math.abs(r - b) < TOLERANCE 121 && Math.abs(g - b) < TOLERANCE; 122 } 123 124 /** 125 * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. 126 */ buildScaledBitmap(Drawable drawable, int maxWidth, int maxHeight)127 public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, 128 int maxHeight) { 129 if (drawable == null) { 130 return null; 131 } 132 int originalWidth = drawable.getIntrinsicWidth(); 133 int originalHeight = drawable.getIntrinsicHeight(); 134 135 if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) && 136 (drawable instanceof BitmapDrawable)) { 137 return ((BitmapDrawable) drawable).getBitmap(); 138 } 139 if (originalHeight <= 0 || originalWidth <= 0) { 140 return null; 141 } 142 143 // create a new bitmap, scaling down to fit the max dimensions of 144 // a large notification icon if necessary 145 float ratio = Math.min((float) maxWidth / (float) originalWidth, 146 (float) maxHeight / (float) originalHeight); 147 ratio = Math.min(1.0f, ratio); 148 int scaledWidth = (int) (ratio * originalWidth); 149 int scaledHeight = (int) (ratio * originalHeight); 150 Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); 151 152 // and paint our app bitmap on it 153 Canvas canvas = new Canvas(result); 154 drawable.setBounds(0, 0, scaledWidth, scaledHeight); 155 drawable.draw(canvas); 156 157 return result; 158 } 159 } 160