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