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
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
|
|
|