# ACTIVESTATE TEAPOT-PKG BEGIN TM -*- tcl -*- # -- Tcl Module # @@ Meta Begin # Package canvas::gradient 0.1 # Meta as::build::date 2013-10-30 # Meta as::origin http://sourceforge.net/projects/tcllib # Meta category Variations on a canvas # Meta description Canvas with a gradient background # Meta license BSD # Meta platform tcl # Meta require {Tcl 8.5} # Meta require {Tk 8.5} # Meta subject {widget background} canvas background gradients # Meta subject {color gradient} image # Meta summary canvas::gradient # @@ Meta End # ACTIVESTATE TEAPOT-PKG BEGIN DECLARE package provide canvas::gradient 0.1 # ACTIVESTATE TEAPOT-PKG END DECLARE # ACTIVESTATE TEAPOT-PKG END TM # *- tcl -*- # ### ### ### ######### ######### ######### # Copyright (c) 2013 Jarek Lewandowski (MaxJarek) # Origin http://wiki.tcl.tk/6100 # Origin http://wiki.tcl.tk/37242 # Origin http://wiki.tcl.tk/9079 # OLL licensed (http://wiki.tcl.tk/10892) # ### ### ### ######### ######### ######### ## Requisites package require Tcl 8.5 package require Tk 8.5 namespace eval ::canvas {} # ### ### ### ######### ######### ######### ## Implementation. proc ::canvas::gradient {canvas args} { gradient::DrawGradient $canvas {*}$args bind $canvas [list ::canvas::gradient::DrawGradient %W {*}$args] return } # ### ### ### ######### ######### ######### ## Helper commands. Internal. namespace eval ::canvas::gradient {} # ### ### ### ######### ######### ######### ## Helper commands. ## Recreate the entire gradient from scratch, as a series of (nested) ## items each filled with a piece of it. This command is called on ## *every* change to the canvas's geometry. ## TODO: Force redraw only on changes to width and height, not ## position. proc ::canvas::gradient::DrawGradient {canvas args} { # Fill any holes in the user's specification with the defaults. set args [dict merge { -direction x -color1 red -color2 green -type linear } $args] set color1 [dict get $args -color1] set color2 [dict get $args -color2] set direction [dict get $args -direction] ## Clear gradient. Destroys all canvas items the old gradient ## consisted of. $canvas delete canvas::gradient ## Get current canvas width and height. set canWidthPx [winfo width $canvas] set canHeightPx [winfo height $canvas] ## No gradient if the canvas' area is too small if {($canWidthPx < 10) || ($canHeightPx < 10)} return ## Get the distance 'distPx' (in pixels) over which ## the 2 colors are to be gradiated. switch -exact -- $direction { x { set distPx $canWidthPx } y { set distPx $canHeightPx } r { set halfWidthPx [expr {int($canWidthPx / 2)}] set halfHeightPx [expr {int($canHeightPx / 2)}] set distPx [expr {max($halfHeightPx,$halfWidthPx)}] # Even with the radial gradient stopping at the farthest # canvas border (see dist calculation above, max), we may # have undefined pixels in the corners. The rectangle # added below ensures that these have a defined color as # well (the end color). $canvas create rectangle 0 0 $canWidthPx $canHeightPx \ -tags canvas::gradient -fill $color2 } d1 - d2 { # Hm. I wonder if that should be the length of the # diagonal instead (hypot). set distPx [expr {$canWidthPx + $canHeightPx}] } default { return -code error "Invalid direction $direction" } } ## Translate whatever color specification came in into RGB triples ## we can then interpolate between. if {[catch { lassign [winfo rgb $canvas $color1] r1 g1 b1 lassign [winfo rgb $canvas $color2] r2 g2 b2 } err]} { return -code error $err } ## Calculate the data needed for the interpolation, i.e. color ## range and slope of the line (The ratio of RGB-color-ranges to ## distance 'across' the canvas). set rRange [expr {$r2 - $r1 + 0.0}] set gRange [expr {$g2 - $g1 + 0.0}] set bRange [expr {$b2 - $b1 + 0.0}] set rRatio [expr {$rRange / $distPx}] set gRatio [expr {$gRange / $distPx}] set bRatio [expr {$bRange / $distPx}] ## Increment 'across' the canvas, drawing colored lines, or ovals ## with canvas-'create line', 'create oval'. Computed jump to the ## actual drawing command. Draw_$direction return } # ### ### ### ######### ######### ######### ## Draw helpers, one per direction. proc ::canvas::gradient::Draw_d1 {} { upvar 1 canvas canvas r1 r1 g1 g1 b1 b1 rRatio rRatio gRatio gRatio bRatio bRatio upvar 1 canHeightPx canHeightPx canWidthPx canWidthPx # Drawing for diagonal direction, left+top to bottom+right # Two stages: # - First along y-axis (canHeightPx), top to bottom, # - Then along x-axis (canWidthPx), left to right. # i 0 --> canHeight for {set i 0} {$i <= $canHeightPx} {incr i} { catch { $canvas create line $i 0 0 $i \ -tags canvas::gradient -fill [GetNextColor $i] } } # x canHeight --> canWidth + canHeight # i 0 --> canWidth for { set x $canHeightPx set i 0 } {$i <= $canWidthPx} { incr i incr x } { catch { $canvas create line $i $canHeightPx $x 0 \ -tags canvas::gradient -fill [GetNextColor $x] } } return } proc ::canvas::gradient::Draw_d2 {} { upvar 1 canvas canvas r1 r1 g1 g1 b1 b1 rRatio rRatio gRatio gRatio bRatio bRatio upvar 1 canHeightPx canHeightPx canWidthPx canWidthPx # Drawing for diagonal direction, bottom+left to top+right # Two stages: # - First along y-axis (canHeightPx), bottom to top. # - Then along x-axis (canWidthPx), left to right. # x 0 --> canHeight # i canHeight --> 0 for { set x 0 set i $canHeightPx } {$i >= 0} { incr i -1 incr x } { catch { $canvas create line $x $canHeightPx 0 $i \ -tags canvas::gradient -fill [GetNextColor $x] } } # x canHeight --> canWidth + canHeight # i 0 --> canWidth for { set x $canHeightPx set i 0 } {$i <= $canWidthPx} { incr i incr x } { catch { $canvas create line $i 0 $x $canHeightPx \ -tags canvas::gradient -fill [GetNextColor $x] } } return } proc ::canvas::gradient::Draw_x {} { upvar 1 canvas canvas r1 r1 g1 g1 b1 b1 rRatio rRatio gRatio gRatio bRatio bRatio upvar 1 canHeightPx canHeightPx distPx distPx for {set i $distPx} {$i >= 0} {incr i -1} { catch { $canvas create line $i 0 $i $canHeightPx \ -tags canvas::gradient -fill [GetNextColor $i] } } return } proc ::canvas::gradient::Draw_y {} { upvar 1 canvas canvas r1 r1 g1 g1 b1 b1 rRatio rRatio gRatio gRatio bRatio bRatio upvar 1 canWidthPx canWidthPx distPx distPx for {set i $distPx} {$i >= 0} {incr i -1} { catch { $canvas create line 0 $i $canWidthPx $i \ -tags canvas::gradient -fill [GetNextColor $i] } } return } proc ::canvas::gradient::Draw_r {} { upvar 1 canvas canvas r1 r1 g1 g1 b1 b1 rRatio rRatio gRatio gRatio bRatio bRatio upvar 1 halfWidthPx halfWidthPx halfHeightPx halfHeightPx distPx distPx for {set i $distPx} {$i >= 0} {incr i -1} { set xx1 [expr {$halfWidthPx + $i}] set xx2 [expr {$halfHeightPx + $i}] set xx3 [expr {$halfWidthPx - $i}] set xx4 [expr {$halfHeightPx - $i}] catch { $canvas create oval $xx1 $xx2 $xx3 $xx4 \ -outline {} -tags canvas::gradient -fill [GetNextColor $i] } } return } # ### ### ### ######### ######### ######### ## Helper command. Compute the color for step i of the gradient. ## Linear interpolation from the start color. proc ::canvas::gradient::GetNextColor {i} { upvar 1 r1 r1 g1 g1 b1 b1 rRatio rRatio gRatio gRatio bRatio bRatio set nR [expr {int ($r1 + ($rRatio * $i))}] set nG [expr {int ($g1 + ($gRatio * $i))}] set nB [expr {int ($b1 + ($bRatio * $i))}] return [format "#%04X%04X%04X" $nR $nG $nB] } # ### ### ### ######### ######### ######### ## Ready package provide canvas::gradient 0.1 return