• 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 kotlin.math.sqrt
22 
23 /** Utility class for extracting colors from a bitmap. */
24 object ColorExtractor {
25     private const val NUM_SAMPLES = 20
26 
27     /**
28      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
29      *
30      * @param bitmap The bitmap to scan
31      */
32     @JvmStatic
findDominantColorByHuenull33     fun findDominantColorByHue(bitmap: Bitmap): Int {
34         val height = bitmap.height
35         val width = bitmap.width
36         val sampleStride = sqrt((height * width) / NUM_SAMPLES.toDouble()).toInt().coerceAtLeast(1)
37 
38         // This is an out-param, for getting the hsv values for an rgb
39         val hsv = FloatArray(3)
40 
41         // First get the best hue, by creating a histogram over 360 hue buckets,
42         // where each pixel contributes a score weighted by saturation, value, and alpha.
43         val hueScoreHistogram = FloatArray(360)
44         var highScore = -1f
45         var bestHue = -1
46 
47         val pixels = IntArray(NUM_SAMPLES)
48         var pixelCount = 0
49 
50         for (y in 0..<height step sampleStride) {
51             for (x in 0..<width step sampleStride) {
52                 val argb = bitmap.getPixel(x, y)
53                 val alpha = 0xFF and (argb shr 24)
54                 if (alpha < 0x80) {
55                     // Drop mostly-transparent pixels.
56                     continue
57                 }
58                 // Remove the alpha channel.
59                 val rgb = argb or -0x1000000
60                 Color.colorToHSV(rgb, hsv)
61                 // Bucket colors by the 360 integer hues.
62                 val hue = hsv[0].toInt()
63                 if (hue < 0 || hue >= hueScoreHistogram.size) {
64                     // Defensively avoid array bounds violations.
65                     continue
66                 }
67                 if (pixelCount < NUM_SAMPLES) {
68                     pixels[pixelCount++] = rgb
69                 }
70                 val score = hsv[1] * hsv[2]
71                 hueScoreHistogram[hue] += score
72                 if (hueScoreHistogram[hue] > highScore) {
73                     highScore = hueScoreHistogram[hue]
74                     bestHue = hue
75                 }
76             }
77         }
78 
79         val rgbScores = SparseArray<Float>()
80         var bestColor = -0x1000000
81         highScore = -1f
82         // Go back over the RGB colors that match the winning hue,
83         // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets.
84         // The highest-scoring RGB color wins.
85         for (i in 0..<pixelCount) {
86             val rgb = pixels[i]
87             Color.colorToHSV(rgb, hsv)
88             val hue = hsv[0].toInt()
89             if (hue == bestHue) {
90                 val s = hsv[1]
91                 val v = hsv[2]
92                 val bucket = (s * 100).toInt() + (v * 10000).toInt()
93                 // Score by cumulative saturation * value.
94                 val score = s * v
95                 val oldTotal = rgbScores[bucket]
96                 val newTotal = if (oldTotal == null) score else oldTotal + score
97                 rgbScores.put(bucket, newTotal)
98                 if (newTotal > highScore) {
99                     highScore = newTotal
100                     // All the colors in the winning bucket are very similar. Last in wins.
101                     bestColor = rgb
102                 }
103             }
104         }
105         return bestColor
106     }
107 }
108