How to generate n different colors for any natural number n?

28,684

Solution 1

100 is a lot of colours, but you might be able to do it by distributing them as sparsely as possible in the HSB or HSL space; doing it in RGB is probably difficult.

For example, you might decide to use 10 different hues, 4 different saturation levels, and 3 different brightness settings, that would give you up to 120 colours. You'll need to pick the saturation and brightness values carefully; human eyes are complicated and confusing sensors. If you treat the colour space as a cone, you will probably want a different number of hues at each lightness/saturation level.

Here's a link to the wikipedia entry on HSB.

Solution 2

Yeah. Defining distinct is a product of deferring to a color space then when we say maximally distinct colors what we mean to say is colors which are as far from all the other colors as possible. But since the color space doesn't change the answer isn't going to change. And implementing something that better fits with human eyes and how human eyes see color like CIE-lab de2000 color distance makes redoing all the calculations hard, but makes a static list easy. Here's 128 entries.

private static final String[] indexcolors = new String[]{
        "#000000", "#FFFF00", "#1CE6FF", "#FF34FF", "#FF4A46", "#008941", "#006FA6", "#A30059",
        "#FFDBE5", "#7A4900", "#0000A6", "#63FFAC", "#B79762", "#004D43", "#8FB0FF", "#997D87",
        "#5A0007", "#809693", "#FEFFE6", "#1B4400", "#4FC601", "#3B5DFF", "#4A3B53", "#FF2F80",
        "#61615A", "#BA0900", "#6B7900", "#00C2A0", "#FFAA92", "#FF90C9", "#B903AA", "#D16100",
        "#DDEFFF", "#000035", "#7B4F4B", "#A1C299", "#300018", "#0AA6D8", "#013349", "#00846F",
        "#372101", "#FFB500", "#C2FFED", "#A079BF", "#CC0744", "#C0B9B2", "#C2FF99", "#001E09",
        "#00489C", "#6F0062", "#0CBD66", "#EEC3FF", "#456D75", "#B77B68", "#7A87A1", "#788D66",
        "#885578", "#FAD09F", "#FF8A9A", "#D157A0", "#BEC459", "#456648", "#0086ED", "#886F4C",
        
        "#34362D", "#B4A8BD", "#00A6AA", "#452C2C", "#636375", "#A3C8C9", "#FF913F", "#938A81",
        "#575329", "#00FECF", "#B05B6F", "#8CD0FF", "#3B9700", "#04F757", "#C8A1A1", "#1E6E00",
        "#7900D7", "#A77500", "#6367A9", "#A05837", "#6B002C", "#772600", "#D790FF", "#9B9700",
        "#549E79", "#FFF69F", "#201625", "#72418F", "#BC23FF", "#99ADC0", "#3A2465", "#922329",
        "#5B4534", "#FDE8DC", "#404E55", "#0089A3", "#CB7E98", "#A4E804", "#324E72", "#6A3A4C",
        "#83AB58", "#001C1E", "#D1F7CE", "#004B28", "#C8D0F6", "#A3A489", "#806C66", "#222800",
        "#BF5650", "#E83000", "#66796D", "#DA007C", "#FF1A59", "#8ADBB4", "#1E0200", "#5B4E51",
        "#C895C5", "#320033", "#FF6832", "#66E1D3", "#CFCDAC", "#D0AC94", "#7ED379", "#012C58"
};

Here's the first 256 as an image.

max distance

(left-to-right) (top-to-bottom). You might be able to get a few more distinct colors if you made sure each color was as equidistant as possible within the colorspace. That lookup table picks each additional color as maximally distinct from all previous colors rather than designating the N at the start and then mapping out the colorspace. So yeah, brute force and a high level color distance algorithm and you're set to make this same set of colors yourself. Over the course of a day or so.


If you do set lists and make them equidistant you can get a distinct number between different colors for example, 5 colors, Beats the default list at 5 colors which was at min_delta_max 53.2 with min_delta_max 61.5

Or the colors in the list, 10 color list, #156FC3 #165859 #24C4FF #30A581 #957D5C #213E02 #DE9AF5 #68D840 #6E0062 #C25B77 which exceeds the first ten elements in the precomputed list.

If you wanted to try this instead: https://gist.github.com/tatarize/a483db49993e6e0e994ad82ba3e2a22e

Can be edited to take num_of_colors, and you can run it for a long time and get a set of colors which should have a lower overall min_delta_max (the biggest maximum minimum distances between any two colors in the list). You'd still want a precompiled list.

Solution 3

Edit:

I don't have any expertise in this area and my math skills are pretty average. But I have the opinion that the solution to this problem is more complex and interesting than many answers here suggest, since I tried to do something similar recently and didn't find a solution.

Color Difference

The perception of color is of course subjective, but there is significant agreement between humans. For example, we can all agree that red, green and blue are very different colors, and even colorblind people agree that black and white are very different.

RGB

The most common representation of color in computer systems is the vector (r, g, b) which suggests a simple distance function like

RGB color difference

Lets set the range for r, g and b to [0, 1] and see how this works:

  1. Red (1, 0, 0) and red (1, 0, 0) has the distance of 0, which should be obvious
  2. Red (1, 0, 0) and yellow (1, 1, 0) has the distance of 1, which is smaller than the distance of
  3. Red (1, 0, 0) and blue (0, 0, 1) which is sqrt(2), which is plausible

So far, so good. The problem however is that blue and red have the same distance 1 from black (0, 0, 0), but when looking at the image this doesn't seem to hold true:

blue and red on black

Also yellow (1, 1, 0) and magenta (1, 0, 1) both have have the same distance 1 from white (1, 1, 1), which doesn't seem to make sense either:

yellow and magenta on white

HSL and HSV

I think it is safe to assume that analogue metrics for the HSL and HSV color schemes have the same problems. These color schemes aren't designed for comparing color.

CIEDE2000

Luckily, there are scientists already trying to find a good way to compare colors. They came up with some elaborate methods, the latest one being CIEDE2000

CIEDE2000

(the full formula described in the article is huge)

This metric takes human perception into consideration, like the fact that we seem to be unable to discern shades of blue very well. So I'd say we use this as our color difference function.

The Color Picking Algorithm

Naive solution

Some answers suggested the following algorithm

colors = []
for n in range(n):
    success=False
    while not success:
        new_color = random_color()
        for color in colors:
            if distance(color, new_color)>far_enough:
                colors.append(new_color)
                success = True
                break

This algorithm has some problems:

  1. The spacing of the colors isn't optimal. If we imagine the colors to be like numbers on a line, three numbers would be optimally spaced like this:

    |a-----b-----c|

    Packing an additional one number in there without moving a, b, and c is clearly worse than realigning all the colors.

  2. The algorithm isn't guaranteed to terminate. What if there is no color that is far enough form the existing colors in the list? The loop will continue forever

Proper solution

Well.. I don't have one.

Solution 4

You want to convert to HSL and then iterate through the values of the hue (H) while keeping the other 2 values constant.

For each value you convert from HSL back to RGB.

See my answers here and here.

If your N is very large and therefore the colors are NOT visually distinct you could at that point re-iterate over all of the same hues and change the other components to vary the saturation or luminosity. So basically you could have a max number of hue values to use, and once that is hit you can start over with a different saturation or luminosity.

Solution 5

Not an answer to your question, but, if n has a maximum value and your application allows it, you could use a predefined list of colors like this:

http://en.wikipedia.org/wiki/List_of_colors

One advantage is that you could show a humanly readable color name in a tooltip for people with color blindness.

Share:
28,684
deostroll
Author by

deostroll

You are probably reading this space for the WRONG reasons...statistically speaking. Why? Because the SO community isn't so welcoming... But we can make this work...just hang in there... :) https://codeblog.jonskeet.uk/2018/03/17/stack-overflow-culture/amp/ (At least, read the "Jon’s Stack Overflow Covenant" section) About me Software engineer. Loves JavaScript. Python. Science. Astronomy. IoT. Programming. Movies. Computers. Married. 1 Kid. From Kerala, India. Works in Bangalore. More ways to connect -> https://plus.google.com/+ArunJayapal (linkedin, facebook, twitter, blah, blah)

Updated on July 10, 2022

Comments

  • deostroll
    deostroll over 1 year

    Say n = 100; How do I generate 100 visually distinct colors? Is this mathematically possible?

  • Otto Allmendinger
    Otto Allmendinger about 14 years
    This doesn't utilize less bright and less saturated colors
  • Brian R. Bondy
    Brian R. Bondy about 14 years
    If you want to, you easily can by randomizing those values while keeping a good mix of the hue.
  • kennytm
    kennytm about 14 years
    @Brian: That will easily generate lots of gray-like colors.
  • Brian R. Bondy
    Brian R. Bondy about 14 years
    @KennyTM no it won't. My original suggestion was to simply keep the saturation and luminosity at constant values and iterate your hue values. Of course you can randomize them slightly a little if you'd like to address @Otto Allmendinger's concern.
  • Jason Orendorff
    Jason Orendorff about 14 years
    The problem with doing this in HSL space is that all colors with 0 lightness are the same; and colors with the same lightness and 0 saturation are the same. So points that seem to be far apart turn out not to be visually distinct. It actually seems better to me to distribute the colors sparsely in RGB.
  • kennytm
    kennytm about 14 years
    @Brian: So you're using a very small subset of colors which easily causes visually-indistinguishable colors.
  • Brian R. Bondy
    Brian R. Bondy about 14 years
    @KennyTM: Agree, updated to address that problem.
  • kennytm
    kennytm about 14 years
    @Jason: Who said you need to treat the color space as a cube? (Although RGB is easier.)
  • Carl Norum
    Carl Norum about 14 years
    Yeah, 'as sparsely as possible' is probably not what I meant to say. The second paragraph clears things up a bit, but I'll edit that end clean it up.
  • Carl Norum
    Carl Norum about 14 years
    I take it back, I do mean 'as sparsely as possible'. Just treat the HSB colour space as a cone.
  • Otto Allmendinger
    Otto Allmendinger about 14 years
    If he needs to choose 3 colors, how does he prevent picking "Alizarin" and "Amaranth" and "Carmine" which nearly look the same?
  • Carlos Gutiérrez
    Carlos Gutiérrez about 14 years
    @Otto - Using the formula for Color Difference you posted, or using a list including only colors that "look different". (BTW the three you mention look very distinct to me, but there are others that I see as identical, genetics I guess)
  • Carlos Gutiérrez
    Carlos Gutiérrez about 14 years
    This page is intended to provide helpful information for correct implementation of the CIEDE2000 color-difference formula. ece.rochester.edu/~gsharma/ciede2000
  • Otto Allmendinger
    Otto Allmendinger about 14 years
    well, we can agree that they are all red which is too close. The color difference equation is just one piece of the puzzle, you have to come up with a better method than picking random colors sequentially and checking if they are too close to existing colors (I'll write up the details in my answer)
  • Mark Ransom
    Mark Ransom about 14 years
    It's tempting to use the LAB color space, but many of those coordinates don't map back to RGB space or the human visible gamut - en.wikipedia.org/wiki/Lab_color_space . That makes it difficult to choose random colors from it.
  • Phrogz
    Phrogz almost 11 years
    Using CIE is exactly what I did for my online tool here: phrogz.net/css/distinct-colors.html
  • Phrogz
    Phrogz almost 11 years
    The real problem with HSB (or HSL)--whether as a cube, cone or sphere--is that hue values distributed by equal numeric separation do not produce equal amounts of visual separation. HSB is way better than using RGB, but you need non-linear mappings along the axes to get the best visual separation.
  • kumarharsh
    kumarharsh over 9 years
    Do take a look into the Lab and HCL colourspaces (HCL is perhaps the best one for generating colours for humans, but it has a concave domain). A notable tool is the iWantHue tool by medialab: tools.medialab.sciences-po.fr/iwanthue
  • Martin Thoma
    Martin Thoma about 9 years
    Sometimes simple is beautiful. Thanks.
  • Martin Thoma
    Martin Thoma about 9 years
    Something is wrong with your image. FFFF00 is certainly not lime.
  • Tatarize
    Tatarize about 9 years
    I ran the code on two different instances and changed an equals so it seems to have gone with "#FFFF00" as the second color. With a second color as yellow rather than green, it bounced around to different values. They are both technically correct. As they are maximally distant but there are apparently different valid answers for what color is the most unlike black. Depending on whether my value is >= current furthest or just > current furthest. I'll update the graphic.
  • Tatarize
    Tatarize about 9 years
    Not super sure, but triple checked the code. It produced the given static list. Exported a newer color image using the correct values with a lot more values (since I ran it up into the high 200s).
  • Christopher Galpin
    Christopher Galpin about 2 years
    @Tatarize I'm using the first 5 after black and they're superb, thanks! Though now you have me really curious how they would differ if just these 5 (or 6) were generated equidistant as you mentioned. I looked for code on your blog but didn't find any so I may never know. 😉
  • Tatarize
    Tatarize about 2 years
    @ChristopherGalpin I cannot guarantee a result before the heat death of the universe but here's some python code to give you an answer. gist.github.com/tatarize/ade134305086663b0503f0c413af77e8 Keep in mind when, I ran the above brute force thing it took me a month, though each got progressively harder with more results. And the attempt here is to for phase 2, manipulate each color to gradually improve the whole, which means 6 * 3 different color components need either a +1, 0, -1 across the entire set of results. And to keep trying until improvement stops.
  • Tatarize
    Tatarize about 2 years
    user-images.githubusercontent.com/3302478/… provides a somewhat naive early result there. The distances between each additional goes 101.2, 79.6, 58.3, 53.2. This quickly goes up to 54 before it hits a bunch of code that seems to take ages since its trying to many bit manipulations.
  • Tatarize
    Tatarize about 2 years
    Combined the pair checking code with some brute force adjustment code to unjam things it still needs to brute force prove 18 different color components can't get changed across 3 different operations. 56.429232872431825: #002600 #C5FF00 #06E6FD #C505FF #FF4846
  • Tatarize
    Tatarize about 2 years
    Ran it for a day. At 5, your values are: #00004A #FEFF2C #FF8AFF #00FFF5 #AE1400. This is about 61.62 rather than 53.2 or so.