GUI for Drawing a Disk with centered lighting and with nice shading

uniquename - 2012oct28

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.

About a month ago, I made a GUI for Drawing 'Gradient Spheres' (lighted disks), with lots of control, based on a 'demo script' of Keith Vetter, donated here many years ago. Both Vetter's script and mine used a canvas-'create oval' technique to make the disk and its lighting.

However, in the past month, I have written several scripts that get better edge-shading at the outer edge of shapes, by using a canvas-'create image' technique.

Those scripts are

That sequence of scripts was set off by a wiki.tcl.tk posting by 'ulis' (deceased circa 2008) in which he used the canvas-'create image' (and 'put') technique to create one of these 'super' shapes --- with shading at the edge. I give credit here to 'ulis'. (R.I.P.)

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 4 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
  • v = a * (rho - (R+r)/2) ^ 2, where the coefficient 'a' is given by 1 / ((R-r)/2) ^ 2 --- for the donut (ring)

(See the pages above for details.)

So I knew I should be able to devise a metric over the disk to achieve nice shading --- even though I wanted to keep the lighting effect on the disk, with nice shading of the lighting --- not just have nice shading at the outer edge of the disk.

More specifically, I wanted a Tk GUI script that 'draws' a color-filled 'disk shape' --- including shading to a 'background color' at the outer edge of the disk shape.

AND, to get an effect like a light-reflection in the middle, the disk includes shading from a 'lighting color' in the center to the 'disk color', as one moves outward from the center.

So there are 3 colors involved:

   lighting-color --> disk-color --> background-color

going from the-center-of-the-disk to its-outer-edge.

Examples of use of this disk-shaped image:

  • At a large size, the 3D-looking disk image could be used as a background for a LOGO.
  • At a medium size, the image could be used as a background for an ICON.
  • At a small size, the image could be used as a 'BULLET' --- for line items on a web page or for menu items in a Tk GUI 'toolchest'.

_____

DERIVATION OF A 'COLOR METRIC' FOR THE '3-COLOR-GRADIENT DISK' :

Let 'R' be the radius of the outer edge of the circular disk.

We will let 'r' be the radius at which the 'lighting-color' at the center of the disk FINISHES transitioning to the 'disk-color'. Furthermore 'r' is the radius at which the 'disk-color' STARTS transitioning to the 'background color'.

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 will, as a preliminary step, let our 'color-metric' be

   v = rho(x,y) / r   when rho is between 0 and r

Note that v on this 'domain' is 0 at rho=0 and 1 at rho=r.

On the 'outer' part of the disk, we want v to be 0 at rho=r and v to be 1 at rho=R.

For rho between r and R

   v = (rho(x,y) - r) / ( R - r)

looks like it will work.

This is a suitable metric if we apply it over the 2 different 'domains' to the 2 different color pairs.

Specifically, when rho(x,y) is between 0 and r, we get the color at the pixel x,y by the weighted average

   (1 - v) * lighting-color + v * disk-color.

Check:

At x,y=0,0 , rho=0, so v = 0 and the equation reduces to the 'lighting-color', which is what we want.

And when x,y is such that rho=r, then v=1 and the equation reduces to the 'disk-color', which is what we want.

And when rho(x,y) is between r and R, we get the color at the pixel x,y by the weighted average

   (1 - v) * disk-color + v * background-color.

Check:

When x,y is such that rho=r, then v=(r-r)/(R-r)=0 and the equation for the color at pixel x,y reduces to the 'disk-color', which is what we want.

When x,y is such that rho=R, then v=(R-r)/(R-r)=1 and the equation for the color at pixel x,y reduces to the 'background-color', which is what we want.

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 one of the two 'v,1-v' equations.

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

If x,y is such that rho=sqrt(x*x + y*y) is between 0 and r, then color1 and color2 will be the 'lighting' color and the 'disk' color.

If x,y is such that rho=sqrt(x*x + y*y) is between r and R, then color1 and color2 will be the 'disk' color and the 'background' color.

Thus we will get the two kinds of shading (lighting-shading and the 3D effect of edge-shading at the outer radius) for the 'disk shape'.

Note that over either domain --- 0 to r, or r to R --- if color1 = color2, then the color given by

   (1 - v) * color1 + v * color2

is simply a constant color --- color1 = color2.

_____

THE GUI DESIGN:

To create the Tk script I used the code of 'donut' script (referenced above) 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 3-color-gradient disk shape will be drawn.

I decided to put 3 'scale' widgets on the GUI --- whose slider-bars can be used to change the values of

  • ratio 'rOverR'
  • exponent M
  • exponent N

where the exponents are used to control 'extensity' of the shading. (The term 'extensity' is discussed at the bottom of this page.)

The 2 scales for the exponents M and N are used to control the intensity and extent of the shading --- which is achieved using the two color weighting factors v^M and (1.0 - v^M) over the 'domain' (0,r)--- and the two color weighting factors v^N and (1.0 - v^N) over the 'domain' (r,R).

Like in the other 'color-metric' GUI's, I used buttons to call on a color selector GUI --- to set the 3 colors involved --- 'lighting', 'disk', and 'background'.

(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 disk, I ended up with the following GUI --- and the code presented below.

disk_lightedANDedgeShaded_yellow-red-black_GUIscreenshot_666x467.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 --- to improve performance.

---

Note that the 3 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.

By using the 'horizontal-scanlines' technique with the 'put' command, I was able to keep the draw times to about half a second for the default window/canvas size.

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 projectile vomiting videos.


 Code of the Tk script 'make_colorDisk_withCenteredColorGradient_andWithShadedBorder.tk':
#!/usr/bin/wish -f
##
## SCRIPT: make_colorDisk_withCenteredColorGradient_andWithShadedBorder.tk
##
## 
## PURPOSE:  This Tk GUI script 'draws' a color-filled 'disk shape'
##           --- including shading to a 'background color' at the
##           outer edge of the disk shape.
##
##           AND, to get an effect like a light-reflection in the middle,
##           the disk includes shading from a 'lighting color' in the
##           center to the 'disk color', as one moves outward from the
##           center.
##
##           So there are 3 colors involved:
##              lighting-color --> disk-color --> background-color
##           going from the-center-of-the-disk to its-outer-edge.
##
##    Examples of use of this disk-shaped image:
##    1) At a large size, the 3D-looking disk 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) At a small size, the image could be used as a 'BULLET' ---
##       for line items on a web page or for menu items in a Tk GUI
##       'toolchest'.
##
#########
## 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
##    disk-shape lies within the canvas (and the image structure) ---
##    with a margin around it.  In the margin, outside the
##    disk-shape on the canvas, a user-selected 'background' color
##    is applied.
##
#########################
## REFERENCES and CREDITS:
##
##  This Tk script is based on my Tk script at https://wiki.tcl-lang.org/37214
##  - 'GUI for Drawing a 3D Donut (a ring) with nice shaded edges'.
##
##  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 donut shape, to get
##  a 'weighted-average' of the two colors, for the color at the pixel x,y.
##
##  I defined a 'color-metric' at any point x,y on the rectangular image
##  on the rectangular canvas by
##
##         v = a * (rho - (R+r)/2) ^ 2
##
##   where 'r' and 'R' are the internal and external radii of the donut
##   and
##   where the coefficient 'a' is given by 1 /  ((R-r)/2) ^ 2
##   and
##   where rho(x,y) = sqrt (x*x + y*y).
##
##  v is 0.0 in the center of the ring shape and 
##  v is 1.0 on the inner and outer borders of the ring shape.
##
##  The canvas 'create image' (and 'put' commands) technique used in the
##  'make-shaded-donut' script is similar to the technique used in three of my
##  scripts that make SHADED EDGES around 'super-ellipses' and around
##  'super-formula-shapes' 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/37156 -
##       'GUI for Drawing 'Super-formula' shapes, with nice shaded border'
##  and
##      https://wiki.tcl-lang.org/37143 -
##      GUI for Drawing Rectangular 'Buttons' with nice shaded edges
##
##  This entire sequence of scripts was set off by a wiki.tcl.tk
##  posting by 'ulis' (deceased circa 2008) in which he used the
##  canvas-'image create'-and-'put' technique to create one of these
##  'super' shapes, with shading at the edge.
##  CREDIT GOES TO 'ulis' (R.I.P.).
##
##  Those 3 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.
##
##  Probably the most easily explained 'color-metric' is the one
##  for the 'super-ellipse'. Here is an explanation for that shape.
##
##+#####################
## 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 v is 1 on the border of the
##     super-ellipse ("by definition") and v declines to 0 towards the
##     center --- the origin (0,0).
##
##     (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 '2-GRADIENT-DISK':
## 
##  Let 'R' be the radius of the outer edge of the circular disk.
##
##  We will let 'r' be the radius at which the 'lighting-color' at
##  the center of the disk FINISHES transitioning to the 'disk-color'.
##  Furthermore 'r' is the radius at which the 'disk-color' STARTS
##  transitioning to the 'background color'.
##
##  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 will, as a preliminary step, let our 'color-metric' be
##
##      v = rho(x,y) / r   when rho is between 0 and r
##
##  Note that v on this 'domain' is 0 at rho=0 and 1 at rho=r.
##
##  On the 'outer' part of the disk,
##  we want v to be 0 at rho=r and v to be 1 at rho=R.
##
##  For rho between r and R
##      v = (rho(x,y) - r) / ( R - r)
##  looks like it will work.
##
##  This is a suitable metric if we apply it over the 2 different
##  'domains' to the 2 different color pairs.
##
##  Specifically, when rho(x,y) is between 0 and r,
##  we get the color at the pixel x,y by the weighted average
##
##     (1 - v) * lighting-color + v * disk-color.
##
##  Check:
##     At x,y=0,0 , rho=0, so v = 0 and the equation
##     reduces to the 'lighting-color', which is what we want.
##
##     And when x,y is such that rho=r, then v=1 and the equation
##     reduces to the 'disk-color', which is what we want.
##
##  And when rho(x,y) is between r and R,
##  we get the color at the pixel x,y by the weighted average
##
##     (1 - v) * disk-color + v * background-color.
##
##  Check:
##     When x,y is such that rho=r, then v=(r-r)/(R-r)=0 and the
##     equation for the color at pixel x,y reduces to the 'disk-color',
##     which is what we want.
##
##     When x,y is such that rho=R, then v=(R-r)/(R-r)=1 and the
##     equation for the color at pixel x,y reduces to the 
##     'background-color', which is what we want.
##
##  Now we have a suitable metric, v.
##
############################
## USING THE 'COLOR-METRIC':
##
##     At a point x,y, we determine the 'shaded color' at the point by
##     using one of the two 'v,1-v' equations.
##
##     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
##
##    If x,y is such that rho=sqrt(x*x + y*y) is between 0 and r, then
##    color1 and color2 will be the 'lighting' color and the 'disk' color.
##
##    If x,y is such that rho=sqrt(x*x + y*y) is between r and R, then
##    color1 and color2 will be the 'disk' color and the 'background' color.
##
##    Thus we will get the two kinds of shading (lighting-shading and
##    the 3D effect of edge-shading at the outer radius) for the
##    'disk shape'.
##
##    Note that over either domain --- 0 to r, or r to R --- if
##    color1 = color2, then the color given by
##           (1 - v) * color1 + v * color2
##    is simply a constant color --- olor1 = color2.
##
########################################
## A SLIGHT CHANGE TO THE 'COLOR-METRIC':
##
##     Actually, it turns out that 1-v and v gives a rather washed-out (too
##     gradual) shading effect at the outer radius. It is better if we
##     raise v to a power N and use v^N and (1 - v^N).
##
##     It may turn out that N = 6, say, gives pretty nice shading for
##     the disk shape at the outer edge. But rather than hard-code the
##     value of N, we provide an 'edge-shading' scale widget on the GUI
##     so that the user can set the value of N.
##
##     Similarly, we provide a 'lighting-shading' scale widget on the
##     GUI so that the user can set a value M, so that the factors
##     v^M and (1 - v^M) are used to calculate the lighting shading.
##
##+##############
## THE GUI DESIGN:
##
##           The GUI made by this Tk script contains a rectangular
##           CANVAS widget on which the color-filled disk shape
##           will be drawn.
##
##           The GUI includes 2 'SCALE' widgets whose slider-bars can
##           be used to change the values of the EXPONENTS M and N,
##           mentioned above.
##
##           The GUI includes a 'SCALE' widget whose slider-bar can
##           be used to set the RATIO of little-r to big-R.
##
##           (We could provide 2 'scale' widgets for the user to set
##            both r and R, in pixels. But, since the user
##            will usually want the disk to be contained within the
##            canvas and with a margin of about 10%, we can query
##            the current width & height of the canvas, and set R suitably.
##            That makes the GUI a little simpler and probably will
##            not infringe much on flexibility/freedom for the user.)
##
##           The GUI also includes 3 BUTTONS which call a COLOR SELECTOR GUI
##           to set (change) the 3 colors:  lighting, disk, background/margin.
##
##           The main proc is a 'redraw' proc.
##           A redraw includes clearing the canvas and redrawing the
##           disk shape.
##
##           A redraw should be done (a) whenever any of the 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.
##
##    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
##        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
##        disk shape and on the background 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 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 tkGUIs/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
##        (and color shading) 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'
##                   '.fRscales1'
##                   '.fRscales2'
##                   '.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
##                          3 buttons (for setting the 3 colors: 'lighting',
##                                     'disk', and 'background'),
##                            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 '.fRscales1':
##                          1 'label' and 1 'scale' widget for setting
##                             rOverR, the ration of little-r to big-R.
##                             This is the main control on the outer
##                             extent of the lighting shading.
##
##     - In '.fRscales2':
##                          1 'label' and 1 'scale' widget each for M and N
##                             (where M is an exponent used to control the
##                              'extensity' of the lighting shading, and N is
##                              an exponent used to control the 'extensity'
##                              of the edge-shading.)
##
##     - In '.fRcanvas':    1 'canvas' widget 
##
##  3) Define bindings:
##
##       - a button1-release on each of the 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
##                           disk shape
##                           --- for the current values of the scale
##                           parameters and the 3 colors.
##
##     - 'set_shape_color1'   - shows a color selector GUI and uses the
##                              user-selected color to set the 'lighting'
##                              color for the disk shape on the canvas
##                               
##     - 'set_shape_color2'   - shows a color selector GUI and uses the
##                              user-selected color to set the 'disk' 
##                              color for the disk shape on the canvas
##
##     - 'set_background_color' - shows a color selector GUI and uses the
##                                user-selected color to reset the color of
##                                the canvas/image-rectangle 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, COLOR2hex,
##                                     COLORbkGNDhex ---
##                                     to start with a disk 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 2012oct27
## Changed by: ...... ......... 2012
##+#######################################################################

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

wm title    . "Edge-shaded, lighted, colored DISK, on a single-color canvas"
wm iconname . "ColorDisk"

wm geometry . +15+30


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

tk_setPalette "#e0e0e0"

## Initialize the 'lighting' color.
## A bright color with RGB's near 255 would be good.

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

## Initialize the 'disk' color
## to gradiate to, from the 'lighting' color.
## Should be darker than the lighting color for
## a nice high-lighting effect.

# set COLOR2r 255
# set COLOR2g 255
# set COLOR2b 0
set COLOR2r 0
set COLOR2g 0
set COLOR2b 200
set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b]


## Initialize the 'background' color.
## Black usually is good --- at least for starters.
# 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.  Usually for 'text' and 'message' widgets too.
##+########################################################

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 PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2


## SCALE geom parameters:

set BDwidthPx_scale 2
# set initScaleLengthPx 100

## This is the 'height' of a scale,
## if it is horizontal orientation.
set scaleWidthPx 10


##+###################################################################
## Set a MINSIZE of the window (roughly).
##
## For width, allow for the minwidth of the '.fRbuttons' frame:
##            about 3 buttons (Exit,Color1,Color2,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  Color1  Color2  Background"]

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

set minWinWidthPx [expr {24 + $minWinWidthPx}]


## MIN HEIGHT ---
## for the 'stack' of 3 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' '.fRscales1'  '.fRscales2' '.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 .fRscales1  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRscales2  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRcanvas  -relief $RELIEF_frame  -borderwidth $BDwidth_frame


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

pack .fRbuttons \
     .fRscales1 \
     .fRscales2 \
   -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 several BUTTONS --- and a LABEL:
##    - an exit-button,
## and
##    - 3 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 "\
Lighting
 Color" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_shape_color1"

button .fRbuttons.buttCOLOR2 \
   -text "\
Disk
 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.
## Also elapsed execution time, or a 'calculation in progress'
## msg is shown in this label.

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.buttCOLOR2 \
     .fRbuttons.buttCOLORbkGND \
     .fRbuttons.labelCOLORS \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

#     .fRbuttons.buttHELP \


##+###############################################
## In the '.fRscales1' FRAME ----
##  DEFINE-and-PACK 1 LABEL and 1 SCALE widget -
## for ratio rOverR, where r is the outer
## extent of the lighting shading and R is the
## radius of the disk.
##################################################

label .fRscales1.label_rOverR \
   -text "\
Ratio 'rOverR' (where 'r' defines the extent of
 the lighting and 'R' is the radius of the disk):" \
   -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 rOverR 0.90

scale .fRscales1.scale_rOverR \
   -from 0.00 -to 1.00 \
   -resolution 0.05 \
   -bigincrement 0.05 \
   -digits 3 \
   -repeatdelay 1000 \
   -length 200 \
   -font fontTEMP_SMALL_varwidth \
   -variable rOverR \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"


## Pack the widgets in frame 'fRscales1'.

pack .fRscales1.label_rOverR \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscales1.scale_rOverR \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+#####################################################
## In the '.fRscales2' FRAME ----
##  DEFINE-and-PACK 2 pairs of LABEL-and-SCALE widgets -
## 1 LABEL and 1 SCALE widget for exponent M, and
## 1 LABEL and 1 SCALE widget for exponent N.
##+#####################################################

##  DEFINE SCALE for exponent M.

label .fRscales2.label_M \
   -text "\
exponent M to control
 'extensity' of lighting 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 M 1

scale .fRscales2.scale_M \
   -from 0.2 -to 5.0 \
   -resolution 0.2 \
   -bigincrement 0.2 \
   -digits 2 \
   -repeatdelay 1000 \
   -length 100 \
   -font fontTEMP_SMALL_varwidth \
   -variable M \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"


##  DEFINE SCALE for exponent N.

label .fRscales2.label_N \
   -text "\
exponent N to control
 'extensity' of outer-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 3

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

#   -command "ReDraw"


## Pack the widgets in frame 'fRscales2'.

pack .fRscales2.label_M \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRscales2.scale_M \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

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

pack .fRscales2.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 .fRscales1.scale_rOverR  <ButtonRelease-1> "ReDraw 0"
bind .fRscales2.scale_M       <ButtonRelease-1> "ReDraw 0"
bind .fRscales2.scale_N       <ButtonRelease-1> "ReDraw 0"


##+######################################################################
## PROCS SECTION:
##
##  - ReDraw            - Called by button1-release bindings on the 
##                        scale widgets, by the set-color procs,
##                        and in the GUI initialization section at the
##                        bottom of this script.
##
##                        Draws the disk shape on the canvas for
##                        the current scale parameter values and for the
##                        current color var values.
##
##  - set_shape_color1   - called by color1 ('lighting') button '-command'
##
##  - set_shape_color2   - called by color2 ('disk')     button '-command'
##
##  - set_background_color  - called by background color button '-command'
##
##  - ReDraw_if_canvas_resized - called by 'bind' to canvas <Configure>
##
##+#######################################################################


##+#####################################################################
## proc ReDraw -
##
## PURPOSE:
##     Draws the disk shape on the canvas.
##
##     We will use symmetry in the 'disk shape' and calculate the pixel
##     color of pixels in the upper left quadrant of the image. As the color
##     of each pixel is determined, that hex-color is 'poked' into 4 pixels,
##     in 4 quadrants, a pixel at a time, with calls like: 
##           imgID put $hexcolor -to $x $y
##     where $x $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 as follows:
##
## Let    rho = sqrt(x*x + y*y)
## where x,y are measured from the middle of the canvas/image rectangle.
##
## On the 'domain' where rho is between 0 and r, our 'color-metric' is
##
##      v = rho(x,y) / r
##
##  On the 'domain' where rho is between r and R, our 'color-metric' is
##
##      v = (rho(x,y) - r) / ( R - r)
##
##  This is a suitable metric if we apply it over the 2 different
##  'domains' to the 2 different color pairs.
##
##  Specifically, when rho(x,y) is between 0 and r,
##  we get the color at the pixel x,y by the weighted average
##
##     (1 - v) * lighting-color + v * disk-color
##  More briefly:
##     (1 - v) * color1 + v * color2
##
##  And when rho(x,y) is between r and R,
##  we get the color at the pixel x,y by the weighted average
##
##     (1 - v) * disk-color + v * background-color
##  More briefly:
##     (1 - v) * color2 + v * bkgndcolor
##+#####################################################################

proc ReDraw {x} {

   global M N rOverR \
         COLOR1r COLOR1g COLOR1b COLOR1hex \
         COLOR2r COLOR2g COLOR2b COLOR2hex \
         COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex

   ## Set the current time, for determining elapsed
   ## time for building the 'photo' image.

   set t0 [clock milliseconds]

   ## Indicate that drawing calculations are starting by
   ## a notice in the COLORS label. Use 'update' to apply the change.
   ## COMMENTED. This cause processing problems.

   # .fRbuttons.labelCOLORS configure -text "\
# Colors:  Lighting - $COLOR1hex  Disk - $COLOR2hex  Background - $COLORbkGNDhex
#
#  ** CALCULATIONS IN PROGRESS **"

   # 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 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 ---
   ## which is the same as the pixel-coordinates of the origin.

   set xmidPx [expr {$imgWidthPx  / 2}]
   set ymidPx [expr {$imgHeightPx / 2}]

   ## Set R to about 90% of the min of the half-width and
   ## half-height of the image rectangle.

   set R $xmidPx
   if { $ymidPx < $R} {set R $ymidPx}
   set R [expr {round(0.90 * $R)}]

   ## Set r from R and the ratio rOverR.

   set r [expr {int($rOverR * $R)}]

   #########################################################################
   ## 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':
   ##
   ## For 0 <= rho <= r, our 'color-metric' is
   ##      v = rho(x,y) / r
   ## and we set pixel-color(x,y) = (1-v)*color1 + v*color2 .
   ##
   ## For r <= rho <= R, our 'color-metric' is
   ##      v = (rho(x,y) - r) / ( R - r)
   ## and we set pixel-color(x,y) = (1-v)*color2 + v*bkgndcolor .
   ##
   ## where
   ##   rho = sqrt( x^2 + y^2 )
   ##
   ## We use the symmetry of the disk 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 ---
   ## here outside the xy-loop below --- so that we do not
   ## calculate it again and again.

   set Rminusr [expr { $R - $r }]
   # set Rminusr [expr { double($R - $r) }]

   ## Set the pixel distance to the left side and the
   ## top side of the square containing the 'disk'.
   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 disk
      ## (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 disk
      ## (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 disk. 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 determine if x,y is located such that
      ## rho(x,y) is between 0 & r or between r and R.
      ## Accordingly we set hexcolor1 and hexcolor2 for the averaging.
      ## Then we calc. the color-metric, v, do the average, and poke
      ## the color at x,y --- and in the 3 other quadrants. We do this
      ## 'pixel-by-pixel' rather than by 'scanline', because
      ## we are in a square where it is NOT all background color and
      ## the color is gradually changing over the pixels in the disk.

      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) )}]

         ## If rho > R, the point x,y is outside the 'disk shape'
         ## so we set the pixel to the background color.
         ## and skip out to the next x with 'continue'.

         if {$rho > $R} {
            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}]

            ## FOR TESTING: (show the 4 pixels that have just been drawn)
            #   update

            continue
         }
         ## END OF if {$rho > $R}

         ## At this point we should be inside the disk.
         ## Calculate our color-metric:
         ##
         ## For 0 <= rho <= r, our 'color-metric' is
         ##      v = rho(x,y) / r
         ## and we set pixel-color(x,y) = (1-v)*color1 + v*color2
         ## and 'continue'.
         ##
         ## For r <= rho <= R, our 'color-metric' is
         ##      v = (rho(x,y) - r) / ( R - r)
         ## and we set pixel-color(x,y) = (1-v)*color2 + v*bkgndcolor
         ## and 'continue'.

         if {$rho <= $r} {
            ## We should be inside the light-shading area.
            set v [expr {$rho / $r}]
            set vpow [expr {pow($v,$M)}]
            set oneMinusVpow [expr {1.0 - $vpow}]
            set Red [expr {int(($vpow * $COLOR2r) + ($oneMinusVpow * $COLOR1r))}]
            set Grn [expr {int(($vpow * $COLOR2g) + ($oneMinusVpow * $COLOR1g))}]
            set Blu [expr {int(($vpow * $COLOR2b) + ($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}]

            ## FOR TESTING: (show the 4 pixels that have just been drawn)
            #   update

            continue
         }
         ## END OF if {$rho <= $r}

         ## if {$rho >= $r && $rho <= $R}
         ## We should be between r and R, so we should not need the 'if' above.
            set v [expr {($rho - $r) / double($Rminusr)}]
            set vpow [expr {pow($v,$N)}]
            set oneMinusVpow [expr {1.0 - $vpow}]
            set Red [expr {int(($vpow * $COLORbkGNDr) + ($oneMinusVpow * $COLOR2r))}]
            set Grn [expr {int(($vpow * $COLORbkGNDg) + ($oneMinusVpow * $COLOR2g))}]
            set Blu [expr {int(($vpow * $COLORbkGNDb) + ($oneMinusVpow * $COLOR2b))}]
            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}]

            ## FOR TESTING: (show the 4 pixels that have just been drawn)
            #   update
         
         ## END OF   if {$rho >= $r && $rho <= $R}
         ## We should not need the brace to end this 'if', if the
         ## 'if' statement is commented above.

 
      }
      ## END OF   for {set xPx 0} {$xPx < $xmidPx} {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 containing
      ## the disk (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. CAREFUL! If this text is too long, it may cause
   ## the <Configure> binding to keep triggering a ReDraw.

   .fRbuttons.labelCOLORS configure -text "\
 Colors:  Lighting - $COLOR1hex   Disk - $COLOR2hex  Background - $COLORbkGNDhex
                    Disk-radius-R: $R  Lighting-radius-r: $r  (pixels)
  DRAW TIME: [expr {[clock milliseconds] - $t0}] millisecs elapsed"

   ## This update to show the change to the COLORS label is not
   ## necessary. The update is shown as the processing stops
   ## and we return to the wait-state of the Tk event-handling loop.
   # 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_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 triplet to set a 'lighting' 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 image with the new color.

   ReDraw 0

}
## END OF proc 'set_shape_color1'


##+#####################################################################
## proc 'set_shape_color2'
##+##################################################################### 
## PURPOSE:
##
##   This procedure is invoked to get an RGB triplet
##   via 3 RGB slider bars on the FE Color Selector GUI.
##
##   Uses that RGB triplet to set an 'disk' 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 image with the new 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 triplet to set the color of the canvas ---
##   actually to set a background color for use on the
##   'image structure' that is put on the canvas widget.
##
## 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 image with the new 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 rOverR 0.90
set M 1.4
set N 4

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


The term 'extensity' on the GUI is meant to convey the fact that the exponents M and N control both the intensity of a color at a boundary and the apparent extent of the color from the boundary.

By putting a scale widget on the GUI for adjusting a 'pow' exponent to change the 'extensity' of the shading --- at the outer edge of the disk --- and at the maximum extent of the lighting, it is easy to find a most-pleasing shading effect.

I found that low powers (like 1 or 2) at the outer edge of the disk give a really washed-out edge to the disk where it meets the background color. I think you will find that a higher value (like 6) gives a good-looking edge.

That means that v^6 and (1 - v^6) gives a better border edge when blending the 'disk' color and the 'background' color to give the pixel color at an x,y near the outer boundary --- better than using the weighted average of the 2 colors using v and (1-v).

___

If you look at the page GUI for Drawing 'Gradient Spheres' (lighted disks), with lots of control, you will see that Vetter's script and mine --- using the canvas-'create oval' technique --- included an extra feature.

Namely, the lighting was actually off-center. In Vetter's script the 'high-light' was on the 'northwest' side of the disk --- in my script, you could set it to any of 8 compass points --- or center.

It turns out the math of getting a good 'color-metric' to achieve that effect is rather messy. You end up with a quadratic equation to solve that has some really messy coefficients to calculate.

It might be actually easier to do the coding using an 'interative numerical analysis' technique to find the 'root' ('zero') of the non-linear equation --- and it may even be computationally faster.

I may return to this script someday to enhance it --- to handle an off-center lighting effect --- and add a compass-points-options-widget to the GUI, like I had in GUI for Drawing 'Gradient Spheres' (lighted disks), with lots of control.

That will give me an opportunity to implement and experiment with some non-linear equation solving procs --- using a bisection, Newton-Raphson, or secant method.