proc + {n1 n2} { expr {$n1 + $n2} } proc - {n1 n2} { expr {$n1 - $n2} } proc * {n1 n2} { expr {$n1 * $n2} } proc / {n1 n2} { expr {$n1 / $n2} } proc toInt {n} { expr int($n) } proc drawGradient {win type col1Str col2Str} { $win delete gradient set width [winfo width $win] set height [winfo height $win] foreach {r1 g1 b1} [winfo rgb $win $col1Str] break foreach {r2 g2 b2} [winfo rgb $win $col2Str] break set rRange [- $r2.0 $r1] set gRange [- $g2.0 $g1] set bRange [- $b2.0 $b1] if {$type == "x"} { set rRatio [/ $rRange $width] set gRatio [/ $gRange $width] set bRatio [/ $bRange $width] for {set x 0} {$x < $width} {incr x} { set nR [toInt [+ $r1 [* $rRatio $x]]] set nG [toInt [+ $g1 [* $gRatio $x]]] set nB [toInt [+ $b1 [* $bRatio $x]]] set col [format {%4.4x} $nR] append col [format {%4.4x} $nG] append col [format {%4.4x} $nB] $win create line $x 0 $x $height -tags gradient -fill #${col} } } else { set rRatio [/ $rRange $height] set gRatio [/ $gRange $height] set bRatio [/ $bRange $height] for {set y 0} {$y < $height} {incr y} { set nR [toInt [+ $r1 [* $rRatio $y]]] set nG [toInt [+ $g1 [* $gRatio $y]]] set nB [toInt [+ $b1 [* $bRatio $y]]] set col [format {%4.4x} $nR] append col [format {%4.4x} $nG] append col [format {%4.4x} $nB] $win create line 0 $y $width $y -tags gradient -fill #${col} } } return $win } canvas .grad1 bind .grad1 <Configure> [list drawGradient .grad1 x red royalblue] canvas .grad2 bind .grad2 <Configure> [list drawGradient .grad2 y yellow red] pack .grad1 .grad2 -fill both -expand 1___uniquename 2014jan27For those who do not have the facilities or time to implement the GPS (G. P. Staplin) code above, here is an image of the 2 Tk canvases that the code creates.The 2 canvases expand and contract as the user 'grabs' a corner/side of the window and expands and contracts the window --- but the upper canvas does not collapse vertically until the lower canvas has been collapsed to zero height.
Here's a slightly more portable version of that code with some error-checking thrown in for good measure. -- Damon Courtney
proc DrawGradient {win axis col1Str col2Str} { if {[winfo class $win] != "Canvas"} { return -code error "$win must be a canvas widget" } $win delete gradient set width [winfo width $win] set height [winfo height $win] switch -- $axis { "x" { set max $width; set x 1 } "y" { set max $height; set x 0 } default { return -code error "Invalid axis $axis: must be x or y" } } if {[catch {winfo rgb $win $col1Str} color1]} { return -code error "Invalid color $col1Str" } if {[catch {winfo rgb $win $col2Str} color2]} { return -code error "Invalid color $col2Str" } foreach {r1 g1 b1} $color1 break foreach {r2 g2 b2} $color2 break set rRange [expr $r2.0 - $r1] set gRange [expr $g2.0 - $g1] set bRange [expr $b2.0 - $b1] set rRatio [expr $rRange / $max] set gRatio [expr $gRange / $max] set bRatio [expr $bRange / $max] for {set i 0} {$i < $max} {incr i} { set nR [expr int( $r1 + ($rRatio * $i) )] set nG [expr int( $g1 + ($gRatio * $i) )] set nB [expr int( $b1 + ($bRatio * $i) )] set col [format {%4.4x} $nR] append col [format {%4.4x} $nG] append col [format {%4.4x} $nB] if {$x} { $win create line $i 0 $i $height -tags gradient -fill #${col} } else { $win create line 0 $i $width $i -tags gradient -fill #${col} } } bind $win <Configure> [list DrawGradient $win $axis $col1Str $col2Str] return $win } canvas .grad1 DrawGradient .grad1 y darkblue royalblue canvas .grad2 DrawGradient .grad2 x yellow red pack .grad1 .grad2 -fill both -expand 1___uniquename 2014jan27For those who do not have the facilities or time to implement the Courtney code above, here is an image of the 2 Tk canvases that the code creates.This demo behaves like the Staplin demo above:The 2 canvases expand and contract as the user 'grabs' a corner/side of the window and expands and contracts the window --- but the upper canvas does not collapse vertically until the lower canvas has been collapsed to zero height.
DKF: Both versions are cool!
pw: Here is a library for drawing simple gradients. It's currently capable of five styles: vertical, horizontal, circular, central, and arc.
# Create color gradients of various shapes and sizes on a canvas. # Usage: # gradient draw <canvas> <tags> <x> <y> <width> <height> <vertical|horizontal|circular|central|arc> <color1> <color2> ?-animate? ?option? ... # gradient redraw <canvas> <tags> <x> <y> <width> <height> <vertical|horizontal|circular|central|arc> <color1> <color2> ?-animate? ?option? ... # gradient resize <canvas> <tags> <x> <y> <width> <height> # gradient recolor <canvas> <tags> <color1> <color2> namespace eval gradient { namespace ensemble create -subcommands { draw redraw recolor resize demo } # Create a gradient on a canvas. The gradient will be placed at the top of the display list. proc draw {canvas tags x y width height orient color1 color2 args} { # Ensure valid colors are provided while also converting to lists of 16-bit RGB values. if { [catch {winfo rgb . $color1} rgb1] } { error "invalid color: $color1" } if { [catch {winfo rgb . $color2} rgb2] } { error "invalid color: $color2" } # Delete the gradient if it already exists. $canvas delete [join $tags &&] # Create a hidden canvas item to store meta data about this gradient. set meta_data [list x $x y $y width $width height $height orient $orient color1 $color1 color2 $color2 args $args] $canvas create line $x $y $x $y -state hidden -tags [list {*}$tags meta [list gradient: {*}$meta_data]] # Intercept the -animate option. if { "-animate" in $args} { set index [lsearch $args "-animate"] set args [lreplace $args $index $index] set animate 1 } else { set animate 0 } # Draw the gradient. if { $orient eq "central" } { # Create the canvas rectangle items. foreach {x1 y1 x2 y2 color} [get_rects $x $y $width $height $rgb1 $rgb2] { $canvas create rectangle $x1 $y1 $x2 $y2 -outline $color -width 2 -tags $tags {*}$args if {$animate} {update} } } elseif { $orient eq "circular" } { # Create the canvas oval items. foreach {x1 y1 x2 y2 color} [get_rects $x $y $width $height $rgb1 $rgb2] { $canvas create oval $x1 $y1 $x2 $y2 -outline $color -width 2 -tags $tags {*}$args if {$animate} {update} } } elseif { $orient eq "arc" } { # Create the canvas arc items. foreach {x1 y1 x2 y2 color} [get_rects $x $y $width $height $rgb1 $rgb2] { $canvas create arc $x1 $y1 $x2 $y2 -outline $color -width 2 -tags $tags -style arc {*}$args if {$animate} {update} } } elseif { $orient in [list "vertical" "horizontal"] } { # Create the canvas line items. foreach {x1 y1 x2 y2 color} [get_lines $x $y $width $height $orient $rgb1 $rgb2] { $canvas create line $x1 $y1 $x2 $y2 -fill $color -tags $tags -capstyle projecting {*}$args if {$animate} {update} } } else { # Delete the meta data and throw an error. $canvas delete [join $tags &&] error "invalid orientation: $orient; must be: vertical, horizontal, circular, central, or arc" } return } # Same as draw, but if the gradient already exists then it will retain its position in the canvas's display list after being redrawn. proc redraw {canvas tags x y width height orient color1 color2 args} { # If the gradient already exists, remember its location in the display list. set item_above [$canvas find above [join $tags &&]] # Draw the gradient. draw $canvas $tags $x $y $width $height $orient $color1 $color2 {*}$args # Move the gradient to its previous location in the display list, if any. if { $item_above ne "" } { $canvas lower [join $tags &&] $item_above } return } # Shortcut procedure to change the colors of the gradient. Same as redraw but introspectively determines some meta data. # This will use the gradient's x/y location when it was last drawn. proc recolor {canvas tags color1 color2} { # Retrieve the gradient's meta data. set meta [get_meta_data $canvas $tags] set meta [dict remove $meta color1 color2] dict with meta {} # Redraw the gradient with the new colors. redraw $canvas $tags $x $y $width $height $orient $color1 $color2 {*}$args return } # Shortcut procedure to resize and reposition a gradient. Same as redraw but introspectively determines some meta data. proc resize {canvas tags x y width height} { # Retrieve the gradient's meta data. set meta [get_meta_data $canvas $tags] set meta [dict remove $meta x y width height] dict with meta {} # Recreate the canvas lines with the new dimensions. redraw $canvas $tags $x $y $width $height $orient $color1 $color2 {*}$args return } # Retrieve meta information on a gradient. proc get_meta_data {canvas tags} { if { [string is integer -strict $tags] } { # This is supposedly a canvas item ID instead of a list of tags describing a gradient. Avoid introducing subtle logic errors. error "cannot provide canvas item ID in lieu of gradient tags: $tags" } # This is a list of tags for a gradient. Find the hidden meta item for this gradient. set all_tags [$canvas gettags "meta&&[join $tags &&]"] set meta_data [lrange [lsearch -inline -index 0 $all_tags "gradient:"] 1 end] if { [llength $meta_data] == 0 } { error "cannot find meta data for gradient with tags: $tags" } return $meta_data } # For a canvas item ID, return all tags that are not gradient meta tags. proc get_non_meta_tags {canvas id} { set tags [$canvas gettags $id] set index [lsearch $tags "meta"] set tags [lreplace $tags $index $index] set index [lsearch -index 0 $tags "gradient:"] set tags [lreplace $tags $index $index] return $tags } # Calculate a list of center-to-outward colored rectangles suitable for [canvas create rectangle] or [canvas create oval]. proc get_rects {x y width height rgb1 rgb2} { set rects [list] set x1 [expr {int($x)+1}] set y1 [expr {int($y)+1}] set x2 [expr {$x1+$width-1}] set y2 [expr {$y1+$height-1}] # Calculate each rectangle. if { $width > $height } { # Decrement X by one pixel in both directions. Decrement Y by a fraction in both directions. # The Y axis will be more heavily concentrated. set ratio [expr { 1.0*$height/$width }] set length [expr { $width/2 }] while { $x2 > $x1 } { lappend rects $x1 $y1 $x2 $y2 [get_color $rgb1 $rgb2 $length [expr { ($x2-$x1)/2 }]] set x1 [expr { $x1+1 }] set x2 [expr { $x2-1 }] set y1 [expr { $y1+$ratio }] set y2 [expr { $y2-$ratio }] } } else { # Decrement Y by one pixel in both directions. Decrement X by a fraction in both directions. # The X axis will be more heavily concentrated (unless width==height). set ratio [expr { 1.0*$width/$height }] set length [expr { $height/2 }] while { $y2 > $y1 } { lappend rects $x1 $y1 $x2 $y2 [get_color $rgb1 $rgb2 $length [expr { ($y2-$y1)/2 }]] set x1 [expr { $x1+$ratio }] set x2 [expr { $x2-$ratio }] set y1 [expr { $y1+1 }] set y2 [expr { $y2-1 }] } } return $rects } # Calculate a list of left-to-right or top-to-bottom colored lines suitable for [canvas create line]. proc get_lines {x1 y1 width height orient rgb1 rgb2} { set lines [list] set x1 [expr {int($x1)}] set y1 [expr {int($y1)}] set x2 [expr {$x1+$width}] set y2 [expr {$y1+$height}] if { $orient eq "vertical" } { # Calculate the color for each horizontal line. for {set y $y1} {$y < $y2} {incr y} { lappend lines $x1 $y $x2 $y [get_color $rgb1 $rgb2 $height [expr {$y-$y1}]] } } elseif { $orient eq "horizontal" } { # Calculate the color for each vertical column. for {set x $x1} {$x < $x2} {incr x} { lappend lines $x $y1 $x $y2 [get_color $rgb1 $rgb2 $width [expr {$x-$x1}]] } } else { error "invalid orientation: $orient; must be: vertical or horizontal" } return $lines } # Calculates the color at the specified index between rgb1 and rgb2 where rgb1 and rgb2 are the specified length apart. proc get_color {rgb1 rgb2 length index} { # Throw an error if the index is out of bounds. if { $index < 0 || $index >= $length } { error "index $index is out of bounds for length $length" } lassign $rgb1 r1 g1 b1 lassign $rgb2 r2 g2 b2 # Determine the ratio between each starting component color and ending component color. set r_ratio [expr { 1.00*($r2-$r1+1)/$length }] set g_ratio [expr { 1.00*($g2-$g1+1)/$length }] set b_ratio [expr { 1.00*($b2-$b1+1)/$length }] # Calculate the new component colors at the given index. set r [expr { int($r_ratio*$index+$r1) }] set g [expr { int($g_ratio*$index+$g1) }] set b [expr { int($b_ratio*$index+$b1) }] # A hacky workaround to make up for a lack of precision (or faulty math?). # The final pixel of the gradient should exactly match the color of rgb2 if { $index == [expr {$length-1}] } { lassign $rgb2 r g b } # Convert the integer RGB values to a hex color. return [rgb_to_hex [list $r $g $b]] } # Convert a list of 16-bit RGB values to an 8-bit hex color. Using 8-bit hex colors instead of 16-bit # speeds up drawing of images two-fold (I haven't benchmarked this for canvases). proc rgb_to_hex {rgb} { lassign $rgb r g b set r [format %02x [expr {$r/256}]] set g [format %02x [expr {$g/256}]] set b [format %02x [expr {$b/256}]] return #$r$g$b } # Converts 8-bit RGB values to 16-bit RGB values. proc rgb_8_to_16_bit {rgb} { lassign $rgb r g b return [list [expr {$r*256}] [expr {$g*256}] [expr {$b*256}]] } # Returns a random 8-bit hex color. proc random_color {} { return [rgb_to_hex "[expr {int(rand()*65536)}] [expr {int(rand()*65536)}] [expr {int(rand()*65536)}]"] } # Redraw all gradients with random colors proc randomize_all_gradient_colors {canvas} { foreach id [$canvas find withtag meta] { set tags [get_non_meta_tags $canvas $id] recolor $canvas $tags [random_color] [random_color] } return } # Randomize colors for all canvas items. proc randomize_all_canvas_item_colors {canvas} { foreach id [$canvas find withtag all] { if { [$canvas type $id] in {window} } { continue } $canvas itemconfigure $id -fill [random_color] if { [$canvas type $id] in {rectangle arc oval} } { $canvas itemconfigure $id -outline [random_color] } } return } # Run a demonstration. proc demo {} { # Create a toplevel window with a canvas filling the contents. set win .gradients if { [winfo exists $win] } { destroy $win } toplevel $win set cvs [canvas $win.cvs -highlightthickness 0] pack $cvs -fill both -expand yes wm geometry $win 800x600 raise $win # Fill the bottom of the screen with a striped green fade. gradient draw $cvs footer 0 0 10 10 vertical white darkgreen -dash {3 1} bind $cvs <Configure> { gradient resize %W footer 0 [expr {%h-300}] %w 300 } # Draw a strip of rainbow. set width 100 set colors {white red orange yellow green blue purple violet white} for {set i 1} {$i < [llength $colors]} {incr i} { set c1 [lindex $colors $i-1] set c2 [lindex $colors $i] gradient draw $cvs "wave $i" [expr {$width*($i-1)}] 0 $width 50 horizontal $c1 $c2 } # Draw some balls with diameters of 100. set colors {green red yellow blue orange white} for {set i 0} {$i < 6} {incr i} { gradient draw $cvs "beachball $i" 0 55 100 100 arc [lindex $colors $i] #333 -start [expr {$i*60}] -extent 60 } set colors {cyan pink orange firebrick} for {set i 0} {$i < [llength $colors]} {incr i} { gradient draw $cvs "quadball $i" 110 55 100 100 arc [lindex $colors $i] #eee -start [expr {$i*90}] -extent 90 } gradient draw $cvs ball1 220 55 100 100 circular #bbbbbb white gradient draw $cvs ball2 330 55 100 100 circular #888 #eee gradient draw $cvs ball3 440 55 100 100 circular black grey gradient draw $cvs ball4 550 55 100 100 circular black #555 gradient draw $cvs ball5 660 55 100 100 circular cyan black # Draw some boxes. gradient draw $cvs box1 0 160 150 100 horizontal white firebrick gradient draw $cvs box2 150 160 200 100 central black firebrick gradient draw $cvs box3 350 160 100 100 horizontal firebrick black gradient draw $cvs box4 450 160 200 100 central firebrick black gradient draw $cvs box5 650 160 150 100 horizontal black white # Display some control buttons. frame $cvs.f button $cvs.f.b1 -text "Redraw with random gradient colors" -command [list [namespace current]::randomize_all_gradient_colors $cvs] button $cvs.f.b2 -text "Relaunch demo with default colors" -command [list [namespace current]::demo] button $cvs.f.b3 -text "Chaos" -command [list [namespace current]::randomize_all_canvas_item_colors $cvs] pack $cvs.f.b1 $cvs.f.b2 $cvs.f.b3 -fill x $cvs create window 400 400 -window $cvs.f return } } gradient demoMaxJarek: We have also tklib canvas::gradient module.
arjen - 2013-10-04 07:37:28I tried the example in the documentation for the canvas::gradient package. Unfortunately a small extension of that example showed a glitch:
# grad.tcl -- # Show a gradient on the canvas # package require canvas::gradient canvas .c canvas::gradient .c -direction x -color1 yellow -color2 blue pack .c -fill both -expand 1 set poly [.c create polygon 200 100 300 200 200 300 200 100 -fill blue]The polygon does not show up. My guess is that the gradient items are drawn later, so that a ".c lower canvas::gradient" is required.(I should add this to the Tklib tracker too - will do later)
SeS 21 March 2016, See also Blending/fading widget colors