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 modelThe 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 magentaColors 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] }
proc distinctHueLabels {n} { eval destroy [info commands .b*] set inc [expr {1.0/$n}] set s 1.0 set l 1.0 set nn 0 for {set h 0.0} {$h < 1.0} {set h [expr {$h + $inc}]} { button .b$nn -bg [hls2tk $h $l $s] -width 10 -height 1 pack .b$nn ; incr nn } } # this one needs some work proc distinctLabels {n} { eval destroy [info commands .b*] set ns [expr {int(sqrt($n)+0.999)}] set inc [expr {1.0/$ns}] set s 1.0 set nn 0 for {set l 1.0} {$l > 0.3} {set l [expr {$l - 0.7*$inc}]} { for {set h 0.0} {$h < 1.0} {set h [expr {$h + $inc}]} { button .b$nn -bg [hls2tk $h $l $s] -width 10 -height 1 pack .b$nn ; incr nn if {$nn == $n} { break } } } } proc hls2tk {h l s} { set rgb [hls2rgb $h $l $s] foreach c $rgb { set intc [expr {int($c * 256)}] if {$intc == 256} { set intc 255 } set c1 [format %1X $intc] if {[string length $c1] == 1} {set c1 "0$c1"} append init $c1 } return #$init }John Droggitisnother take on John's proc distinctLabels, works better for me WRT producing visually distinguishable colors
proc distinctLabels2 {n} { eval destroy [info commands .b*] set nn 1 set hue_increment .15 set s 1.0 ;# non-variable saturation set lum_steps [expr $n * $hue_increment] set int_lum_steps [expr int($lum_steps)] if {$lum_steps > $int_lum_steps} { ;# round up set lum_steps [expr $int_lum_steps + 1] } set lum_increment [expr .7 / $lum_steps] # assertion 1: we can get 7 disctinct hues, and the rest of the # variants should be controlled by luminocity # assertion 2: luminocity should stay above .3 otherwise colors get # too dark to distinguish for {set l 1.0} {$l > 0.3} {set l [expr {$l - $lum_increment}]} { for {set h 0.0} {$h < 1.0} {set h [expr {$h + $hue_increment}]} { button .b$nn -bg [hls2tk $h $l $s] -width 10 -height 1 pack .b$nn ; incr nn if {$nn > $n} { return } } } } # # Same as distinctLabels2, but it returns a list # of colors rather than drawing buttons # proc distinctColors {n} { set nn 1 set hue_increment .15 set s 1.0 ;# non-variable saturation set lum_steps [expr $n * $hue_increment] set int_lum_steps [expr int($lum_steps)] if {$lum_steps > $int_lum_steps} { ;# round up set lum_steps [expr $int_lum_steps + 1] } set lum_increment [expr .7 / $lum_steps] for {set l 1.0} {$l > 0.3} {set l [expr {$l - $lum_increment}]} { for {set h 0.0} {$h < 1.0} {set h [expr {$h + $hue_increment}]} { lappend rc [hls2tk $h $l $s] incr nn if {$nn > $n} { return $rc } } } return $rc }What about the specific case of a pair of colors, conceived as background and foreground? Here are a few ideas:
- Arjen Markus suggests sending (r, g, b) :-> (f(r), f(g), f(b)), with f(x) = 255 for small x, f(x) = 0 for large x, and pick-a-heuristic for intermediate values, with bright (white) the tie-breaker.
- think about tk_setPalette.
- study color wheel work of Johaness Itten.
- see the Cookbook on "color manipulation" [1].
- Felix Lee suggests
proc contrasting {w color {colors {black white}}} { ## Return a color in $colors that contrasts with ## $color in window $w. Checks luminance. set y0 [luminance $w $color] set best "" set diff 0 foreach c $colors { set y [luminance $w $c] set d [expr {abs($y - $y0)}] if {$diff < $d} { set best $c set diff $d } } return $best }
kbk: "A good rule of thumb is to take your background colour and compute Y = 0.3R + 0.59G + 0.11B; if Y exceeds 0.5, use black foreground text, otherwise use white."dkf: "I don't use those numbers; I just look at and think, 'is the background "light" or "dark"'."....jenglish: "Using Paul Haeberli's coefficients for luminance (http://www.sgi.com/grafica/matrix/index.html) instead of the NTSC standard seems to work a little better."kbk: "... You can get closer by considering the gamma-corrected values. Yes, Y=0.5 as the breakpoint may switch to white too early, but it's At Least Good Enough - the foreground text is at least readable. NTSC presumes γ=2.2 and monitors today are "hotter" than when the NTSC standard was designed."