Version 0 of Selecting visually different RGB colors

Updated 2001-06-14 15:54:28

From a news:comp.lang.tcl posting by Frederic Bonnet:

Rolf Schroedter wrote: How can I define N visually different screen (RGB) colors? N is defined at runtime in the range of 20..200. I guess the question is about color spaces. Which color space does best meet the human eye? How to select N equally spaced colors in that color space? How to convert to RGB?

This is not the point of view of a graphics professional, only my limited experience. I had to display 3D structures using distinct "equally spaced" colors. My opinion is that the HSB and HLS color spaces best meet the human mind, and also maybe the human eye. We are used to represent colors as 3 main composants: the Hue (red, green, yellow, ie the rainbow colors), the Luminosity or Brightness (dark, light), and the Saturation (pastel, deep). Hue correspond to the color's wave length, Luminosity to the color's amplitude, Brightness to the color's perceptual luminosity (at equal luminosity, green looks brighter than red), and Saturation to the light's purity.

The two most important composants being the Hue and Brightness due to the structure of the human retina, my guess is that you can create contrasted colors by interpolating H and B, leaving S to its maximum (every color tends to white when it is desaturated, so the contrast tends to decrease). Also, the perceptual contrast decreases with the brighness (darker colors look closer than brighter).

There are subtle differences between HLS and HSB. IIRC, B is proportional to the sum of the weighted RGB values, whereas L is just the maximum value. For example, the HSB color with maximum B would be white whereas RGB colors red (1,0,0), green (0,1,0) and yellow (1,1,0) would have the same L value. B is closer to the perceptual feeling of brightness because yellow looks brighter than green, which looks brighter than red. The HLS color space looks like a cone. The lower tip is black, the upper disk is the famous "color wheel". The HSB color space looks like 2 cones: the lower tip is black, the upper tip is white, the color wheel is at the middle.

For your case, you can perfectly use HLS, which is simpler to compute. Here is a small Tcl proc that does HLS to RGB conversion. Then interpolate values on H and L. Also remember than with low L values, colors look less contrasted.

 proc hls2rgb {h l s} {
    # h, l and s are floats between 0.0 and 1.0, ditto for r, g and b
    # h = 0   => red
    # h = 1/3 => green
    # h = 2/3 => blue

    set h6 [expr {($h-floor($h))*6}]
    set r [expr {  $h6 <= 3 ? 2-$h6
                            : $h6-4}]
    set g [expr {  $h6 <= 2 ? $h6
                            : $h6 <= 5 ? 4-$h6
                            : $h6-6}]
    set b [expr {  $h6 <= 1 ? -$h6
                            : $h6 <= 4 ? $h6-2
                            : 6-$h6}]
    set r [expr {$r < 0.0 ? 0.0 : $r > 1.0 ? 1.0 : double($r)}]
    set g [expr {$g < 0.0 ? 0.0 : $g > 1.0 ? 1.0 : double($g)}]
    set b [expr {$b < 0.0 ? 0.0 : $b > 1.0 ? 1.0 : double($b)}]

    set r [expr {(($r-1)*$s+1)*$l}]
    set g [expr {(($g-1)*$s+1)*$l}]
    set b [expr {(($b-1)*$s+1)*$l}]
    return [list $r $g $b]
 }

DKF: As a side note, if you set the Saturation to 0 then you get no variation in colour as the Hue varies, and changing the Luminosity gives you different shades of grey.


Conversions using the HSV color model

The HSV color model can be visualized as a cone standing on its tip. The top surface of the cone is the color circle. The Hue value is expressed as an angle on the top surface. The six basic colors correspond to angles (in degrees) as follows:

   angle   color
      0     red
     60     yellow
    120     green
    180     cyan
    240     blue
    300     magenta

Colors 180 degrees apart are complementary.

If the color is exactly on the vertical axis (see below) the color is part of a grayscale and has no hue at all.

The Saturation (purity) of the color is the distance from the vertical axis of the cone to the color on the top surface. It varies from 0 (exactly on the vertical axis; a grayscale color) to 1 (at the edge of the top surface; a pure color).

The Value (intensity) of the color is the distance from the tip of the cone to the color. It varies from 0 (at the cone's tip; the color black) to 1 (on the top surface).

 # rgb2hsv --
 #
 #       Convert a color value from the RGB model to HSV model.
 #
 # Arguments:
 #       r g b        the red, green, and blue components of the color
 #               value.  The procedure expects, but does not
 #               ascertain, them to be in the range 0 to 1.
 #
 # Results:
 #       The result is a list of three real number values.  The 
 #       first value is the Hue component, which is in the range
 #       0.0 to 360.0, or -1 if the Saturation component is 0.
 #       The following to values are Saturation and Value,
 #       respectively.  They are in the range 0.0 to 1.0.
 #
 # Credits:
 #       This routine is based on the Pascal source code for an
 #       RGB/HSV converter in the book "Computer Graphics", by
 #       Baker, Hearn, 1986, ISBN 0-13-165598-1, page 304.
 #

 proc rgb2hsv {r g b} {
     set h [set s [set v 0.0]]]
     set sorted [lsort -real [list $r $g $b]]
     set v [expr {double([lindex $sorted end])}]
     set m [lindex $sorted 0]

     set dist [expr {double($v-$m)}]
     if {$v} {
         set s [expr {$dist/$v}]
     }
     if {$s} {
         set r' [expr {($v-$r)/$dist}] ;# distance of color from red
         set g' [expr {($v-$g)/$dist}] ;# distance of color from green
         set b' [expr {($v-$b)/$dist}] ;# distance of color from blue
         if {$v==$r} {
             if {$m==$g} {
                 set h [expr {5+${b'}}]
             } else {
                 set h [expr {1-${g'}}]
             }
         } elseif {$v==$g} {
             if {$m==$b} {
                 set h [expr {1+${r'}}]
             } else {
                 set h [expr {3-${b'}}]
             }
         } else {
             if {$m==$r} {
                 set h [expr {3+${g'}}]
             } else {
                 set h [expr {5-${r'}}]
             }
         }
         set h [expr {$h*60}]          ;# convert to degrees
     } else {
         # hue is undefined if s == 0
         set h -1
     }
     return [list $h $s $v]
 }

 # hsv2rgb --
 #
 #       Convert a color value from the HSV model to RGB model.
 #
 # Arguments:
 #       h s v        the hue, saturation, and value components of
 #               the color value.  The procedure expects, but
 #               does not ascertain, h to be in the range 0.0 to 
 #               360.0 and s, v to be in the range 0.0 to 1.0.
 #
 # Results:
 #       The result is a list of three real number values, 
 #       corresponding to the red, green, and blue components
 #       of a color value.  They are in the range 0.0 to 1.0.
 #
 # Credits:
 #       This routine is based on the Pascal source code for an
 #       HSV/RGB converter in the book "Computer Graphics", by
 #       Baker, Hearn, 1986, ISBN 0-13-165598-1, page 304.
 #

 proc hsv2rgb {h s v} {
     set v [expr {double($v)}]
     set r [set g [set b 0.0]]
     if {$h == 360} { set h 0 }
     # if you feed the output of rgb2hsv back into this 
     # converter, h could have the value -1 for
     # grayscale colors.  Set it to any value in the
     # valid range.
     if {$h == -1} { set h 0 }
     set h [expr {$h/60}]
     set i [expr {int(floor($h))}]
     set f [expr {$h - $i}]
     set p1 [expr {$v*(1-$s)}]
     set p2 [expr {$v*(1-($s*$f))}]
     set p3 [expr {$v*(1-($s*(1-$f)))}]
     switch -- $i {
         0 { set r $v  ; set g $p3 ; set b $p1 }
         1 { set r $p2 ; set g $v  ; set b $p1 }
         2 { set r $p1 ; set g $v  ; set b $p3 }
         3 { set r $p1 ; set g $p2 ; set b $v  }
         4 { set r $p3 ; set g $p1 ; set b $v  }
         5 { set r $v  ; set g $p1 ; set b $p2 }
     }
     return [list $r $g $b]
 }

Peter Lewerin