This is something I've pseudo-solved many times and never quite found a solution that's stuck with me. The problem is to come up with a way to generate N colors that are as distinguishable as possible where N is a parameter.
I've read somewhere the human eye can't distinguish between less than 4 values apart. so This is something to keep in mind. The following algorithm does not compensate for this.
I'm not sure this is exactly what you want, but this is one way to randomly generate non-repeating color values:
(beware, inconsistent pseudo-code ahead)
//colors entered as 0-255 [R, G, B]
colors = []; //holds final colors to be used
rand = new Random();
//assumes n is less than 16,777,216
randomGen(int n){
while (len(colors) < n){
//generate a random number between 0,255 for each color
newRed = rand.next(256);
newGreen = rand.next(256);
newBlue = rand.next(256);
temp = [newRed, newGreen, newBlue];
//only adds new colors to the array
if temp not in colors {
colors.append(temp);
}
}
}
One way you could optimize this for better visibility would be to compare the distance between each new color and all the colors in the array:
for item in color{
itemSq = (item[0]^2 + item[1]^2 + item[2]^2])^(.5);
tempSq = (temp[0]^2 + temp[1]^2 + temp[2]^2])^(.5);
dist = itemSq - tempSq;
dist = abs(dist);
}
//NUMBER can be your chosen distance apart.
if dist < NUMBER and temp not in colors {
colors.append(temp);
}
But this approach would significantly slow down your algorithm.
Another way would be to scrap the randomness and systematically go through every 4 values and add a color to an array in the above example.
Following the algorithm above, what if you took the wheel and divided it by the N colors you wanted. If you wanted two colors, do one at 0,0,0 and the next at 256,256,256? 3 colors is 0,0,0 next at 128,128,128, next at 256,256,256 and so on?
Naturally you'll hit some limit that it is indistinguishable, but you'll be asking for a LOT of colors at that point.
@Dillie-O
Those are just grey colors (unless I misunderstood your answer). But I think you are on the right track...
The normal algorithm takes a color-wheel you sample it at equal angles. If you need N colors, you sample it at 360/N and you get a few good colors. This is really only a good solution to up ~12 or so. And there are TONS of colors that get ignored once you go passed that, while getting more and more shades of the same.
The problem is that the color wheel is only one path through the color space and so points equidistant on the color-wheel are still confined to a narrow part of the fuller color space.
Isn't it also a factor which order you set up the colors?
Like if you use Dillie-Os idea you need to mix the colors as much as possible. 0 64 128 256 is from one to the next. but 0 256 64 128 in a wheel would be more "apart"
Does this make sense?
I'm accepting nlucaroni's answer with two additional questions (that I'll ask as separate questions)
First, is RGB the best color space?
Once you've generated N maximally separated vectors how do you sort them such that the first M are also relatively distinct.
It would be best to find colors maximally distant in a "perceptually uniform" colorspace, e.g. CIELAB (using Euclidean distance between L*, a*, b* coordinates as your distance metric) and then converting to the colorspace of your choice. Perceptually uniformity comes from tweaking the colorspace to approximate the non-linearities in the human visual system.
Some related resources:
ColorBrewer - Sets of colours designed to be maximally distinguishable for use on maps.
Escaping RGBland: Selecting Colors for Statistical Graphics - A technical report describing a set of algorithms for generating good (i.e. maximally distinguishable) colour sets in the hcl colour space.
Here is some code to allocate RGB colors evenly around a HSL color wheel of specified luminosity.
class cColorPicker
{
public:
void Pick( vector<DWORD>&v_picked_cols, int count, int bright = 50 );
private:
DWORD HSL2RGB( int h, int s, int v );
unsigned char ToRGB1(float rm1, float rm2, float rh);
};
/**
Evenly allocate RGB colors around HSL color wheel
@param[out] v_picked_cols a vector of colors in RGB format
@param[in] count number of colors required
@param[in] bright 0 is all black, 100 is all white, defaults to 50
based on Fig 3 of http://epub.wu-wien.ac.at/dyn/virlib/wp/eng/mediate/epub-wu-01_c87.pdf?ID=epub-wu-01_c87
*/
void cColorPicker::Pick( vector<DWORD>&v_picked_cols, int count, int bright )
{
v_picked_cols.clear();
for( int k_hue = 0; k_hue < 360; k_hue += 360/count )
v_picked_cols.push_back( HSV2RGB( k_hue, 100, bright ) );
}
/**
Convert HSL to RGB
based on http://www.codeguru.com/code/legacy/gdi/colorapp_src.zip
*/
DWORD cColorPicker::HSL2RGB( int h, int s, int l )
{
DWORD ret = 0;
unsigned char r,g,b;
float saturation = s / 100.0f;
float luminance = l / 100.f;
float hue = (float)h;
if (saturation == 0.0)
{
r = g = b = unsigned char(luminance * 255.0);
}
else
{
float rm1, rm2;
if (luminance <= 0.5f) rm2 = luminance + luminance * saturation;
else rm2 = luminance + saturation - luminance * saturation;
rm1 = 2.0f * luminance - rm2;
r = ToRGB1(rm1, rm2, hue + 120.0f);
g = ToRGB1(rm1, rm2, hue);
b = ToRGB1(rm1, rm2, hue - 120.0f);
}
ret = ((DWORD)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)));
return ret;
}
unsigned char cColorPicker::ToRGB1(float rm1, float rm2, float rh)
{
if (rh > 360.0f) rh -= 360.0f;
else if (rh < 0.0f) rh += 360.0f;
if (rh < 60.0f) rm1 = rm1 + (rm2 - rm1) * rh / 60.0f;
else if (rh < 180.0f) rm1 = rm2;
else if (rh < 240.0f) rm1 = rm1 + (rm2 - rm1) * (240.0f - rh) / 60.0f;
return static_cast<unsigned char>(rm1 * 255);
}
int _tmain(int argc, _TCHAR* argv[])
{
vector<DWORD> myCols;
cColorPicker colpick;
colpick.Pick( myCols, 20 );
for( int k = 0; k < (int)myCols.size(); k++ )
printf("%d: %d %d %d\n", k+1,
( myCols[k] & 0xFF0000 ) >>16,
( myCols[k] & 0xFF00 ) >>8,
( myCols[k] & 0xFF ) );
return 0;
}
Last I checked JFreeChart has this precise algorithm and as it is open source you can check out what it does. I do know that the colors I get do not seem to be randomly spaced along some circle or sphere, but rather chosen more specifically.