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; 2005c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.Canvas; 2105c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.Matrix; 2205c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.Paint; 2305c362d5645367c816069aa138b597b77f317aa4Dan Sandlerimport android.graphics.PorterDuff; 2439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 2539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi/** 2639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Utility class for image analysis and processing. 275c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggi * 285c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggi * @hide 2939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 3039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggipublic class ImageUtils { 3139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 3239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi // Amount (max is 255) that two channels can differ before the color is no longer "gray". 3339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private static final int TOLERANCE = 20; 3439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 3539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi // Alpha amount for which values below are considered transparent. 3639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private static final int ALPHA_TOLERANCE = 50; 3739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 3805c362d5645367c816069aa138b597b77f317aa4Dan Sandler // Size of the smaller bitmap we're actually going to scan. 3905c362d5645367c816069aa138b597b77f317aa4Dan Sandler private static final int COMPACT_BITMAP_SIZE = 64; // pixels 4005c362d5645367c816069aa138b597b77f317aa4Dan Sandler 4139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private int[] mTempBuffer; 4205c362d5645367c816069aa138b597b77f317aa4Dan Sandler private Bitmap mTempCompactBitmap; 4305c362d5645367c816069aa138b597b77f317aa4Dan Sandler private Canvas mTempCompactBitmapCanvas; 4405c362d5645367c816069aa138b597b77f317aa4Dan Sandler private Paint mTempCompactBitmapPaint; 4505c362d5645367c816069aa138b597b77f317aa4Dan Sandler private final Matrix mTempMatrix = new Matrix(); 4639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 4739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi /** 4839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect 4939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * gray". 5005c362d5645367c816069aa138b597b77f317aa4Dan Sandler * 5105c362d5645367c816069aa138b597b77f317aa4Dan Sandler * Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than 5205c362d5645367c816069aa138b597b77f317aa4Dan Sandler * COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements 5305c362d5645367c816069aa138b597b77f317aa4Dan Sandler * will survive the squeezing process, contaminating the result with color. 5439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 5539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi public boolean isGrayscale(Bitmap bitmap) { 5605c362d5645367c816069aa138b597b77f317aa4Dan Sandler int height = bitmap.getHeight(); 5705c362d5645367c816069aa138b597b77f317aa4Dan Sandler int width = bitmap.getWidth(); 5805c362d5645367c816069aa138b597b77f317aa4Dan Sandler 5905c362d5645367c816069aa138b597b77f317aa4Dan Sandler // shrink to a more manageable (yet hopefully no more or less colorful) size 6005c362d5645367c816069aa138b597b77f317aa4Dan Sandler if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) { 6105c362d5645367c816069aa138b597b77f317aa4Dan Sandler if (mTempCompactBitmap == null) { 6205c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmap = Bitmap.createBitmap( 6305c362d5645367c816069aa138b597b77f317aa4Dan Sandler COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Bitmap.Config.ARGB_8888 6405c362d5645367c816069aa138b597b77f317aa4Dan Sandler ); 6505c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap); 6605c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 6705c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapPaint.setFilterBitmap(true); 6805c362d5645367c816069aa138b597b77f317aa4Dan Sandler } 6905c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempMatrix.reset(); 7005c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempMatrix.setScale( 7105c362d5645367c816069aa138b597b77f317aa4Dan Sandler (float) COMPACT_BITMAP_SIZE / width, 7205c362d5645367c816069aa138b597b77f317aa4Dan Sandler (float) COMPACT_BITMAP_SIZE / height, 7305c362d5645367c816069aa138b597b77f317aa4Dan Sandler 0, 0); 7405c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase 7505c362d5645367c816069aa138b597b77f317aa4Dan Sandler mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint); 7605c362d5645367c816069aa138b597b77f317aa4Dan Sandler bitmap = mTempCompactBitmap; 7705c362d5645367c816069aa138b597b77f317aa4Dan Sandler width = height = COMPACT_BITMAP_SIZE; 7805c362d5645367c816069aa138b597b77f317aa4Dan Sandler } 7939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 8005c362d5645367c816069aa138b597b77f317aa4Dan Sandler final int size = height*width; 8139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi ensureBufferSize(size); 8239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); 8339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi for (int i = 0; i < size; i++) { 8439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi if (!isGrayscale(mTempBuffer[i])) { 8539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return false; 8639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 8739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 8839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return true; 8939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 9039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 9139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi /** 9239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Makes sure that {@code mTempBuffer} has at least length {@code size}. 9339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 9439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi private void ensureBufferSize(int size) { 9539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi if (mTempBuffer == null || mTempBuffer.length < size) { 9639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi mTempBuffer = new int[size]; 9739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 9839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 9939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 10039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi /** 10139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Classifies a color as grayscale or not. Grayscale here means "very close to a perfect 10239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * gray"; if all three channels are approximately equal, this will return true. 10339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * 10439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi * Note that really transparent colors are always grayscale. 10539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi */ 1065c2d84675b239bc04ae98c75526e5b81897ee183Jorim Jaggi public static boolean isGrayscale(int color) { 10739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int alpha = 0xFF & (color >> 24); 10839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi if (alpha < ALPHA_TOLERANCE) { 10939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return true; 11039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 11139fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 11239fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int r = 0xFF & (color >> 16); 11339fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int g = 0xFF & (color >> 8); 11439fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi int b = 0xFF & color; 11539fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi 11639fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi return Math.abs(r - g) < TOLERANCE 11739fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi && Math.abs(r - b) < TOLERANCE 11839fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi && Math.abs(g - b) < TOLERANCE; 11939fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi } 12039fa59fc4907d3c8faad41bf20e1f855dbcda5e6Jorim Jaggi} 121