139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi/* 239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Copyright (C) 2014 The Android Open Source Project 339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * 439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Licensed under the Apache License, Version 2.0 (the "License"); 539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * you may not use this file except in compliance with the License. 639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * You may obtain a copy of the License at 739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * 839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * http://www.apache.org/licenses/LICENSE-2.0 939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * 1039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Unless required by applicable law or agreed to in writing, software 1139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * distributed under the License is distributed on an "AS IS" BASIS, 1239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * See the License for the specific language governing permissions and 1439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * limitations under the License 1539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 1639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 175c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggipackage com.android.internal.util; 1839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 1939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggiimport android.graphics.Bitmap; 2039fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franzimport android.graphics.Bitmap.Config; 2105c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.Canvas; 2205c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.Matrix; 2305c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.Paint; 2405c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.PorterDuff; 2539fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franzimport android.graphics.drawable.BitmapDrawable; 2639fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franzimport android.graphics.drawable.Drawable; 2739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 2839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi/** 2939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Utility class for image analysis and processing. 305c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggi * 315c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggi * @hide 3239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 3339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggipublic class ImageUtils { 3439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 3539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi // Amount (max is 255) that two channels can differ before the color is no longer "gray". 3639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private static final int TOLERANCE = 20; 3739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 3839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi // Alpha amount for which values below are considered transparent. 3939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private static final int ALPHA_TOLERANCE = 50; 4039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 4105c362d5645367c816069aa138b597b77f317aa4Dan Sandler // Size of the smaller bitmap we're actually going to scan. 4205c362d5645367c816069aa138b597b77f317aa4Dan Sandler private static final int COMPACT_BITMAP_SIZE = 64; // pixels 4305c362d5645367c816069aa138b597b77f317aa4Dan Sandler 4439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private int[] mTempBuffer; 4505c362d5645367c816069aa138b597b77f317aa4Dan Sandler private Bitmap mTempCompactBitmap; 4605c362d5645367c816069aa138b597b77f317aa4Dan Sandler private Canvas mTempCompactBitmapCanvas; 4705c362d5645367c816069aa138b597b77f317aa4Dan Sandler private Paint mTempCompactBitmapPaint; 4805c362d5645367c816069aa138b597b77f317aa4Dan Sandler private final Matrix mTempMatrix = new Matrix(); 4939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 5039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi /** 5139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect 5239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * gray". 5305c362d5645367c816069aa138b597b77f317aa4Dan Sandler * 5405c362d5645367c816069aa138b597b77f317aa4Dan Sandler * Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than 5505c362d5645367c816069aa138b597b77f317aa4Dan Sandler * COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements 5605c362d5645367c816069aa138b597b77f317aa4Dan Sandler * will survive the squeezing process, contaminating the result with color. 5739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 5839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi public boolean isGrayscale(Bitmap bitmap) { 5905c362d5645367c816069aa138b597b77f317aa4Dan Sandler int height = bitmap.getHeight(); 6005c362d5645367c816069aa138b597b77f317aa4Dan Sandler int width = bitmap.getWidth(); 6105c362d5645367c816069aa138b597b77f317aa4Dan Sandler 6205c362d5645367c816069aa138b597b77f317aa4Dan Sandler // shrink to a more manageable (yet hopefully no more or less colorful) size 6305c362d5645367c816069aa138b597b77f317aa4Dan Sandler if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) { 6405c362d5645367c816069aa138b597b77f317aa4Dan Sandler if (mTempCompactBitmap == null) { 6505c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmap = Bitmap.createBitmap( 6605c362d5645367c816069aa138b597b77f317aa4Dan Sandler COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Bitmap.Config.ARGB_8888 6705c362d5645367c816069aa138b597b77f317aa4Dan Sandler ); 6805c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap); 696a49ddef62865c1b245ad60a13c334f0ffaf1a5fChris Craik mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 7005c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapPaint.setFilterBitmap(true); 7105c362d5645367c816069aa138b597b77f317aa4Dan Sandler } 7205c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempMatrix.reset(); 7305c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempMatrix.setScale( 7405c362d5645367c816069aa138b597b77f317aa4Dan Sandler (float) COMPACT_BITMAP_SIZE / width, 7505c362d5645367c816069aa138b597b77f317aa4Dan Sandler (float) COMPACT_BITMAP_SIZE / height, 7605c362d5645367c816069aa138b597b77f317aa4Dan Sandler 0, 0); 7705c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase 7805c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint); 7905c362d5645367c816069aa138b597b77f317aa4Dan Sandler bitmap = mTempCompactBitmap; 8005c362d5645367c816069aa138b597b77f317aa4Dan Sandler width = height = COMPACT_BITMAP_SIZE; 8105c362d5645367c816069aa138b597b77f317aa4Dan Sandler } 8239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 8305c362d5645367c816069aa138b597b77f317aa4Dan Sandler final int size = height*width; 8439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi ensureBufferSize(size); 8539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); 8639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi for (int i = 0; i < size; i++) { 8739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi if (!isGrayscale(mTempBuffer[i])) { 8839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return false; 8939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 9039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 9139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return true; 9239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 9339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 9439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi /** 9539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Makes sure that {@code mTempBuffer} has at least length {@code size}. 9639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 9739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private void ensureBufferSize(int size) { 9839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi if (mTempBuffer == null || mTempBuffer.length < size) { 9939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi mTempBuffer = new int[size]; 10039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 10139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 10239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 10339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi /** 10439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect 10539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * gray"; if all three channels are approximately equal, this will return true. 10639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * 10739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Note that really transparent colors are always grayscale. 10839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 1095c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggi public static boolean isGrayscale(int color) { 11039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int alpha = 0xFF & (color >> 24); 11139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi if (alpha < ALPHA_TOLERANCE) { 11239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return true; 11339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 11439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 11539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int r = 0xFF & (color >> 16); 11639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int g = 0xFF & (color >> 8); 11739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int b = 0xFF & color; 11839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 11939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return Math.abs(r - g) < TOLERANCE 12039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi && Math.abs(r - b) < TOLERANCE 12139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi && Math.abs(g - b) < TOLERANCE; 12239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 12339fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz 12439fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz /** 12539fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. 12639fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz */ 12739fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, 12839fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz int maxHeight) { 12939fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz if (drawable == null) { 13039fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz return null; 13139fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz } 13239fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz int originalWidth = drawable.getIntrinsicWidth(); 13339fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz int originalHeight = drawable.getIntrinsicHeight(); 13439fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz 13539fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz if ((originalWidth <= maxWidth) && (originalHeight <= maxHeight) && 13639fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz (drawable instanceof BitmapDrawable)) { 13739fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz return ((BitmapDrawable) drawable).getBitmap(); 13839fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz } 13939fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz if (originalHeight <= 0 || originalWidth <= 0) { 14039fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz return null; 14139fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz } 14239fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz 14339fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz // create a new bitmap, scaling down to fit the max dimensions of 14439fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz // a large notification icon if necessary 14539fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz float ratio = Math.min((float) maxWidth / (float) originalWidth, 14639fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz (float) maxHeight / (float) originalHeight); 14739fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz ratio = Math.min(1.0f, ratio); 14839fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz int scaledWidth = (int) (ratio * originalWidth); 14939fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz int scaledHeight = (int) (ratio * originalHeight); 15039fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); 15139fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz 15239fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz // and paint our app bitmap on it 15339fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz Canvas canvas = new Canvas(result); 15439fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz drawable.setBounds(0, 0, scaledWidth, scaledHeight); 15539fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz drawable.draw(canvas); 15639fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz 15739fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz return result; 15839fb7fd730dc2113ced7e663d7a35e48a4c6b1aeBenjamin Franz } 15939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi} 160