Thursday, March 24, 2011

How to automatically generate N "distinct" colors?

I wrote the two methods below to automatically select N distinct colors. It works by defining a piecewise linear function on the RGB cube. The benefit of this is you can also get a progressive scale if that's what you want, but when N gets large the colors can start to look similar. I can also imagine evenly subdividing the RGB cube into a lattice and then drawing points. Does anyone know any other methods? I'm ruling out defining a list and then just cycling through it. I should also say I don't generally care if they clash or don't look nice, they just have to be visually distinct.

public static List<Color> pick(int num) {
 List<Color> colors = new ArrayList<Color>();
 if (num < 2)
  return colors;
 float dx = 1.0f / (float) (num - 1);
 for (int i = 0; i < num; i++) {
  colors.add(get(i * dx));
 }
 return colors;
}

public static Color get(float x) {
 float r = 0.0f;
 float g = 0.0f;
 float b = 1.0f;
 if (x >= 0.0f && x < 0.2f) {
  x = x / 0.2f;
  r = 0.0f;
  g = x;
  b = 1.0f;
 } else if (x >= 0.2f && x < 0.4f) {
  x = (x - 0.2f) / 0.2f;
  r = 0.0f;
  g = 1.0f;
  b = 1.0f - x;
 } else if (x >= 0.4f && x < 0.6f) {
  x = (x - 0.4f) / 0.2f;
  r = x;
  g = 1.0f;
  b = 0.0f;
 } else if (x >= 0.6f && x < 0.8f) {
  x = (x - 0.6f) / 0.2f;
  r = 1.0f;
  g = 1.0f - x;
  b = 0.0f;
 } else if (x >= 0.8f && x <= 1.0f) {
  x = (x - 0.8f) / 0.2f;
  r = 1.0f;
  g = 0.0f;
  b = x;
 }
 return new Color(r, g, b);
}
From stackoverflow
  • If N is big enough, you're going to get some similar-looking colors. There's only so many of them in the world.

    Why not just evenly distribute them through the spectrum, like so:

    IEnumerable<Color> CreateUniqueColors(int nColors)
    {
        int subdivision = (int)Math.Floor(Math.Pow(nColors, 1/3d));
        for(int r = 0; r < 255; r += subdivision)
            for(int g = 0; g < 255; g += subdivision)
                for(int b = 0; b < 255; b += subdivision)
                    yield return Color.FromArgb(r, g, b);
    }
    

    If you want to mix up the sequence so that similar colors aren't next to each other, you could maybe shuffle the resulting list.

    Am I underthinking this?

  • 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);
    }
    
    mquander : This technique is smart. I bet it'll get more aesthetic results than mine.
  • Here's an idea. Imagine an HSV cylinder

    Define the upper and lower limits you want for the Brightness and Saturation. This defines a square cross section ring within the space.

    Now, scatter N points randomly within this space.

    Then apply an iterative repulsion algorithm on them, either for a fixed number of iterations, or until the points stabilise.

    Now you should have N points representing N colours that are about as different as possible within the colour space you're interested in.

    Hugo

  • Here's a solution to managed your "distinct" issue, which is entirely overblown:

    Create a unit sphere and drop points on it with repelling charges. Run a particle system until they no longer move (or the delta is "small enough"). At this point, each of the points are as far away from each other as possible. Convert (x, y, z) to rgb.

    I mention it because for certain classes of problems, this type of solution can work better than brute force.

    I originally saw this approach here for tesselating a sphere.

    Again, the most obvious solutions of traversing HSL space or RGB space will probably work just fine.

    Rocketmagnet : That's a good idea, but it probably makes sense to use a cube, rather than a sphere.

0 comments:

Post a Comment