You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

278 lines
7.3 KiB

# *- 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 <Configure> [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
## Lower the newly created gradient items into the background
$canvas lower canvas::gradient
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.2
return