bevel 3d

MGS [2003/04/06] - In order to draw rectangles on a canvas with a raised/sunken relief, you need to know how to compute the colors of the 3d beveled edges - the light and dark shadows. I converted tk8.4.0/unix/tkUnix3d.c to pure tcl code:

# ======================================================================

  namespace eval color {
    variable MAX_INTENSITY 65535
  }

# ======================================================================

# shadow:dark --

# Compute the dark shadow color for a 3d border.

# Cut 40% from each of the color components. If the background is
# already very dark, make the dark color a little lighter than the
# background by increasing each color component 1/4th of the way to
# MAX_INTENSITY.

# This adapted from tk8.4.0/unix/tkUnix3d.c

proc color::shadow:dark {R G B} {

# ----------------------------------------------------------------------

  variable MAX_INTENSITY

# ----------------------------------------------------------------------

  if { [expr {($R*0.5*$R) + ($G*1.0*$G) + ($B*0.28*$B)}] <
       [expr {$MAX_INTENSITY*0.05*$MAX_INTENSITY}] } {
    set r [expr {($MAX_INTENSITY + 3*$R)/4}]
    set g [expr {($MAX_INTENSITY + 3*$G)/4}]
    set b [expr {($MAX_INTENSITY + 3*$B)/4}]
  } else {
    set r [expr {(60 * $R)/100}]
    set g [expr {(60 * $G)/100}]
    set b [expr {(60 * $B)/100}]
  }

# ----------------------------------------------------------------------

  return [format #%04X%04X%04X $r $g $b]

}

# ======================================================================

# shadow:light --

# Compute the light shadow color for a 3d border.

# Boost each component by 40% or half-way to white, whichever is greater
# (the first approach works better for unsaturated colors, the second
# for saturated ones). If the background is already very bright, instead
# choose a slightly darker color for the light shadow by reducing each
# color component by 10%.

# This adapted from tk8.4.0/unix/tkUnix3d.c

proc color::shadow:light {R G B} {

# ----------------------------------------------------------------------

  variable MAX_INTENSITY

# ----------------------------------------------------------------------

  if { $G > [expr {$MAX_INTENSITY*0.95}] } {
    set r [expr {(90 * $R)/100}]
    set g [expr {(90 * $G)/100}]
    set b [expr {(90 * $B)/100}]
  } else {
    set tmp1 [expr {(14 * $R)/10}]

    if { $tmp1 > $MAX_INTENSITY } { set tmp1 $MAX_INTENSITY }

    set tmp2 [expr {($MAX_INTENSITY + $R)/2}]
    set r    [expr {($tmp1 > $tmp2) ? $tmp1 : $tmp2}]
    set tmp1 [expr {(14 * $G)/10}]

    if { $tmp1 > $MAX_INTENSITY } { set tmp1 $MAX_INTENSITY }

    set tmp2 [expr {($MAX_INTENSITY + $G)/2}]
    set g    [expr {($tmp1 > $tmp2) ? $tmp1 : $tmp2}]
    set tmp1 [expr {(14 * $B)/10}]

    if { $tmp1 > $MAX_INTENSITY } { set tmp1 $MAX_INTENSITY }

    set tmp2 [expr {($MAX_INTENSITY + $B)/2}]
    set b    [expr {($tmp1 > $tmp2) ? $tmp1 : $tmp2}]
  }

# ----------------------------------------------------------------------

  return [format #%04X%04X%04X $r $g $b]

}

# ======================================================================

# demo code
proc rect {c x1 y1 x2 y2 bd color} {

  set dark  [eval color::shadow:dark  [winfo rgb $c $color]]
  set light [eval color::shadow:light [winfo rgb $c $color]]
  puts "dark  $color = \[$dark\]"
  puts "light $color = \[$light\]"

  $c create polygon \
    $x1 $y1 \
    $x2 $y1 \
    [expr $x2 - $bd] [expr $y1 + $bd] \
    [expr $x1 + $bd] [expr $y1 + $bd] \
    [expr $x1 + $bd] [expr $y2 - $bd] \
    $x1 $y2 \
    -fill    $light \
    -outline $light

  $c create polygon \
    $x2 $y1 \
    $x2 $y2 \
    $x1 $y2 \
    [expr $x1 + $bd] [expr $y2 - $bd] \
    [expr $x2 - $bd] [expr $y2 - $bd] \
    [expr $x2 - $bd] [expr $y1 + $bd] \
    -fill    $dark \
    -outline $dark

  $c create rectangle \
    [expr $x1 + $bd] [expr $y1 + $bd] \
    [expr $x2 - $bd] [expr $y2 - $bd] \
    -fill    $color \
    -outline $color

  return

}

  button .b -text "Hello World" -bg red -fg white -bd 10
  canvas .c

  pack .b
  pack .c -expand 1 -fill both

  update idletasks

  rect .c 10 10 \
    [expr [winfo reqwidth  .b] + 10] \
    [expr [winfo reqheight .b] + 10] \
    10 red

# ======================================================================

Note that RGB values are specified as 16-bit values, as returned from [winfo rgb].


Note that at least on Microsoft Windows, I changed the winfo width/height to winfo reqwidth/reqheight. Otherwise you get an incorrect width/height. (I would have thought the update would have taken care of this??) Brett Schwarz

Try binding to <Configure> and use %w and %h (more robust).

MGS [2003/04/08] - Yeah, the emphasis here was not on the correctness of the demo code :-) It was really just for a comparison of the hilight/shadow colors. I think I meant to use reqwidth/reqheight, but I was in a rush to get it posted, so now I've changed it.


uniquename 2014jan27

For those who do not have the facilities or time to implement the code above, here is an image of the two beveled rectangles drawn in a Tk 'wish' window --- one rectangle (with text) made using a Tk 'button' widget and one rectangle (without text) made by drawing polygons on a canvas widget with a 'rect' proc.

rectanglesDrawnOnCanvas_with3DbevelColors_wiki8722_288x141.jpg

The 'button' widget is packed above the 'canvas' widget. If the canvas had been defined with raised relief, it would be clear where the canvas boundaries are.