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