GUI for Drawing a 3D Donut (a ring) with nice shaded edges

uniquename - 2012oct24

I am interested in making nice images for 'toolchest' and 'drawer' backgrounds (and other GUI embellishments), as I have indicated at Experiments in making embellished GUI's and at A color-gradient-button-maker GUI.

Recently I saw an advertising picture for a drama. The picture was of a metal-looking heart with several 3D-looking bullet holes in it. Each of the bullet holes had an indented edge, and from that indentation came the shaded, 3D-look.

The heart image looked somewhat like the following image

valentineHeart_withBulletHole_146x143.jpg

but the bullet holes looked more like this:

bulletHole_shaded_71x71.jpg

I was more interested in the holes than in the heart-shape. I figured that I could use images of a circle-shape with a hole in it.

I had several ideas for uses for such a donut-shaped or ring-shaped image:

1) At a large size, the 3D-donut image could be used as a background for a logo.

2) At a medium size, the image could be used as a background for an icon.

3) And if the image were shrunk to a small size, the image could be used as an unusual 'bullet' (a 'bullet-with-a-bullet-hole-in-it') for line items on a web page or for menu items in a Tk GUI 'toolchest'.

I recently made scripts for making objects with shaded-edges, yielding a 3D-look:

These scripts used the concept of a 'color-metric' --- a scalar function defined over the x,y pixels of a Tk canvas image. The metric had the value 1.0 on the boundary of the shape, and the value 0.0 in the center.

The metrics that I used for the 3 shapes above were

  • v = |x/a|^n + |y/b|^n --- for the super-ellipse
  • v = sqrt(x*x + y*y) / R(theta) --- for the super-formula
  • v = max(abs(x/xhalf), abs(y/yhalf)) --- for the rectangle

(See the pages above for details.)

So I knew I should be able to make a Tk script for creating a ring --- say with internal radius 'r' and external radius 'R' --- with an edge-shaded hole in it. In fact, there should be edge-shading at both radius 'r' and radius 'R'.

The tricky thing in this case is that I needed the metric to have the value 0.0 in a place other than the center of the circular shape.

In fact, it would be nice if the metric were zero in the middle of the donut/ring --- at the average radius (R + r)/2.

_____

DERIVATION OF A 'COLOR METRIC' FOR THE 'DONUT' :

Let 'r' be the radius of the circular hole in the donut and let 'R' be the radious of the outer edge of the circular donut.

Let any point x,y in the rectangular image area be measured from the center of the rectangular image. Let 'rho' denote the radial distance of the point x,y from that center. So

  rho(x,y) = sqrt (x*x + y*y)

We note that a point x,y is on the circle in the middle of the donut (between the hole and the outer edge) if

      rho = (R + r) / 2

We note that the quadratic form v = (rho - (R+r)/2) ^ 2 is a parabola, and v is non-negative. If you plot 'v' versus 'rho', the graph touches the 'rho' axis at (R+r)/2.

To make this a suitable metric, we need to apply a factor such that v is 1.0 at rho = r and at rho = R.

A little algebra shows that our metric is given by

    v = a * (rho - (R+r)/2) ^ 2

where the coefficient 'a' is given by 1 / ((R-r)/2) ^ 2.

Now we have a suitable metric, v.

_____

USING THE METRIC:

At a point x,y, we determine the 'shaded color' at the point by using a color interpolated between the user-selected 'fill' color (color1) of the 'donut shape' and the user-selected background color (color2).

We calculate the 'shaded color' at x,y by calulating a weighted average based on applying the factor (1.0 - $v) to color1 --- and applying $v to color2. That is, shaded-color = (1 - v) * color1 + v * color2.

We actually calculate via formulas like

   shaded-R = (1 - v) * R1 + v * R2
   shaded-G = (1 - v) * G1 + v * G2
   shaded-B = (1 - v) * B1 + v * B2

Thus we will get the edge-shading (the 3D effect) for the 'donut shape'.

Actually, it turns out that 1-v and v gives a rather washed-out (too gradual) shading effect. It is better if we raise v to a power N and use v^N and (1 - v^N). It turns out that N = 3 or 4 gives pretty nice shading for the donut shape, but rather than hard-code the value of N, we provide a scale widget on the GUI so that the user can set the value of N.

_____

THE GUI DESIGN:

To create the Tk script I used the code of GUI for Drawing a 'Super-Formula' Shape, with nice shaded border as a starting point. That GUI had most of the needed widgets.

Of course, the GUI made by this Tk script needs to contain a rectangular canvas widget on which the color-filled donut shape will be drawn.

I decided to put 2 'scale' widgets on the GUI --- whose slider-bars can be used to change the values of 'r' and 'R'.

And I included a scale for the exponent N to control the extent of the shading --- which is achieved by using the two color weighting factors v^N and (1.0 - v^N).

Like in the other 'color-metric' GUI's, I used two buttons --- for 'fill-color' and 'background color' --- to call on a color selector GUI.

(I could add a checkbutton widget so that I could turn the 'edge-shading' on or off, but I did not do that. That's left as an exercise. But note that you can get the same effect by setting N real high.)

After some coding of a 'ReDraw' proc that uses the 'color-metric' to draw the shaded donut, I ended up with the following GUI --- and the code presented below.

donutMakingGUI_edgeShaded_magentaONblack_screenshot_590x415.jpg

Note that I include a display of 'elapsed time' for each redraw --- by using the Tcl 'clock milliseconds' command.

Having learned my lesson (from development of the script at GUI for Drawing 'Super-ellipses', with nice shaded edges) about using braces with 'expr' statements, I consistently used braces with ALL 'expr' commands.

---

Note that the 2 color buttons call on a color-selector-GUI script to set those colors. You can make that color-selector Tk script by cutting-and-pasting the code from the page A non-obfuscated color selector GUI on this site.

_____________________________________________________________________

Below is the code that produced this GUI.

There are comments at the top of the code, in a section titled 'USING THE GENERATED IMAGE', that describe how one could make use of images produced by this GUI.

I follow my usual 'canonical' structure for Tk code, for this Tk script:

  0) Set general window & widget parms (win-name, win-position,
     win-color-scheme, fonts, widget-geometry-parms, win-size-control).

  1a) Define ALL frames and sub-frames.
  1b) Pack   ALL frames and sub-frames.

  2) Define & pack all widgets in the frames.

  3) Define keyboard and mouse/touchpad/touch-sensitive-screen action
     BINDINGS, if needed.

  4) Define PROCS, if needed.

  5) Additional GUI initialization (typically with one or more of
     the procs), if needed.

This structure is discussed in more detail on the page A Canonical Structure for Tk Code --- and variations.

This structure makes it easy for me to find code sections --- while generating and testing a Tk script, and when looking for code snippets to include in Tk scripts (code re-use).

_________________________________________________________________

As in all my scripts that use the 'pack' geometry manager (which is all of my 100-plus Tk scripts, so far), I provide the four main pack parameters --- '-side', '-anchor', '-fill', and '-expand' --- on all the 'pack' commands for the frames and widgets.

I think I have found a good setting of the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various widgets of this GUI. In particular ...

The 'canvas' widget expands/contracts appropriately when the window size is changed --- and button and label widgets stay fixed in size and relative-location as the window size is changed.

If anyone wants to change the way the GUI configures itself as the main window size is changed, they can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various widgets --- to get the widget behavior that they want.

For example, you may want the scale widgets to stay fixed in length, rather than x-expanding whenever the window is expanded in the x-direction.

You might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. Or change font families.

In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.

I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

_____________________________________________________________________

That said, here's the code --- with plenty of comments to describe what most of the code-sections are doing.

The most complex code is in the 'ReDraw' proc. I attempted to significantly reduce the number of 'put' commands used to draw an image --- by using 'horizontal-scanlines' (of hex-colors) in the areas of the image where the color is totally the background color --- i.e. above and below the square containing the donut --- and on the left and right of that square.

When I first wrote the 'ReDraw' proc, I 'put' ALL hexcolors at each individual pixel --- no use of the horizontal scanline technique. The draw times for the image were around 10 seconds or more.

By using the 'horizontal-scanlines' technique with the 'put' command, I was able to reduce the draw times to about half a second.

It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this. Without the comments --- especially in the 'ReDraw' proc, the code would look like 'too much monkey business to be involved in', to quote Chuck Berry.

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch America's/Britain's/Icelands's/Greenland's Funniest Home Videos.


 Code of the Tk script 'make_donut3D_shadedEdges.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: make_donut3D_shadedEdges.tk
##
## 
## PURPOSE:  This Tk GUI script 'draws' a color-filled 'donut shape'
##           --- including shading to a background color, around the
##           two edges (outer and inner) of the donut shape.
##
##           This 'donut' could be made small, for example, to be
##           used as a 'bullet' decorative item --- for a list of items
##           on a web page or a Tk GUI. So, in that use-case, this
##           shaded object can be thought of as a 'bullet' with a
##           bullet-hole in it.
##
##           Source of the bullet-hole metaphor:
##           An advertising picture of a metal heart with
##           a 3D-looking bullet hole in it gave me the idea
##           of creating an object with an edge-shaded hole in it.
##           
##           (The bullet hole had an indented edge, and thus the shading.)
##
##    Examples of use of this donut-shaped image:
##    1) As mentioned above, this 'holy-bullet' could be used as an
##       unusual 'bullet' for line items on a web page or for menu
##       items in a Tk GUI 'toolchest' --- if the image is shrunk to
##       a small size.
##    2) At a large size, the 3D-donut image could be used as a
##       background for a logo.
##    3) At a medium size, the image could be used as a background
##       for an icon.
##
## METHOD:
##
##    This script makes this shaded shape via  a Tk image 'structure'
##    placed on a Tk canvas widget.
##
##    The image is put on the canvas via a Tk canvas 'image create' command
##    --- and the image is generated via 'put <hexcolor> -to $x $y'
##    commands on the image.
##
##    The rectangular image-structure covers the entire canvas widget. The
##    donut-shape lies within the canvas (and the image structure) ---
##    with a margin around it.  In the margin, outside the
##    donut-shape on the canvas, a user-selected 'background' color
##    is applied. The background color is also applied to the hole
##    in the donut shape.
##
## REFERENCES and CREDITS:
##
##  This Tk script is based on my Tk script at https://wiki.tcl-lang.org/37156
##  - 'GUI for Drawing 'Super-formula' shapes, with nice shaded border'.
##
##  In that script, I devised a 'color-metric' v at each point x,y.
##  Then v and (1-v) could be applied to a user-selected background color
##  and a user-selected 'fill' color for the super-formula shape, to get
##  a 'weighted-average' of the two colors, for the color at the pixel x,y.
##
##  The equation for the 'super-formula' is:
##
##  R(theta) = ( |cos(m*theta/4)/a| ^ n2 + |sin(m*theta/4)/b| ^ n3 ) ^ -1/n1
##
##  where the points on the border of the super-formula shape are given by
##    x = R(theta) cos(theta)
##    y = R(theta) sin(theta).
##
##  I defined a 'color-metric' at any point x,y on the rectangular image
##  on the rectangular canvas by
##     v = r(x,y) / R(theta)
##  where r(x,y) = sqrt(x*x + y*y).
##
##  v is 0.0 in the center of the super-formula shape and 
##  v is 1.0 on the border of the super-formula shape.
##
##  The canvas 'create image' (and 'put' commands) technique used in the
##  super-formula script is similar to the technique used in two of my
##  scripts that make SHADED EDGES around 'super-ellipses' and around
##  color-gradient-rectangles.
##
##  See
##      https://wiki.tcl-lang.org/37004 -
##      GUI for Drawing 'Super-ellipses', with nice shaded edges
##  and
##      https://wiki.tcl-lang.org/37143 -
##      GUI for Drawing Rectangular 'Buttons' with nice shaded edges
##
##  Those 2 scripts also use a 'color metric' v --- and the factors
##  v and (1 - v) were used get a weighted average of 2 colors to
##  apply that color to a pixel at x,y.
##
##+#####################
## THE SHADING TECHNIQUE (metric) for the super-ellipse:
##
##     For detail on the shading technique applied to super-ellipses,
##     see  wiki.tcl.tk/37004 -
##     GUI for Drawing 'Super-ellipses', with nice shaded edges.
##
##     The edge-shading effect for the super-ellipse benefited from the equation
##     for a super-ellipse --- more precisely, the equation for its edge:
##              |x/a|^n + |y/b|^n   =  1
##
##     The interior of the super-ellipse is given by the inequality
##              |x/a|^n + |y/b|^n  <=  1
##
##     The edge shading (3D effect) was obtained by using a 'metric' on the
##     points x,y in the super-ellipse --- a value 'v', between 0 and 1,
##     given by:  
##                     v = |x/a|^n + |y/b|^n
##     for each point inside the super-ellipse.
##
##     The equation for v dictates that the value of v is 1 on the border of
##     the super-ellipse and declines to 0 towards the center.
##
##     (1.0 - $v) is applied to the user-selected 'unshaded' RGB-color for
##     the super-ellipse --- and  $v  applied to the user-selected RGB
##     background color. The weighted-average of the pair of RGB values
##     gives us the color at any x,y point in the super-ellipse.
##
##     (Note that v is not a constant. The values of x and y gave us the
##      'v' value to apply to get the 'shaded' color at x,y --- i.e.
##      v is a function of x and y.)
##
##+##############################################
## DERIVATION OF A 'COLOR METRIC' FOR THE 'DONUT':
## 
##  Let r be the radius of the circular hole in the donut and let
##  R be the radious of the outer edge of the circular donut.
##
##  Let any point x,y in the rectangular image area be measured
##  from the center of the rectangular image. Let 'rho' denote
##  the radial distance of the point x,y from that center. So
##
##  rho(x,y) = sqrt (x*x + y*y)
##
##  We note that a point x,y is on the circle in the middle of
##  the donut (between the hole and the outer edge) if
##      rho = (R + r) / 2
##
##  We note that the quadratic form  v =  (rho - (R+r)/2) ^ 2
##  is a parabola. If you plot v agains rho, the graph
##  touches the rho axis at (R+r)/2.
##
##  To make this a suitable metric, we need to apply a factor
##  such that v is 1.0 at rho = r and at rho = R.
##
##  A little algebra shows that our metric is given by
##
##    v = a * (rho - (R+r)/2) ^ 2
##
##  where the coefficient a is given by 1 /  ((R-r)/2) ^ 2.
##
##  Now we have a suitable metric, v.
##
##     At a point x,y, we determine the 'shaded color' at the point by
##     using a color interpolated between the user-selected 'fill' color
##     (color1) of the 'donut shape' and the user-selected
##     background color (color2).
##
##     We calculate the 'shaded color' at x,y by calulating a weighted average
##     based on applying the factor (1.0 - $v) to color1 --- and applying $v
##     to color2. That is, shaded-color = (1 - v) * color1 + v * color2.
##
##     We actually calculate via formulas like
##        shaded-R = (1 - v) * R1 + v * R2
##        shaded-G = (1 - v) * G1 + v * G2
##        shaded-B = (1 - v) * B1 + v * B2
##
##    Thus we will get the edge-shading (the 3D effect) for the
##    'donut shape'.
##
##     Actually, it turns out that 1-v and v gives a rather washed-out (too
##     gradual) shading effect. It is better if we raise v to a power N
##     and use v^N and (1 - v^N).  It turns out that N = 3 gives pretty nice
##     shading for the donut shape, but rather than hard-code the value of N,
##     we provide a scale widget on the GUI so that the user can set the
##     value of N.
##
##+##############
## THE GUI DESIGN:
##
##           The GUI made by this Tk script contains a rectangular
##           canvas widget on which the color-filled donut shape
##           will be drawn.
##
##           The GUI includes 2 'scale' widgets whose slider-bars can
##           be used to change the values of r and R.
##
##           There is also a scale for the exponent N to control the extent
##           of the shading --- by using the two color weighting factors
##           v^N and  (1.0 - v^N).
##
##           The GUI also includes a button to call a color selector GUI
##           to set the 'fill' color of the donut shape.
##
##           Another button calls the same color selector GUI to set a
##           background color ---  which is also the color we gradiate
##           toward at the inner and outer boundaries of the 'donut shape'.
##
##           (Note that we could put another color selector button on the
##            GUI to select the border color to be a different color from
##            the background color of the canvas area.)
##
##           A redraw includes clearing the canvas and redrawing the
##           donut shape.
##
##           A redraw should be done (a) whenever any of the 3 scales change,
##           (b) whenever a color button is used to change a color, and
##           (c) whenever the window (and thus the canvas) is resized ---
##           so that the image/shape will be redrawn in the center of
##           the canvas.
##
##       NOTE: We could have let the size of the canvas determine the
##             size of R and eliminated the R-scale. That would have
##             been a little more convenient for the user, because if
##             they resize the canvas, they probably want the donut
##             to resize to fit inside the canvas with about a 10% margin.
##             This anticipation of what the user wants (and eliminating
##             the user's need to adjust the R-scale) would come at the
##             expense of a little flexibility (if we eliminate the R-scale).
##
##             Another alternative, would be to keep the R-scale but
##             also automatically change the size of R if the user
##             resizes the window (and thus the size of the canvas).
##
##    PERFORMANCE CONSIDERATIONS:
##        Since the redraw has a lot of pixels to color, especially when
##        the canvas is expanded to a pretty large size, a redraw may
##        take several seconds.
##
##        So it is probably not going to be feasible/pleasing to do redraws
##        'dynamically' with the '-command' option of the 'scale' widgets.
##
##        For now, I have defined a button1-release binding on the 3
##        scale widgets to trigger a redraw --- only when the user
##        finishes dragging the sliderbar of any scale.
##
##        However, it should be pointed out that if erasing the
##        canvas and calculating-colors and putting the colors in the
##        donut shape completes within a small fraction of a second, it
##        would be feasible to do the redraws 'dynamically' with each
##        sliderbar, via the '-command' option. (But it might heat up
##        the CPU doing those operations --- by quite a few degrees.)
##
## USING THE GENERATED IMAGE:
##           A screen/window capture utility (like 'gnome-screenshot'
##           on Linux) can be used to capture the GUI image in a GIF
##           or PNG file, say.
##
##           If necessary, an image editor (like 'mtpaint' on Linux)
##           can be used to crop the window capture image.  The image
##           could also be down-sized --- say to make a 'bullet' image
##           file or an icon-background image file.
##
##           The colored image file could be used with a utility (like the
##           ImageMagick 'convert' command) to change the outer&inner background
##           color to TRANSPARENT, making a partially transparent GIF
##           (or PNG) file. Then the semi-transparent image file could be used,
##           for 'bullets' in HTML pages or in Tk GUI's --- or for the
##           background of icons or buttons for use in GUIs/web-pages.
##
##           The image could also be taken into a scalable vector graphics
##           (SVG) editor (like Inkscape on Linux) and the SVG editor used
##           to add anti-aliased, scalable text to the image. OR, the shape can
##           be used as an underlying pattern to reproduce the shape as a
##           scalable shape, by using curve drawing tools of the SVG editor.
##           The SVG image could then be saved as a totally scalable image,
##           without a raster-image needing to be stored in the SVG file.
##
##+########################################################################
## 'CANONICAL' STRUCTURE OF THIS TK CODE:
##
##  0) Set general window & widget parms (win-name, win-position,
##     win-color-scheme, fonts, widget-geometry-parms, win-size-control).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack   ALL frames and sub-frames.
##
##  2) Define all widgets in the frames. Pack them.
##
##  3) Define keyboard or mouse/touchpad/touch-sensitive-screen action
##     BINDINGS, if needed.
##
##  4) Define PROCS, if needed.
##
##  5) Additional GUI INITIALIZATION (typically with one or two of
##     the procs), if needed.
##
##
## Some detail about the code structure of this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :  '.fRbuttons'
##                   '.fRscales'
##                   '.fRcanvas'
##      No sub-frames.
##
##  1b) Pack ALL frames.
##
##  2) Define all widgets in the frames (and pack them):
##
##     - In '.fRbuttons':
##                          1 button widget ('Exit'), (perhaps 'Help' someday)
##                            and
##                          2 buttons (for setting the donut shape's
##                                     fill & the background/canvas color),
##                            and
##                          1 label widget to display the current color
##                                  values (in hex) --- and elapsed execution time,
##                                  as well as a 'calculation in progress' msg.
##
##     - In '.fRscales':
##                          1 'label' and 1 'scale' widget each for r, R, and N
##                             (where N is an exponent used to control the
##                              extent of the edge-shading.)
##
##     - In '.fRcanvas':    1 'canvas' widget 
##
##  3) Define bindings:
##
##       - a button1-release on each of the 3 scale widgets causes a redraw
##
##    NOTE:  The color changes should trigger a redraw, but we do not
##           need bindings to do those redraws.
##           The redraws can be done in procs that are used to
##           set each of the colors.
##
##  4) Define procs:
##
##     - 'ReDraw'          - to clear the canvas and redraw the pixels
##                           in the image rectangle that contains the
##                           donut shape
##                           --- for the current values of the 3 scale
##                           parameters and the fill & background colors.
##
##     - set_scale_r_lessThan_R_redraw     - to move r slider, limited by value R.
##
##     - set_scale_R_greaterThan_r_redraw  - to move R slider, limited by value r.
##
##     - 'set_shape_color1'   - shows a color selector GUI and uses the
##                                user-selected color to 'fill' the 
##                                donut shape on the canvas
##
##     - 'set_color_background' - shows a color selector GUI and uses the
##                                user-selected color to reset the color of
##                                the canvas background
##
##     - 'ReDraw_if_canvas_resized' - to do a redraw when a <Configure>
##                                    event is detected on the canvas ---
##                                    but only if the canvas has been resized.
##
##  5) Additional GUI initialization:  Execute proc 'ReDraw' once with
##                                     an initial, example set of parms
##                                     --- 3 scale vars, COLOR1hex,
##                                     COLORbkGNDhex ---
##                                     to start with a donut shape on
##                                     the canvas rather than a blank canvas.
##
##     Also in this section, we define a binding for
##       - a <Configure> (resize) event on the canvas --- to cause a redraw.
##+########################################################################
## DEVELOPED WITH:
##   Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala').
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##                                  showed   8.5 8.5   on Ubuntu 9.10
##    after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts.
##+#######################################################################
## MAINTENANCE HISTORY:
## Created by: Blaise Montandon 2012oct23
## Changed by: Blaise Montandon 2012oct24 Draw the background color around
##                                        the square containing the 'donut'
##                                        with 'scanlines' rather than
##                                        pixel-by-pixel --- to speed up
##                                        drawing by a factor of about 8
##                                        --- from about 8 secs to 1 sec.
##+#######################################################################

##+#######################################################################
## Set general window parms (win-title,win-position).
##+#######################################################################

wm title    . "Edge-shaded 'donut' shape, on a single-color canvas"
wm iconname . "3Ddonut"

wm geometry . +15+30


##+######################################################
## Set the color scheme for the window and its widgets ---
## and set the initial color for the donut-shape interior
## and the canvas background (outside the donut-shape).
##+######################################################

tk_setPalette "#e0e0e0"

## Initialize the donut shape 'fill' color.

# set COLOR1r 255
# set COLOR1g 255
# set COLOR1b 255
set COLOR1r 255
set COLOR1g 0
set COLOR1b 255
set COLOR1hex [format "#%02X%02X%02X" $COLOR1r $COLOR1g $COLOR1b]

## Initialize the donut shape 'boundary' color
## to gradiate to, from the 'fill' color.
## NOT USED.
## We use the background color as the color to gradiate to.
# # set COLOR2r 255
# # set COLOR2g 255
# # set COLOR2b 0
# # set COLOR2r 0
# set COLOR2g 255
# set COLOR2b 255
# set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b]


## Initialize the background color for the canvas.

# set COLORbkGNDr 60
# set COLORbkGNDg 60
# set COLORbkGNDb 60
set COLORbkGNDr 0
set COLORbkGNDg 0
set COLORbkGNDb 0
set COLORbkGNDhex \
    [format "#%02X%02X%02X" $COLORbkGNDr $COLORbkGNDg $COLORbkGNDb]

# set listboxBKGD "#f0f0f0"
# set entryBKGD   "#f0f0f0"


##+########################################################
## Use a VARIABLE-WIDTH FONT for label and button widgets.
##
## Use a FIXED-WIDTH FONT for listboxes (and
## entry fields, if any).
##+########################################################

font create fontTEMP_varwidth \
   -family {comic sans ms} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_varwidth \
   -family {comic sans ms} \
   -size -12 \
   -weight bold \
   -slant roman

## Some other possible (similar) variable width fonts:
##  Arial
##  Bitstream Vera Sans
##  DejaVu Sans
##  Droid Sans
##  FreeSans
##  Liberation Sans
##  Nimbus Sans L
##  Trebuchet MS
##  Verdana


font create fontTEMP_fixedwidth  \
   -family {liberation mono} \
   -size -14 \
   -weight bold \
   -slant roman

font create fontTEMP_SMALL_fixedwidth  \
   -family {liberation mono} \
   -size -12 \
   -weight bold \
   -slant roman

## Some other possible fixed width fonts (esp. on Linux):
##  Andale Mono
##  Bitstream Vera Sans Mono
##  Courier 10 Pitch
##  DejaVu Sans Mono
##  Droid Sans Mono
##  FreeMono
##  Nimbus Mono L
##  TlwgMono



##+###########################################################
## SET GEOM VARS FOR THE VARIOUS WIDGET DEFINITIONS.
## (e.g. width and height of canvas, and padding for Buttons)
##+###########################################################

set initCanWidthPx 400
set initCanHeightPx 300
set minCanHeightPx 24

# set BDwidthPx_canvas 2
  set BDwidthPx_canvas 0


## BUTTON geom parameters:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## LABEL geom parameters:

set BDwidthPx_label 2


## SCALE geom parameters:

set BDwidthPx_scale 2
# set initScaleLengthPx 100
set scaleWidthPx 10


##+###################################################################
## Set a MINSIZE of the window.
##
## For width, allow for the minwidth of the '.fRbuttons' frame:
##            about 3 buttons (Exit,Color1,ColorBkgnd).
##            We want to at least be able to see the Exit button.
##
## For height, allow
##             2 chars  high for the '.fRbuttons' frame,
##             1 char   high for the '.fRscales' frame,
##            24 pixels high for the '.fRcanvas' frame.
##+###################################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
   "Exit  Fill   Background"]

## Add some pixels to account for right-left-side window decoration
## (about 8 pixels), about 3 x 4 pixels/widget for borders/padding for
## 3 widgets --- 3 buttons.

set minWinWidthPx [expr 20 + $minWinWidthPx]


## MIN HEIGHT ---
## for the 3 sub-frames '.fRbuttons', '.fRscales',
## and '.fRcanvas'.
## Allow
##    2 char   high for 'fRbuttons'
##    1 char   high for 'fRscales'
##   24 pixels high for 'fRcanvas'

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr 3 * $CharHeightPx]

## Add about 28 pixels for top-bottom window decoration,
## about 3x4 pixels for each of the 3 stacked frames and their
## widgets (their borders/padding).

set minWinHeightPx [expr $minWinHeightPx + 40]


## FOR TESTING:
#   puts "minWinWidthPx = $minWinWidthPx"
#   puts "minWinHeightPx = $minWinHeightPx"

wm minsize . $minWinWidthPx $minWinHeightPx


## We allow the window to be resizable and we pack the canvas with
## '-fill both -expand 1' so that the canvas can be enlarged by enlarging
## the window.

## If you want to make the window un-resizable, 
## you can use the following statement.
# wm resizable . 0 0


##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : '.fRbuttons' '.fRscales'  '.fRcanvas'
##+################################################################

# set BDwidth_frame 2
# set RELIEF_frame raised

  set BDwidth_frame 0
  set RELIEF_frame flat

frame .fRbuttons -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRscales  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRcanvas  -relief $RELIEF_frame  -borderwidth $BDwidth_frame


##+##############################
## PACK the top-level FRAMES. 
##+##############################

pack .fRbuttons \
     .fRscales \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRcanvas \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1


##+#########################################################
## OK. Now we are ready to define the widgets in the frames.
##+#########################################################


##+#####################################################################
## In the '.fRbuttons' FRAME  ---  DEFINE-and-PACK
##    - an exit-button,
## and
##    - 2 buttons ( to specify colors)
## and
##   - a label widget, to show current color values (in hex)
##+#####################################################################

button .fRbuttons.buttEXIT \
   -text "Exit" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {exit}

## Add this button someday?
# button .fRbuttons.buttHELP \
#   -text "Help" \
#   -font fontTEMP_varwidth \
#   -padx $PADXpx_button \
#   -pady $PADYpx_button \
#   -relief raised \
#   -bd $BDwidthPx_button \
#   -command {help}

button .fRbuttons.buttCOLOR1 \
   -text "\
Fill
 Color" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_shape_color1"

## Not used. Someday?
# button .fRbuttons.buttCOLOR2 \
#    -text "\
# Edge-gradient
#  Color" \
#    -font fontTEMP_varwidth \
#    -padx $PADXpx_button \
#    -pady $PADYpx_button \
#    -relief raised \
#    -bd $BDwidthPx_button \
#    -command "set_shape_color2"


button .fRbuttons.buttCOLORbkGND \
   -text "\
Background
  Color" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_background_color"

## The text for labelCOLORS is set in the ReDraw proc.
## It is done there to make sure that the colors used for
## the current drawing are displayed correctly.

label .fRbuttons.labelCOLORS \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

##+###########################################
## Pack the widgets in the 'fRbuttons' frame.
##+###########################################

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttCOLOR1 \
     .fRbuttons.buttCOLORbkGND \
     .fRbuttons.labelCOLORS \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

#     .fRbuttons.buttHELP \

#     .fRbuttons.buttCOLOR2 \


##+##################################################################
## In the '.fRscales' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget for r
## 1 LABEL and 1 SCALE widget for R
## 1 LABEL and 1 SCALE widget for N
##+###################################################################

label .fRscales.label_r \
   -text "\
Inner radius
 r (in pixels):" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

## Set this widget var in the GUI initialization section
## at the bottom of this script.
# set r 100

scale .fRscales.scale_r \
   -from 0 -to 500 \
   -resolution 1 \
   -bigincrement 1 \
   -repeatdelay 1000 \
   -length 100 \
   -font fontTEMP_SMALL_varwidth \
   -variable r \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#    -command "set_scale_r_lessThan_R_redraw"

pack .fRscales.label_r \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscales.scale_r \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

## DEFINE SCALE for R.

label .fRscales.label_R \
   -text "\
Outer radius
 R (in pixels):" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

## Set this widget var in the GUI initialization section
## at the bottom of this script.
# set R 200

scale .fRscales.scale_R \
   -from 0 -to 800 \
   -resolution 1 \
   -bigincrement 1 \
   -repeatdelay 1000 \
   -length 100 \
   -font fontTEMP_SMALL_varwidth \
   -variable R \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#    -command "set_scale_R_greaterThan_r_redraw"

pack .fRscales.label_R \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscales.scale_R \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##  DEFINE SCALE for exponent N.

label .fRscales.label_N \
   -text "\
exponent N to control
extent of edge shading" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

## Set this widget var in the GUI initialization section
## at the bottom of this script.
# set N 12

scale .fRscales.scale_N \
   -from 1 -to 30 \
   -resolution 1 \
   -bigincrement 1 \
   -repeatdelay 1000 \
   -length 60 \
   -font fontTEMP_SMALL_varwidth \
   -variable N \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRscales.label_N \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscales.scale_N \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+######################################################
## In the '.fRcanvas' FRAME -
## DEFINE-and-PACK the 'canvas' widget
##+######################################################
## We set highlightthickness & borderwidth of the canvas to
## zero, as suggested on page 558, Chapter 37, 'The Canvas
## Widget', in the 4th edition of the book 'Practical
## Programming in Tcl and Tk'.
##+######################################################

canvas .fRcanvas.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

pack .fRcanvas.can \
   -side top \
   -anchor nw \
   -fill both \
   -expand 1


##+########################################
## END OF the DEFINITION OF THE GUI WIDGETS
##+########################################


##+###############################
## BINDINGS SECTION:
##+###############################

## The following <Configure> bind causes an extra ReDraw
## when the GUI is first configured via an 'update' below
## in the GUI initialization section.
##    (And 'update' causes about 40 redraws if
##     we use '.' instead of '.fRcanvas.can'.)
## We move this statement to the bottom of this script.

# bind .fRcanvas.can <Configure> "ReDraw 0"

bind .fRscales.scale_r  <ButtonRelease-1> "set_scale_r_lessThan_R_redraw 0"
bind .fRscales.scale_R  <ButtonRelease-1> "set_scale_R_greaterThan_r_redraw 0"
bind .fRscales.scale_N  <ButtonRelease-1> "ReDraw 0"


##+######################################################################
## PROCS SECTION:
##
##  - ReDraw            - Called by button1-release bindings on the 
##                        3 scale widgets, by the set-color procs,
##                        and in the GUI initialization section at the
##                        bottom of this script.
##
##                        Draws the donut shape on the canvas for
##                        the current scale parameter values and for the
##                        current color var values.
##
##  - set_scale_r_lessThan_R_redraw     - called by a button1-release binding for scale-r
##
##  - set_scale_R_greaterThan_r_redraw  - called by a button1-release binding for scale-R
##
##  - set_shape_color1   - called by color1 (fill) button '-command'
##
##  - set_shape_color2   - called by color2 (outline) button '-command'
##                         (NOT USED, yet)
##
##  - set_background_color  - called by background color button '-command'
##
##  - ReDraw_if_canvas_resized - called by 'bind' to canvas <Configure>
##
##+#######################################################################


##+#####################################################################
## proc ReDraw -
##
## PURPOSE:
##     Draws the donut shape on the canvas.
##
##     We will use symmetry in the 'donut shape' to draw the upper
##     left quadrant of the image 'poking' hex-colors a pixel at a time
##     with calls like: 
##           imgID put $hexcolor -to $x $y
##     where $ $y is measured (in pixels) relative to the middle of
##     the rectangular canvas/image area.
##
## CALLED BY:  bindings (in the BINDINGS section) --- and by set-color procs
##             --- and by the GUI initialization section at the bottom of
##             this script.
##
## NOTE: The 'x' argument is to avoid an error when the scale '-command'
##       passes a scale value as an argument to the command. This is in
##       case we try using the '-command' option of the scale widgets to
##       do the redraws 'dynamically' as a sliderbar is moved.
##+#####################################################################
## For reference, our 'color-metric' is given by
##
##    v = a * (rho - (R+r)/2) ^ 2
## where
##    a = 1 /  ((R-r)/2) ^ 2
## and
##   rho = sqrt(x*x + y*y)
## where x,y are measured from the middle of the canvas/image rectangle.
##+#####################################################################


proc ReDraw {x} {

   global r R N \
         COLOR1r COLOR1g COLOR1b COLOR1hex \
         COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex

  ## COLOR2r COLOR2g COLOR2b COLOR2hex \

   ## Set the current time, for determining elapsed
   ## time for building the 'photo' image.
   set t0 [clock milliseconds]

   ## Indicate that drawing calculations are starting.
   .fRbuttons.labelCOLORS configure -text "\
 Current Colors:  Fill - $COLOR1hex   Background - $COLORbkGNDhex
  ** CALCULATIONS IN PROGRESS **"

   ## This 'update' makes sure that this label update is displayed.
   update

   ## Change the title of the window to show calculations are in process.
   ## (This shows how we could put a msg in the window title bar,
   ##  instead of in a label in the GUI.)
   # wm title . \
   # "** DRAWING CALCULATIONS ARE IN PROGRESS ** Please wait."


   ## Delete the current image structure.
   ## We especially need to do this when the canvas has been re-sized,
   ## so that we can redraw the image according to the new canvas size. (?)
   catch {image delete imgID}

   ## Get the current canvas size.
   set curCanWidthPx  [winfo width  .fRcanvas.can]
   set curCanHeightPx [winfo height .fRcanvas.can]

   ## Initialize the width & height of the image that we are going to create
   ## --- to the size of the canvas ---
   ## and let us make each dimension of the image an even integer (pixels).
   set imgWidthPx  $curCanWidthPx
   set imgHeightPx $curCanHeightPx
   if {$imgWidthPx  % 2 == 1} { incr imgWidthPx -1 }
   if {$imgHeightPx % 2 == 1} { incr imgHeightPx -1 }

   ## Make the new image structure.
   image create photo imgID -width $imgWidthPx -height $imgHeightPx

   ## Put the new image 'structure' on the canvas.
   ## (Note to myself:  Should this statement be at top or bottom of this proc?
   ##  Does this mainly matter the first time the canvas is used?)
   .fRcanvas.can create image 0 0 -anchor nw -image imgID


   ## Get the half width and height of the image rectangle --- with which
   ## which is the same as the pixel-coordinates of the origin.
   set xmidPx [expr {$imgWidthPx  / 2}]
   set ymidPx [expr {$imgHeightPx / 2}]

   #########################################################################
   ## HERE IS THE 'GUTS':
   ## In a loop over yPx and xPx, where yPx and xPx are measured from the
   ## top left of the canvas/image rectangle, we calculate the hex-color
   ## for each pixel, from x = xPx - xmidPx and y = yPx - ymidPx,
   ## according to our 'color-metric':
   ##
   ##    v = a * (rho - (R+r)/2) ^ 2
   ## where
   ##    a = 1 /  ((R-r)/2) ^ 2
   ## and
   ##   rho = sqrt( x^2 + y^2 )
   ##
   ## We use the symmetry of the donut shape. We loop over the pixels
   ## of the upper left quadrant and set the colors in the other 3
   ## quadrants using the calculated pixel color in the upper-right quad.
   #######################################################################

   ## Calc. the parts of the metric that depend on R and r.
   set midR  [expr { ($R + double($r))/2 }]
   set denom [expr { ($R - double($r))/2 }]
   set denom [expr { $denom * $denom }]

   ## Set the pixel distance to the left side and the
   ## top side of the square containing the 'donut'.
   set leftPx  [expr { $xmidPx - $R }]
   set topPx   [expr { $ymidPx - $R }]

   for {set yPx 0} {$yPx <= $ymidPx} {incr yPx} {

      ## If we are above the square containing the donut
      ## (and in the top left quadrant), draw the entire
      ## horizontal scanline across the image --- at height
      ## $yPx --- and below the square at height $imgHeightPx - $yPx.

      if { $yPx < $topPx } {

         ## Draw the scanline at the 2 heights. (We are depending on
         ## the 'put' command to 'tile' the color across the image.)

         ## across upper half of image
         imgID put $COLORbkGNDhex -to \
               0 $yPx \
               $imgWidthPx [expr {$yPx + 1}]

         ## across lower half of image
         imgID put $COLORbkGNDhex -to \
               0 [expr {$imgHeightPx - $yPx}] \
               $imgWidthPx [expr { ($imgHeightPx - $yPx) - 1}]

         ## FOR TESTING: (show one pair of horiz. scanlines at a time)
         #  update

         ## Skip to the next y.
         continue
      }
      ## END OF if { $yPx < $topPx }


      ## If we are below the top of the square containing the donut
      ## (and in the top left quadrant), draw the 'partial'
      ## horizontal scanline from the left of the image (x=0)
      ## to the left side of the square (x=leftPx). Also, draw the
      ## other 3 partial-scanlines in the other 3 quadrants.

      if { $yPx >= $topPx } {

         ## upper-left quadrant (left-point to right-point)
         imgID put $COLORbkGNDhex -to \
               0 $yPx \
               $leftPx [expr {$yPx + 1}]

         ## lower-left quadrant (left-point to right-point)
         imgID put $COLORbkGNDhex -to \
               0 [expr {$imgHeightPx - $yPx}] \
               $leftPx [expr { ($imgHeightPx - $yPx) - 1}]

         ## upper-right quadrant (right-point to left-point)
         imgID put $COLORbkGNDhex -to \
               $imgWidthPx $yPx \
               [expr {$imgWidthPx - $leftPx}] [expr {$yPx + 1}]

         ## lower-right quadrant (right-point to left-point)
         imgID put $COLORbkGNDhex -to \
               $imgWidthPx [expr {$imgHeightPx - $yPx}] \
               [expr {$imgWidthPx - $leftPx}] [expr { ($imgHeightPx - $yPx) - 1}]

         ## FOR TESTING: (show the 4 horiz. scanline-'segments' that were drawn)
         #  update
      }
      ## END OF  if { $yPx >= $topPx } 

      ## Now we just have to draw the pixels inside the square
      ## containing the donut. We calc. the colors for pixels between
      ## between leftPx and xmidPx --- and apply the same hexcolor
      ## to the corresponding pixel in the other 3 quadrants ---
      ## for each y below topPx (and above ymidPx).
      ##
      ## Here is where we need to calc. the color-metric, v, because
      ## we are in a square where it is NOT all background color.
      ## We poke the calculated color a pixel at a time, instead of
      ## using horizontal scanlines.

      for {set xPx $leftPx} {$xPx <= $xmidPx} {incr xPx} {

         ## Calculate the pixel coords relative to the center of
         ## the canvas/image rectangle --- and calculate 'rho'.
         set x [expr {$xPx - $xmidPx}]
         set y [expr {$yPx - $ymidPx}]
         set rho [expr {sqrt( ($x * $x)  + ($y * $y) )}]

         ## Calculate our color-metric:
         ##     v = (rho - (R+r)/2) ^ 2 / denom

         set num [expr { $rho - $midR }]
         set num [expr {$num * $num}]

         set v [expr { $num / $denom }]

         ## FOR TESTING: (show vars at top-mid donut-ring)
         #  if { $xPx == $xmidPx && $yPx == [expr {$ymidPx - $midR}] } {
         #     puts "ReDraw >   xPx: $xPx    yPx: $yPx   xmidPx: $xmidPx  ymidPx: $ymidPx"
         #     puts "           x: $x  y: $y   rho: $rho  R: $R   r: $r   midR: $midR"
         #     puts "           v: $v  num: $num   denom: $denom"
         #  }

         ## According to the value of v, set the pixel color that we will
         ## put at $xPx, $yPx.
         ##
         ## If v > 1.0, the point x,y is outside the 'donut shape'
         ## --- or inside the donut hole,
         ## so we set the pixel to the background color.
         ##
         ## The shading at the edges falls off too slowly if we use v.
         ## --- or even v squared.  A power > 2 gives good shading.

         if {$v > 1.0} {
            imgID put $COLORbkGNDhex -to $xPx $yPx

            ## Put the same color in the other 3 quadrants ---
            ## top-right, bottom-left, bottom-right.
            imgID put $COLORbkGNDhex -to [expr {$imgWidthPx - $xPx}] $yPx
            imgID put $COLORbkGNDhex -to $xPx [expr {$imgHeightPx - $yPx}]
            imgID put $COLORbkGNDhex -to [expr {$imgWidthPx - $xPx}] [expr {$imgHeightPx - $yPx}]

         } else {
            ## We should be inside or on the 'donut shape'.
            set vpow [expr {pow($v,$N)}]
            set oneMinusVpow [expr {1.0 - $vpow}]
            set Red [expr {int(($vpow * $COLORbkGNDr) + ($oneMinusVpow * $COLOR1r))}]
            set Grn [expr {int(($vpow * $COLORbkGNDg) + ($oneMinusVpow * $COLOR1g))}]
            set Blu [expr {int(($vpow * $COLORbkGNDb) + ($oneMinusVpow * $COLOR1b))}]
            if {$Red > 255} {set Red 255}
            if {$Grn > 255} {set Grn 255}
            if {$Blu > 255} {set Blu 255}
            if {$Red < 0} {set Red 0}
            if {$Grn < 0} {set Grn 0}
            if {$Blu < 0} {set Blu 0}
            set hexcolor [format "#%02X%02X%02X" $Red $Grn $Blu]

            ## Put the color at $xPx $yPx.
            imgID put $hexcolor -to $xPx $yPx

            ## Put the same color in the other 3 quadrants ---
            ## top-right, bottom-left, bottom-right.
            imgID put $hexcolor -to [expr {$imgWidthPx - $xPx}] $yPx
            imgID put $hexcolor -to $xPx [expr {$imgHeightPx - $yPx}]
            imgID put $hexcolor -to [expr {$imgWidthPx - $xPx}] [expr {$imgHeightPx - $yPx}]

         }
         ## END OF  if {$v > 1.0}

         ## FOR TESTING: (show the 4 pixels that have just been drawn)
         #   update
 
      }
      ## END OF   for {set xPx 0} {$xPx < $imgWidthPx} {incr xPx}

      ## FOR TESTING: (show what's been drawn so far for y=yPx ---
      ## 2 complete horiz-scanlines with the bkgd-color OR
      ## 4 horiz-scanline-segments outside the square containin
      ## the donut (with the bkgnd-color) PLUS the 2 horiz
      ## lines of pixels that were drawn inside the square with
      ## varying color.
      ## NOTE: This can cause lots of redraws due to the 
      ##       binding on any <Configure> action on the canvas.
      ##       Comment this out when done testing, or change
      ##       the canvas <Configure> binding.
      #    update
 
   }
   ## END OF  for {set yPx 0} {$yPx < $imgHeightPx} {incr yPx}
   
   ## Make sure the text on the COLORS and PROCESSING label widgets
   ## is up to date.

   .fRbuttons.labelCOLORS configure -text "\
 Current Colors:  Fill - $COLOR1hex   Background - $COLORbkGNDhex
  DRAW TIME: [expr {[clock milliseconds] - $t0}] millisecs elapsed"

   update

   ## Change the title of the window to show execution time.
   ## (This shows how we could put a msg in the window title bar,
   ##  instead of in a label in the GUI.)
   # wm title . \
   # "Redraw DONE.  [expr {[clock milliseconds] - $t0}] millisecs elapsed."

}
## END OF proc 'ReDraw'


##+##########################################################
## proc set_scale_r_lessThan_R_redraw
##
## PURPOSE: Make sure r is strictly less than
##          R before doing the redraw.
##
## CALLED BY: a button1-release binding on .fRscales.scale_r.
##+##########################################################

proc set_scale_r_lessThan_R_redraw {x} {
   global r R
   if {$r >= $R} {set r [expr {$R - 1}]}

   ## We do the redraw via a button1-release binding on
   ## scale_r,rather than using '-command ReDraw' on the scale.
   ## If we ever use '-command', comment this statement.
   ReDraw 0
}
## END OF proc 'set_scale_r_lessThan_R_redraw'


##+##########################################################
## proc set_scale_R_greaterThan_r_redraw
##
## PURPOSE: Make sure R is strictly greater than
##          r before doing the redraw.
##
## CALLED BY: a button1-release binding on .fRscales.scale_R.
##+##########################################################

proc set_scale_R_greaterThan_r_redraw {x} {
   global r R
   if {$R <= $r} {set R [expr {$r - 1}]}

   ## We do the redraw via a button1-release binding on
   ## scale_R, rather than using '-command ReDraw' on the scale.
   ## If we ever use '-command', comment this statement.
   ReDraw 0
}
## END OF proc 'set_scale_R_greaterThan_r_redraw'


##+#####################################################################
## proc 'set_shape_color1'
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set a 'fill' color.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR1  button
##+#####################################################################

proc set_shape_color1 {} {

   global COLOR1r COLOR1g COLOR1b COLOR1hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR1r: $COLOR1r"
   #    puts "COLOR1g: $COLOR1g"
   #    puts "COLOR1b: $COLOR1b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR1r $COLOR1g $COLOR1b]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLOR1hex "#$hexRGB"
   set COLOR1r $r255
   set COLOR1g $g255
   set COLOR1b $b255

   ## Redraw the geometry in the new interior color.

   ReDraw 0

}
## END OF proc 'set_shape_color1'


##+#####################################################################
## proc 'set_shape_color2'    (NOT USED, yet)
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set an 'edge' color.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR2  button
##+#####################################################################

proc set_shape_color2 {} {

   global COLOR2r COLOR2g COLOR2b COLOR2hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR2r: $COLOR2r"
   #    puts "COLOR2g: $COLOR2g"
   #    puts "COLOR2b: $COLOR2b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR2r $COLOR2g $COLOR2b]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLOR2hex "#$hexRGB"
   set COLOR2r $r255
   set COLOR2g $g255
   set COLOR2b $b255

   ## Redraw the geometry in the new edge color.

   ReDraw 0

}
## END OF proc 'set_shape_color2'


##+#####################################################################
## proc 'set_background_color'
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB value to set the color of the canvas ---
##   on which all the tagged items (lines) lie.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLORbkGND  button
##+#####################################################################

proc set_background_color {} {

   global COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLORbkGNDr: $COLORbkGNDr"
   #    puts "COLORbkGNDg: $COLORbkGNDb"
   #    puts "COLORbkGNDb: $COLORbkGNDb"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLORbkGNDr $COLORbkGNDg $COLORbkGNDb]

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

   ## FOR TESTING:
   #    puts "TEMPrgb: $TEMPrgb"

   if { "$TEMPrgb" == "" } { return }
 
   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLORbkGNDhex "#$hexRGB"
   set COLORbkGNDr $r255
   set COLORbkGNDg $g255
   set COLORbkGNDb $b255

   ## Redraw the geometry in the new background color.

   ReDraw 0


}
## END OF proc 'set_background_color'


##+#############################################################
## proc ReDraw_if_canvas_resized
##
## PURPOSE: To make sure we are only doing redraws if the
##          <Configure> of the canvas resulted in a change
##          in the size of the canvas.
##
## CALLED BY: bind .fRcanvas.can <Configure> 
##            at bottom of this script.
##+#############################################################

proc ReDraw_if_canvas_resized {} {
   global  PREVcanWidthPx PREVcanHeightPx

   set CURcanWidthPx  [winfo width  .fRcanvas.can]
   set CURcanHeightPx [winfo height .fRcanvas.can]

   if { $CURcanWidthPx  != $PREVcanWidthPx || \
        $CURcanHeightPx != $PREVcanHeightPx} {

      ReDraw 0
      set PREVcanWidthPx  $CURcanWidthPx
      set PREVcanHeightPx $CURcanHeightPx
   }

}
## END OF ReDraw_if_canvas_resized


##+#####################################################
## Additional GUI initialization, if needed (or wanted).
##+#####################################################

## Set initial scale widget variables.
set r 80
set R 140
set N 3

## Initialize the canvas with 'ReDraw'.
## Need 'update' here to set the size of the canvas,
## because 'ReDraw' uses 'winfo' to get the width and
## height of the canvas.
##     See the 'bind <Configure>' command below.

update
ReDraw 0


## After this script drops into the Tk event-handling loop,
## this bind command causes redraws whenever the canvas is resized.

set PREVcanWidthPx  [winfo width  .fRcanvas.can]
set PREVcanHeightPx [winfo height .fRcanvas.can]
bind .fRcanvas.can <Configure> "ReDraw_if_canvas_resized"


To show what I meant by using these shaded-3D-donut images for logo-backgrounds, here are a couple of sample images --- one done with the GIMP image editor and one done with the Inkscape SVG (scalable vector graphics) editor, both on Linux.

donut_edgeShaded_magentaONblack_logo_effie_gimp_300x290.jpg

donut_edgeShaded_magentaONblack_logo_effie_inkscape_329x262.jpg

I ended up with an elliptical shaped image in Inkscape --- mainly because I/you need a 3-week course in using Inkscape --- because it is so non-intuitive and user-UN-friendly. (I have seen the kind of help Inkscape users give to newbies on the Inkscape help forum. Brutal. All heat and no light. You do not want to go there. Spend many an hour going through the built-in tutorials first.)

Ending up with the elliptical image was actually fortunate. I was tempted to go back and try to make this utility more general, by devising a 'color-metric' to use on an ellipical shaped ring. But this image-result just convinced me that I can be satisfied with getting the elliptical shape via an image editor.

___

By the way, by putting a scale widget on the GUI --- to adjust a 'pow' function exponent --- to change the 'extensity' of the shading at the 2 borders of the ring-shape, it is easy to find that low powers (like 1 or 2) give really washed-out edges to the donut. I think you will find that a higher value (like 4) gives a good-looking edge.

That means that v^4 and (1 - v^4) gives better border edges when blending the 'fill' color at xy and the 'background' color --- better than using the weighted average of the 2 colors using v and (1-v).