• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
findDominantColorByHue(Bitmap bitmap)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      */
findDominantColorByHue(Bitmap bitmap, int samples)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