How to automatically generate N "distinct" colors?
This questions appears in quite a few SO discussions:
- Algorithm For Generating Unique Colors
- Generate unique colours
- Generate distinctly different RGB colors in graphs
- How to generate n different colors for any natural number n?
Different solutions are proposed, but none are optimal. Luckily, science comes to the rescue
Arbitrary N
- Colour displays for categorical images (free download)
- A WEB SERVICE TO PERSONALISE MAP COLOURING (free download, a webservice solution should be available by next month)
- An Algorithm for the Selection of High-Contrast Color Sets (the authors offer a free C++ implementation)
- High-contrast sets of colors (The first algorithm for the problem)
The last 2 will be free via most university libraries / proxies.
N is finite and relatively small
In this case, one could go for a list solution. A very interesting article in the subject is freely available:
There are several color lists to consider:
- Boynton's list of 11 colors that are almost never confused (available in the first paper of the previous section)
- Kelly's 22 colors of maximum contrast (available in the paper above)
I also ran into this Palette by an MIT student.Lastly, The following links may be useful in converting between different color systems / coordinates (some colors in the articles are not specified in RGB, for instance):
- http://chem8.org/uch/space-55036-do-blog-id-5333.html
- https://metacpan.org/pod/Color::Library::Dictionary::NBS_ISCC
- Color Theory: How to convert Munsell HVC to RGB/HSB/HSL
For Kelly's and Boynton's list, I've already made the conversion to RGB (with the exception of white and black, which should be obvious). Some C# code:
public static ReadOnlyCollection<Color> KellysMaxContrastSet{ get { return _kellysMaxContrastSet.AsReadOnly(); }}private static readonly List<Color> _kellysMaxContrastSet = new List<Color>{ UIntToColor(0xFFFFB300), //Vivid Yellow UIntToColor(0xFF803E75), //Strong Purple UIntToColor(0xFFFF6800), //Vivid Orange UIntToColor(0xFFA6BDD7), //Very Light Blue UIntToColor(0xFFC10020), //Vivid Red UIntToColor(0xFFCEA262), //Grayish Yellow UIntToColor(0xFF817066), //Medium Gray //The following will not be good for people with defective color vision UIntToColor(0xFF007D34), //Vivid Green UIntToColor(0xFFF6768E), //Strong Purplish Pink UIntToColor(0xFF00538A), //Strong Blue UIntToColor(0xFFFF7A5C), //Strong Yellowish Pink UIntToColor(0xFF53377A), //Strong Violet UIntToColor(0xFFFF8E00), //Vivid Orange Yellow UIntToColor(0xFFB32851), //Strong Purplish Red UIntToColor(0xFFF4C800), //Vivid Greenish Yellow UIntToColor(0xFF7F180D), //Strong Reddish Brown UIntToColor(0xFF93AA00), //Vivid Yellowish Green UIntToColor(0xFF593315), //Deep Yellowish Brown UIntToColor(0xFFF13A13), //Vivid Reddish Orange UIntToColor(0xFF232C16), //Dark Olive Green};public static ReadOnlyCollection<Color> BoyntonOptimized{ get { return _boyntonOptimized.AsReadOnly(); }}private static readonly List<Color> _boyntonOptimized = new List<Color>{ Color.FromArgb(0, 0, 255), //Blue Color.FromArgb(255, 0, 0), //Red Color.FromArgb(0, 255, 0), //Green Color.FromArgb(255, 255, 0), //Yellow Color.FromArgb(255, 0, 255), //Magenta Color.FromArgb(255, 128, 128), //Pink Color.FromArgb(128, 128, 128), //Gray Color.FromArgb(128, 0, 0), //Brown Color.FromArgb(255, 128, 0), //Orange};static public Color UIntToColor(uint color){ var a = (byte)(color >> 24); var r = (byte)(color >> 16); var g = (byte)(color >> 8); var b = (byte)(color >> 0); return Color.FromArgb(a, r, g, b);}
And here are the RGB values in hex and 8-bit-per-channel representations:
kelly_colors_hex = [ 0xFFB300, # Vivid Yellow 0x803E75, # Strong Purple 0xFF6800, # Vivid Orange 0xA6BDD7, # Very Light Blue 0xC10020, # Vivid Red 0xCEA262, # Grayish Yellow 0x817066, # Medium Gray # The following don't work well for people with defective color vision 0x007D34, # Vivid Green 0xF6768E, # Strong Purplish Pink 0x00538A, # Strong Blue 0xFF7A5C, # Strong Yellowish Pink 0x53377A, # Strong Violet 0xFF8E00, # Vivid Orange Yellow 0xB32851, # Strong Purplish Red 0xF4C800, # Vivid Greenish Yellow 0x7F180D, # Strong Reddish Brown 0x93AA00, # Vivid Yellowish Green 0x593315, # Deep Yellowish Brown 0xF13A13, # Vivid Reddish Orange 0x232C16, # Dark Olive Green ]kelly_colors = dict(vivid_yellow=(255, 179, 0), strong_purple=(128, 62, 117), vivid_orange=(255, 104, 0), very_light_blue=(166, 189, 215), vivid_red=(193, 0, 32), grayish_yellow=(206, 162, 98), medium_gray=(129, 112, 102), # these aren't good for people with defective color vision: vivid_green=(0, 125, 52), strong_purplish_pink=(246, 118, 142), strong_blue=(0, 83, 138), strong_yellowish_pink=(255, 122, 92), strong_violet=(83, 55, 122), vivid_orange_yellow=(255, 142, 0), strong_purplish_red=(179, 40, 81), vivid_greenish_yellow=(244, 200, 0), strong_reddish_brown=(127, 24, 13), vivid_yellowish_green=(147, 170, 0), deep_yellowish_brown=(89, 51, 21), vivid_reddish_orange=(241, 58, 19), dark_olive_green=(35, 44, 22))
For all you Java developers, here are the JavaFX colors:
// Don't forget to import javafx.scene.paint.Color;private static final Color[] KELLY_COLORS = { Color.web("0xFFB300"), // Vivid Yellow Color.web("0x803E75"), // Strong Purple Color.web("0xFF6800"), // Vivid Orange Color.web("0xA6BDD7"), // Very Light Blue Color.web("0xC10020"), // Vivid Red Color.web("0xCEA262"), // Grayish Yellow Color.web("0x817066"), // Medium Gray Color.web("0x007D34"), // Vivid Green Color.web("0xF6768E"), // Strong Purplish Pink Color.web("0x00538A"), // Strong Blue Color.web("0xFF7A5C"), // Strong Yellowish Pink Color.web("0x53377A"), // Strong Violet Color.web("0xFF8E00"), // Vivid Orange Yellow Color.web("0xB32851"), // Strong Purplish Red Color.web("0xF4C800"), // Vivid Greenish Yellow Color.web("0x7F180D"), // Strong Reddish Brown Color.web("0x93AA00"), // Vivid Yellowish Green Color.web("0x593315"), // Deep Yellowish Brown Color.web("0xF13A13"), // Vivid Reddish Orange Color.web("0x232C16"), // Dark Olive Green};
the following is the unsorted kelly colors according to the order above.
the following is the sorted kelly colors according to hues (note that some yellows are not very contrasting)
You can use the HSL color model to create your colors.
If all you want is differing hues (likely), and slight variations on lightness or saturation, you can distribute the hues like so:
// assumes hue [0, 360), saturation [0, 100), lightness [0, 100)for(i = 0; i < 360; i += 360 / num_colors) { HSLColor c; c.hue = i; c.saturation = 90 + randf() * 10; c.lightness = 50 + randf() * 10; addColor(c);}
Like Uri Cohen's answer, but is a generator instead. Will start by using colors far apart. Deterministic.
Sample, left colors first:
#!/usr/bin/env python3from typing import Iterable, Tupleimport colorsysimport itertoolsfrom fractions import Fractionfrom pprint import pprintdef zenos_dichotomy() -> Iterable[Fraction]: """ http://en.wikipedia.org/wiki/1/2_%2B_1/4_%2B_1/8_%2B_1/16_%2B_%C2%B7_%C2%B7_%C2%B7 """ for k in itertools.count(): yield Fraction(1,2**k)def fracs() -> Iterable[Fraction]: """ [Fraction(0, 1), Fraction(1, 2), Fraction(1, 4), Fraction(3, 4), Fraction(1, 8), Fraction(3, 8), Fraction(5, 8), Fraction(7, 8), Fraction(1, 16), Fraction(3, 16), ...] [0.0, 0.5, 0.25, 0.75, 0.125, 0.375, 0.625, 0.875, 0.0625, 0.1875, ...] """ yield Fraction(0) for k in zenos_dichotomy(): i = k.denominator # [1,2,4,8,16,...] for j in range(1,i,2): yield Fraction(j,i)# can be used for the v in hsv to map linear values 0..1 to something that looks equidistant# bias = lambda x: (math.sqrt(x/3)/Fraction(2,3)+Fraction(1,3))/Fraction(6,5)HSVTuple = Tuple[Fraction, Fraction, Fraction]RGBTuple = Tuple[float, float, float]def hue_to_tones(h: Fraction) -> Iterable[HSVTuple]: for s in [Fraction(6,10)]: # optionally use range for v in [Fraction(8,10),Fraction(5,10)]: # could use range too yield (h, s, v) # use bias for v here if you use rangedef hsv_to_rgb(x: HSVTuple) -> RGBTuple: return colorsys.hsv_to_rgb(*map(float, x))flatten = itertools.chain.from_iterabledef hsvs() -> Iterable[HSVTuple]: return flatten(map(hue_to_tones, fracs()))def rgbs() -> Iterable[RGBTuple]: return map(hsv_to_rgb, hsvs())def rgb_to_css(x: RGBTuple) -> str: uint8tuple = map(lambda y: int(y*255), x) return "rgb({},{},{})".format(*uint8tuple)def css_colors() -> Iterable[str]: return map(rgb_to_css, rgbs())if __name__ == "__main__": # sample 100 colors in css format sample_colors = list(itertools.islice(css_colors(), 100)) pprint(sample_colors)