
Update There is a new library by Mateusz Kaflowski who was kind enough to post it in the comments. The tutorial below gives you information on developing your own color fetching script and how to tweak it to your liking, but if you want to make sure the colors match the notification exactly, I recommend you use his. More info here.
I was recently writing an Audiobook app and wanted to get dynamic colors based on the book cover similar to Android’s media style notifications. Obviously the best method is to use the Palette API, but implementing it for the most aesthetically pleasing colors with good contrast wasn’t immediately clear.

Some popular apps like Pocketcasts for Phonograph seem to get the most vibrant color they can find from the art and then darken it so the white buttons and text will have enough contrast. I used this method for previous apps since it was easy but this method bothered me since it was possible that the primary color wouldn’t match very well and often clashed with the notification.
To better match Android’s notifications we need to also fetch a complementary color from the art that has enough contrast for good readability.
To start, let’s generate a palette:
public static Palette generatePalette(Bitmap bitmap) { if (bitmap == null) return null; Palette palette = new Palette.Builder(bitmap) .generate(); if(palette.getSwatches().size() <= 1) { palette = new Palette.Builder(bitmap) .clearFilters() .generate(); } return palette; }
There are a couple things to note here. By default, the palette builder has filters that disable colors that are close to black or white or certain hues that might cause accessibility issues for color blind folks like me. The second part in green will clear all filters in case these are the only colors that can be found. You can even add your own filters using addFilter.
Next, we need to be able to easily figure out the distance between two LAB colors. We can do this easily with ColorUtils.
private static double calculateDistance(int color0, int color1) { double[] lab0 = new double[3]; double[] lab1 = new double[3]; ColorUtils.colorToLAB(color0, lab0); ColorUtils.colorToLAB(color1, lab1); return ColorUtils.distanceEuclidean(lab0, lab1); }
Now we need a way to compare different swatches to find the swatch with the greatest distance.
private static class distanceComparator implements Comparator<Palette.Swatch> { private int color; private distanceComparator(int color) { this.color = color; } @Override public int compare(Palette.Swatch swatch1, Palette.Swatch swatch2) { return (int)(calculateDistance(swatch2.getRgb(), color) - calculateDistance(swatch1.getRgb(), color)); } }
Okay. Now everything is set up to get the colors.
public static int[] getMatchingColors(Palette palette, int[]fallback, boolean invert) { if (palette != null) { if(palette.getDominantSwatch() != null) { Palette.Swatch swatch = palette.getDominantSwatch(); int swatchColor = swatch.getRgb(); int matchingColor = -1; List<Palette.Swatch> swatches = new ArrayList<>(palette.getSwatches()); Collections.sort(swatches, new distanceComparator(swatchColor)); for(Palette.Swatch contrastingSwatch : swatches) { Cat.d("Testing contrast to " + ColorUtils.calculateContrast(contrastingSwatch.getRgb(), swatchColor)); if(ColorUtils.calculateContrast(contrastingSwatch.getRgb(), swatchColor) > MIN_ALPHA_CONTRAST) { matchingColor = swatches.get(0).getRgb(); break; } } if(matchingColor == -1) matchingColor = swatch.getBodyTextColor(); if(invert) return new int[]{matchingColor, swatchColor}; return new int[]{swatchColor, matchingColor}; } } return fallback; }
Some explanation here. getDominantSwatch() gets the swatch generated that appears the most in the bitmap and is close to what notifications use for the background.
Next we can get a list of all the swatches that were generated in the palette and use the comparator we made earlier to sort them by the greatest distance. We can then loop through them making sure the swatch also has a high enough contrast for readability. More information can be found in the Material guidelines for this.
If no matching color can be found, we can use getBodyTextColor() to get a guaranteed contrasting color. (this will either be white or black with some alpha)
I also added an invert option to my function in case a user wants to invert the colors for their audiobook to make it lighter or darker.

The result gives you colors that are generally very close to the notification, but may still not match up perfectly. I think there are a couple things that may factor into the difference such as the bitmap being resized. I suspect the notification may also be getting the dominant color from the left side of the image for the gradient it uses. You can try to get these to match better if you want, but I think the colors generated with this method will get as good results.
Do you have any other methods for generating colors that you use?
Thanks for the post, But colors you return don’t match android media style colors
I address this in the last paragraph of the post. I don’t know there code exactly, so I’m not sure exactly
I found that the colors form palette are precessed by system methods so I found them in the AOSP and used them to build that library:
https://github.com/mkaflowski/Media-Style-Palette/blob/master/README.md
Thanks for the library. I’d like to try it out, but syncing always fails when trying to follow your instructions in the documentation. Could you test this to see if it works for you?
Thanks!
-Joel
The version 1.1 should work now.
Thanks! I got it to work. I’ve updated the post to let people know about it.
Thank you 🙂
mkaflowski I think yours is a little off
all thou I think in this albums case yours is better also anyone know how i can fade the image to the color like that from the right thou?
JOELPHILIPPAGE would it be possible for you to whip up a example app like mkaflowski did with just one image though.
I know that’s asking a lot. Thanks
Just with the script I posted above?
Ya just set like a background the dominate color and a textview the contrast color. Thank you .