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