Home | History | Annotate | Download | only in graphics
      1 /*
      2  * Copyright (C) 2017 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 package com.android.launcher3.graphics;
     17 
     18 import android.graphics.Bitmap;
     19 import android.graphics.Color;
     20 import android.util.SparseArray;
     21 
     22 /**
     23  * Utility class for extracting colors from a bitmap.
     24  */
     25 public class ColorExtractor {
     26 
     27     public static int findDominantColorByHue(Bitmap bitmap) {
     28         return findDominantColorByHue(bitmap, 20);
     29     }
     30 
     31     /**
     32      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
     33      * @param bitmap The bitmap to scan
     34      * @param samples The approximate max number of samples to use.
     35      */
     36     public static int findDominantColorByHue(Bitmap bitmap, int samples) {
     37         final int height = bitmap.getHeight();
     38         final int width = bitmap.getWidth();
     39         int sampleStride = (int) Math.sqrt((height * width) / samples);
     40         if (sampleStride < 1) {
     41             sampleStride = 1;
     42         }
     43 
     44         // This is an out-param, for getting the hsv values for an rgb
     45         float[] hsv = new float[3];
     46 
     47         // First get the best hue, by creating a histogram over 360 hue buckets,
     48         // where each pixel contributes a score weighted by saturation, value, and alpha.
     49         float[] hueScoreHistogram = new float[360];
     50         float highScore = -1;
     51         int bestHue = -1;
     52 
     53         int[] pixels = new int[samples];
     54         int pixelCount = 0;
     55 
     56         for (int y = 0; y < height; y += sampleStride) {
     57             for (int x = 0; x < width; x += sampleStride) {
     58                 int argb = bitmap.getPixel(x, y);
     59                 int alpha = 0xFF & (argb >> 24);
     60                 if (alpha < 0x80) {
     61                     // Drop mostly-transparent pixels.
     62                     continue;
     63                 }
     64                 // Remove the alpha channel.
     65                 int rgb = argb | 0xFF000000;
     66                 Color.colorToHSV(rgb, hsv);
     67                 // Bucket colors by the 360 integer hues.
     68                 int hue = (int) hsv[0];
     69                 if (hue < 0 || hue >= hueScoreHistogram.length) {
     70                     // Defensively avoid array bounds violations.
     71                     continue;
     72                 }
     73                 if (pixelCount < samples) {
     74                     pixels[pixelCount++] = rgb;
     75                 }
     76                 float score = hsv[1] * hsv[2];
     77                 hueScoreHistogram[hue] += score;
     78                 if (hueScoreHistogram[hue] > highScore) {
     79                     highScore = hueScoreHistogram[hue];
     80                     bestHue = hue;
     81                 }
     82             }
     83         }
     84 
     85         SparseArray<Float> rgbScores = new SparseArray<>();
     86         int bestColor = 0xff000000;
     87         highScore = -1;
     88         // Go back over the RGB colors that match the winning hue,
     89         // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
     90         // The highest-scoring RGB color wins.
     91         for (int i = 0; i < pixelCount; i++) {
     92             int rgb = pixels[i];
     93             Color.colorToHSV(rgb, hsv);
     94             int hue = (int) hsv[0];
     95             if (hue == bestHue) {
     96                 float s = hsv[1];
     97                 float v = hsv[2];
     98                 int bucket = (int) (s * 100) + (int) (v * 10000);
     99                 // Score by cumulative saturation * value.
    100                 float score = s * v;
    101                 Float oldTotal = rgbScores.get(bucket);
    102                 float newTotal = oldTotal == null ? score : oldTotal + score;
    103                 rgbScores.put(bucket, newTotal);
    104                 if (newTotal > highScore) {
    105                     highScore = newTotal;
    106                     // All the colors in the winning bucket are very similar. Last in wins.
    107                     bestColor = rgb;
    108                 }
    109             }
    110         }
    111         return bestColor;
    112     }
    113 }
    114