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