Edit
To get a better feel for the Android Palette class, I decided to make a simple app to test some of its features - if you are interested, you can find the app on the Play Store: https://play.google.com/store/apps/details?id=com.tonyw.sampleapps.palettecolorextraction. Basically it just has images and the colors that the Palette class extracts (mentioned below), and you can add your own images to test as well. You can find my source code on Github: https://github.com/tony-w/PaletteColorExtraction
Screenshots
Original post
Can someone describe the differences between the colors that can be extracted from a Bitmap using Android's Palette class?
Is it just that the muted colors are duller than vibrant colors? Are dark and light supposed to better match Lollipop's dark and light material design themes, respectively?
Due to such a multitude of reasons and emotions, blue is truly the best color for mobile app design.
The primary color is the color displayed most frequently across your app's screens and components. The primary variant color is used to distinguish two elements of the app using the primary color, such as the top app bar and the system bar. The secondary color provides more ways to accent and distinguish your product.
The idea is simple. To bring the balance into the composition, the colors should be combined in the proportion of 60%–30%–10%. The biggest part should go to the dominant hue, the third of the composition takes secondary color and 10% percent goes to the color which helps to make the accents.
That's a really good question. If you look at the source code you can see that the different swatches are chosen from analyzing the HSL color profile of the pixels on the image, based on target ranges for luminance, saturation, and population (how many pixels in the image are represented by the swatch). It uses a weighted average calculation with preference given to luminance, then saturation, then population.
Generally speaking, vibrant colors are more saturated than muted colors, dark colors are darker, and light swatches are lighter. Which one you use depends on the overall effect you want.
Chris Banes wrote in his blog "Vibrant and Dark Vibrant are the ones that developers will use mostly" but in practice it isn't quite as simple as that.
One example I've found is the applyPalette
method in Romain Guy's sample app from Google IO 2014, although the code assumes the various swatches will be found (possibly because he's working with known images).
Depending on the image, it's possible that some swatch types won't be found, so be sure to account for that possibility in your code.
For example, you could try getting swatches from the palette in a specific order, e.g. for a Dark theme you might try getting Vibrant Dark, then Muted Dark, and then fall back on some default color.
If you want something that is a bit more predictable, it is also possible to grab the most represented color, like this:
public static Palette.Swatch getDominantSwatch(Palette palette) { // find most-represented swatch based on population return Collections.max(palette.getSwatches(), new Comparator<Palette.Swatch>() { @Override public int compare(Palette.Swatch sw1, Palette.Swatch sw2) { return Integer.compare(sw1.getPopulation(), sw2.getPopulation()); } }); }
The HSL values for each swatch are also accessible, so you could write a similar routine to choose, for example, the most saturated swatch that isn't the dominant color.
Using custom targets
Another thing I've found useful is to define some custom targets in addition to the 6 targets defined in Palette.Target
, with different weighting and target lightness and saturation values, in order to increase the chance of finding a useful color.
For example, you can ask Palette's quantizer to include a swatch for the most dominant color that meets the current filter criteria with a target like this:
public static final Target DOMINANT; static { DOMINANT = new Target.Builder().setPopulationWeight(1f) .setSaturationWeight(0f) .setLightnessWeight(0f) .setExclusive(false) .build(); }
And you can get some useful swatches that put a higher priority on lightness than the stock targets like this:
public static final Target DARK; public static final Target LIGHT; public static final Target NEUTRAL; static { DARK = new Target.Builder().setMinimumLightness(0f) .setTargetLightness(0.26f) .setMaximumLightness(0.5f) .setMinimumSaturation(0.1f) .setTargetSaturation(0.6f) .setMaximumSaturation(1f) .setPopulationWeight(0.18f) .setSaturationWeight(0.22f) .setLightnessWeight(0.60f) .setExclusive(false) .build(); LIGHT = new Target.Builder().setMinimumLightness(0.50f) .setTargetLightness(0.74f) .setMaximumLightness(1.0f) .setMinimumSaturation(0.1f) .setTargetSaturation(0.7f) .setMaximumSaturation(1f) .setPopulationWeight(0.18f) .setSaturationWeight(0.22f) .setLightnessWeight(0.60f) .setExclusive(false) .build(); NEUTRAL = new Target.Builder().setMinimumLightness(0.20f) .setTargetLightness(0.5f) .setMaximumLightness(0.8f) .setMinimumSaturation(0.1f) .setTargetSaturation(0.6f) .setMaximumSaturation(1f) .setPopulationWeight(0.18f) .setSaturationWeight(0.22f) .setLightnessWeight(0.60f) .setExclusive(false) .build(); }
You can access the swatch found for a custom target after it is generated by using Palette.getSwatchForTarget, for example:
Palette.Swatch neutral = Palette.getSwatchForTarget(NEUTRAL);
Be aware of the default filter
By default Palette
has a Palette.Filter
that rejects colors very near to black or white, as well as colors "very near to the red I line" which I believe is referring to isochronous colors that are difficult for people with red-blind color blindness.
My theory is that it rejects the near-white and near-black colors in order to help prevent those from being chosen as "saturated" colors when the saturation is weighted highly.
The result of this filter however is that no swatch will be found for images that consist entirely of almost white and/or almost black pixels, as well as a tendency to avoid choosing colors with a pinkish hue.
However, it is possible to remove this filter using Palette.Builder.clearFilters()
, and add your own filter with Palette.Builder.addFilter()
.
In my code I've chosen to make a second attempt at generating a palette if the first attempt doesn't return any swatches:
Palette palette = new Palette.Builder(bitmap).addTarget(DOMINANT) .addTarget(DARK) .addTarget(LIGHT) .addTarget(NEUTRAL) .generate(); if(palette.getSwatches().isEmpty()) { Log.v(TAG, "Getting alternate (UNFILTERED) palette."); palette = new Palette.Builder(bitmap).addTarget(DOMINANT) .addTarget(DARK) .addTarget(LIGHT) .addTarget(NEUTRAL) .clearFilters() /// allow isBlack(), isWhite(), isNearRedILine() .generate(); }
Chaining the attempts preserves the usefulness of the default filter in most cases, but still allows you to find swatches for images that the default would completely reject.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With