Finding the dominant color of an image in an Android @drawable

AndroidColorsBitmapDrawable

Android Problem Overview


You can understand why I'm trying to find the dominant color in an image if you use Windows 7. When your mouse over a program in the taskbar, the background of that particular program changes based on the most dominant color in the icon. I have noticed this technique used in other programs as well, but can't remember them off the top of my head.

I can see this being helpful in a number of UI techniques that I'm using to develop an application, and I was wondering how finding the most common color would be achieved from an Android drawable resource.

Android Solutions


Solution 1 - Android

In Android 5.0 Lollipop, a class was added to help extract useful colors from a Bitmap. The Palette class, found in android.support.v7.graphics, can extract the following colors:

  • Vibrant
  • Vibrant Dark
  • Vibrant Light
  • Muted
  • Muted Dark
  • Muted Light

This Android training page gives all the details you need to use the class (I tried it myself in Android Studio and it was very straightforward): http://developer.android.com/training/material/drawables.html#ColorExtract

To quote:

> The Android Support Library r21 and above includes the Palette > class, which lets you extract prominent colors from an image. To > extract these colors, pass a Bitmap object to the Palette.generate() > static method in the background thread where you load your images. If > you can't use that thread, call the Palette.generateAsync() method and > provide a listener instead.* > > You can retrieve the prominent colors from the image using the getter > methods in the Palette class, such as Palette.getVibrantColor. > > To use the Palette class in your project, add the following Gradle > dependency to your app's module: > > dependencies { > ... > implementation 'com.android.support:palette-v7:21.0.+' > } > Or if you're using androidx: > > implementation 'androidx.palette:palette:1.0.0'

If you need to use generateAsync(), here's how:

Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() {
    public void onGenerated(Palette palette) {
        // Do something with colors...
    }
});

EDIT: Since the question asks how to extract colors from a drawable resource, you'd first have to convert the drawable to a bitmap to use the technique I've described. Luckily, that is quite simple using BitmapFactory:

Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
                                       R.drawable.icon_resource);`

Solution 2 - Android

There is also an another solution, it's more approximative but if you don't want to have long delay for searching color, it can do the job.

public static int getDominantColor(Bitmap bitmap) {
    Bitmap newBitmap = Bitmap.createScaledBitmap(bitmap, 1, 1, true);
    final int color = newBitmap.getPixel(0, 0);
    newBitmap.recycle();
    return color;
}

Solution 3 - Android

To find the Dominant / Vibrant / Muted color from an image, use Palette:

import:

implementation 'androidx.palette:palette:1.0.0'

usage:

    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image)

    Palette.Builder(bitmap).generate { it?.let {  palette ->
        val dominantColor = palette.getDominantColor(ContextCompat.getColor(context!!, R.color.defaultColor))
        
        // TODO: use dominant color
        
    } }

Solution 4 - Android

This class iterates through a Bitmap and returns the most dominate colour. Feel free to clean up the code where necessary.

public class ImageColour {

String colour;


public ImageColour(Bitmap image) throws Exception {

	 int height = image.getHeight();
     int width = image.getWidth();

     Map m = new HashMap();
        
        for(int i=0; i < width ; i++){
        
            for(int j=0; j < height ; j++){
            
                int rgb = image.getPixel(i, j);
                int[] rgbArr = getRGBArr(rgb);                
                             
                if (!isGray(rgbArr)) {   
                	
                        Integer counter = (Integer) m.get(rgb);   
                        if (counter == null)
                            counter = 0;
                        counter++;                                
                        m.put(rgb, counter);       
                        
                }                
            }
        }        
        
        String colourHex = getMostCommonColour(m);
    }


    
	public static String getMostCommonColour(Map map) {
    	
        List list = new LinkedList(map.entrySet());
        Collections.sort(list, new Comparator() {
              public int compare(Object o1, Object o2) {
            	  
                return ((Comparable) ((Map.Entry) (o1)).getValue())
                  .compareTo(((Map.Entry) (o2)).getValue());
                
              }
              
        });    
        
        Map.Entry me = (Map.Entry )list.get(list.size()-1);
        int[] rgb= getRGBArr((Integer)me.getKey());
        
        return Integer.toHexString(rgb[0])+" "+Integer.toHexString(rgb[1])+" "+Integer.toHexString(rgb[2]);        
    }    


    public static int[] getRGBArr(int pixel) {
    	
        int red = (pixel >> 16) & 0xff;
        int green = (pixel >> 8) & 0xff;
        int blue = (pixel) & 0xff;
        
        return new int[]{red,green,blue};

  }

    public static boolean isGray(int[] rgbArr) {
    	
        int rgDiff = rgbArr[0] - rgbArr[1];
        int rbDiff = rgbArr[0] - rgbArr[2];

        int tolerance = 10;
        
        if (rgDiff > tolerance || rgDiff < -tolerance) 
            if (rbDiff > tolerance || rbDiff < -tolerance) { 
            	
                return false;
                
            }                
        
        return true;
    }

    
public String returnColour() {

	if (colour.length() == 6) {
		return colour.replaceAll("\\s", "");
	} else {
		return "ffffff";
	}
}

to get the hex simply call returnColour();

Solution 5 - Android

Add to dependencies

implementation 'androidx.palette:palette:1.0.0'

and..

 AppCompatImageView imageView = findViewById(R.id.image_view);
   
 Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
 Palette.from(bitmap).generate(palette -> {
      int vibrant = palette.getVibrantColor(0x000000); // <=== color you want
      int vibrantLight = palette.getLightVibrantColor(0x000000);
      int vibrantDark = palette.getDarkVibrantColor(0x000000);
      int muted = palette.getMutedColor(0x000000);
      int mutedLight = palette.getLightMutedColor(0x000000);
      int mutedDark = palette.getDarkMutedColor(0x000000);
 });

Solution 6 - Android

I wrote my own methods to get dominant color:

Method 1 (My technique)

  1. Reduce to ARGB_4444 color space

  2. Compute the maximum occurrence of individual RGB elements and obtaining 3 distinctive maximum values

  3. Combining maximum values to dominant RGB color

     public int getDominantColor1(Bitmap bitmap) {
    
     if (bitmap == null)
         throw new NullPointerException();
    
     int width = bitmap.getWidth();
     int height = bitmap.getHeight();
     int size = width * height;
     int pixels[] = new int[size];
    
     Bitmap bitmap2 = bitmap.copy(Bitmap.Config.ARGB_4444, false);
    
     bitmap2.getPixels(pixels, 0, width, 0, 0, width, height);
    
     final List<HashMap<Integer, Integer>> colorMap = new ArrayList<HashMap<Integer, Integer>>();
     colorMap.add(new HashMap<Integer, Integer>());
     colorMap.add(new HashMap<Integer, Integer>());
     colorMap.add(new HashMap<Integer, Integer>());
    
     int color = 0;
     int r = 0;
     int g = 0;
     int b = 0;
     Integer rC, gC, bC;
     for (int i = 0; i < pixels.length; i++) {
         color = pixels[i];
    
         r = Color.red(color);
         g = Color.green(color);
         b = Color.blue(color);
    
         rC = colorMap.get(0).get(r);
         if (rC == null)
             rC = 0;
         colorMap.get(0).put(r, ++rC);
    
         gC = colorMap.get(1).get(g);
         if (gC == null)
             gC = 0;
         colorMap.get(1).put(g, ++gC);
    
         bC = colorMap.get(2).get(b);
         if (bC == null)
             bC = 0;
         colorMap.get(2).put(b, ++bC);
     }
    
     int[] rgb = new int[3];
     for (int i = 0; i < 3; i++) {
         int max = 0;
         int val = 0;
         for (Map.Entry<Integer, Integer> entry : colorMap.get(i).entrySet()) {
             if (entry.getValue() > max) {
                 max = entry.getValue();
                 val = entry.getKey();
             }
         }
         rgb[i] = val;
     }
    
     int dominantColor = Color.rgb(rgb[0], rgb[1], rgb[2]);
    
     return dominantColor;
      }
    

Method 2 (Old technique)

  1. Reduce to ARGB_4444 color space

  2. Compute the occurrence of each color and finding the maximum one as dominant color

    public int getDominantColor2(Bitmap bitmap) {
    if (bitmap == null)
        throw new NullPointerException();
    
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int size = width * height;
    int pixels[] = new int[size];
    
    Bitmap bitmap2 = bitmap.copy(Bitmap.Config.ARGB_4444, false);
    
    bitmap2.getPixels(pixels, 0, width, 0, 0, width, height);
    
    HashMap<Integer, Integer> colorMap = new HashMap<Integer, Integer>();
    
    int color = 0;
    Integer count = 0;
    for (int i = 0; i < pixels.length; i++) {
        color = pixels[i];
        count = colorMap.get(color);
        if (count == null)
            count = 0;
        colorMap.put(color, ++count);
    }
    
    int dominantColor = 0;
    int max = 0;
    for (Map.Entry<Integer, Integer> entry : colorMap.entrySet()) {
        if (entry.getValue() > max) {
            max = entry.getValue();
            dominantColor = entry.getKey();
        }
    }
    return dominantColor;
    }
    

Solution 7 - Android

Loop through all the pixel's color data and average the color values, ignore anything that is a shade of grey or transparent. I believe that is what Microsoft does in Windows 7 based on a recent blog post.

edit
The blog post: http://blogs.msdn.com/b/oldnewthing/archive/2011/12/06/10244432.aspx

This link showing how Chrome picks the dominant color may also be helpful. http://www.quora.com/Google-Chrome/How-does-Chrome-pick-the-color-for-the-stripes-on-the-Most-visited-page-thumbnails

Solution 8 - Android

None of the other answers did the job for me, and I didn't rule out the cause of the problem.

This is what I ended up using:

public static int getDominantColor(Bitmap bitmap) {
	if (bitmap == null) {
		return Color.TRANSPARENT;
	}
	int width = bitmap.getWidth();
	int height = bitmap.getHeight();
	int size = width * height;
	int pixels[] = new int[size];
	//Bitmap bitmap2 = bitmap.copy(Bitmap.Config.ARGB_4444, false);
	bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
	int color;
	int r = 0;
	int g = 0;
	int b = 0;
	int a;
	int count = 0;
	for (int i = 0; i < pixels.length; i++) {
		color = pixels[i];
		a = Color.alpha(color);
		if (a > 0) {
			r += Color.red(color);
			g += Color.green(color);
			b += Color.blue(color);
			count++;
		}
	}
	r /= count;
	g /= count;
	b /= count;
	r = (r << 16) & 0x00FF0000;
	g = (g << 8) & 0x0000FF00;
	b = b & 0x000000FF;
	color = 0xFF000000 | r | g | b;
	return color;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiontylerView Question on Stackoverflow
Solution 1 - AndroidTony WickhamView Answer on Stackoverflow
Solution 2 - AndroidPdroidView Answer on Stackoverflow
Solution 3 - AndroidFrancisView Answer on Stackoverflow
Solution 4 - AndroidReid L SkipworthView Answer on Stackoverflow
Solution 5 - AndroidWilliaan LopesView Answer on Stackoverflow
Solution 6 - AndroidMohsen AfshinView Answer on Stackoverflow
Solution 7 - AndroidBradley UffnerView Answer on Stackoverflow
Solution 8 - AndroidrraallvvView Answer on Stackoverflow