Hi all,
I’ve recently been working on a pretty major update to my Fake iPhone app. In the process, I came across the need to calculate a suitable background color for icons on the home screen.
It had to be a color that would blend well with the icon, while still being clearly differentiated from the icon border itself.
Apparently Google Chrome already does this, by using a complex algorithm to calculate the most dominant color in an image. And I’ve noticed Windows 7 and Ubuntu’s Unity interface do a similar thing.
From my research, this process is generally known as image quantization. There are several free libraries available to automate the process for entire images, but I couldn’t find anything ready-made for Android. So I wrote my own function
For purposes of my app I decided these algorithms would be too computationally intensive (and too difficult for me to recreate in my spare time!). So instead I chose to use the average color of the icon, based on a sample of a limited set of pixels. This way I could avoid iterating through every pixel in the image, but still come up with a reasonable result.
The source code for this function getAverageColor() is listed below. It takes a BitmapDrawable as input, loops through a linear sampling of pixels, and comes up with an average value by finding the individual average of each HSV component. These are then combined together to return a single Color, encoded as an integer.
[java]
/**
-
Gets the average color in a drawable
-
This is calculated by sampling a subset of pixels
-
All calculations are done in HSV color space
-
Written by David Webb (http://makingmoneywithandroid.com/)
-
@param image The drawable to sample
-
@return An integer representing the average Color of the sampled pixels
*/
private int getAverageColor(Drawable image) {
//Setup initial variables
int hSamples = 20; //Number of pixels to sample on horizontal axis
int vSamples = 20; //Number of pixels to sample on vertical axis
int sampleSize = hSamples * vSamples; //Total number of pixels to sample
float[] sampleTotals = {0, 0, 0}; //Holds temporary sum of HSV values//If white pixels are included in sample, the average color will
// often have an unexpected shade. For this reason, we set a
// minimum saturation for pixels to be included in the sample set.
// Saturation < 0.1 is very close to white (see http://mkweb.bcgsc.ca/color_summarizer/?faq)
float minimumSaturation = 0.1f; //Saturation range is 0…1//By the same token, we should ignore transparent pixels
// (pixels with low alpha value)
int minimumAlpha = 200; //Alpha range is 0…255//Get bitmap
Bitmap b = ((BitmapDrawable)image).getBitmap();
int width = b.getWidth();
int height = b.getHeight();//Loop through pixels horizontally
float[] hsv = new float[3];
int sample;
for(int i=0; i<width; i+=(width/hSamples)) {
//Loop through pixels vertically
for(int j=0; j<height; j+=(height/vSamples)) {
//Get pixel & convert to HSV format
sample = b.getPixel(i, j);
Color.colorToHSV(sample, hsv);//Check pixel matches criteria to be included in sample if((Color.alpha(sample)>minimumAlpha) && (hsv[1] >= minimumSaturation)) { //Add sample values to total sampleTotals[0] += hsv[0]; //H sampleTotals[1] += hsv[1]; //S sampleTotals[2] += hsv[2]; //V }
// else {
// Log.d(TAG, “Pixel rejected: Alpha “+Color.alpha(sample)+”, H: “+hsv[0]+”, S:”+hsv[1]+", V:"+hsv[1]);
// }
}
}
//Divide total by number of samples to get average HSV values
float[] average = new float[3];
average[0] = sampleTotals[0] / sampleSize;
average[1] = sampleTotals[1] / sampleSize;
average[2] = sampleTotals[2] / sampleSize;
//Return average tuplet as RGB color
return Color.HSVToColor(average);
}
[/java]
The color that comes out of this function will probably look pretty ugly for most images. If you want to make it a bit prettier, just put this line immediately before the final return statement:
[java]
//Pretty some things up
average[2] = 0.8f;
[/java]
This manually sets the “Value” component of the HSV tuplet to the value you specify (between 0.0 and 1.0). I’ve found 0.8 works quite well as a background color for icons.
I hope this code can be helpful for anyone else who’s trying to accomplish a similar thing. I don’t claim to be an expert on the topic - I basically know the difference between RGB and HSV, but that’s about it! Coming up with this algorithm and then implementing it was an interesting learning experience for me.
This function sacrifices a lot of accuracy, for the sake of being simple and efficient. I’m definitely open to ideas for improvement, if any gurus out there have suggestions feel free to post them!
And if you do use this algorithm or code in your app, please let me know. It would be great to know where it’s been implemented!
Further reading:
http://mkweb.bcgsc.ca/color_summarizer/?faq#descriptions
http://stackoverflow.com/q/8471236/663469
http://stackoverflow.com/q/622573/663469
http://forums.codeguru.com/archive/index.php/t-306787.html