3-Color-Gradient Isosceles Triangle - Barymetric Blend with Shaded Edges

uniquename - 2013sep05

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.

In 2012, I made at least 4 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 --- or vice versa.

The metrics that I used for the first 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 of the 'shading-metrics' for each.)

Those utilities worked with 2 colors --- blending the two colors --- from one color 'in the middle' to a 2nd color 'at the edge'.

I have had it on my things-to-do list (for about a year now) to explore using 'barymetric coordinates' to blend THREE colors --- assigned to the 3 vertices of a triangle --- over the interior of the triangle.

This interest in gradiating 3 colors over a triangle comes partly from my creating some scripts for displaying 3D surfaces --- and for reading and displaying 3D model files --- such as

In 3D modeling, to get nice rendering of colors over a triangular (or quadrilateral) mesh, it is common practice to use 'Gouraud' shading (simpler than Phong shading) to get an improved appearance of the model.

Gouraud shading works off of colors at the vertices of the polygons. This color shading on the surfaces of 3D models has motivated me to explore using barymetric coordinates to get Gouraud-like shading across a triangle.

Adding 'Edge Shading':

At the same time as doing this '3 color blend', I wanted to use the 'shading-metric' technique (like I used in the scripts to make 3D-like rectangles, super-ellipses, super-formula shapes, and donut shapes) to put a 'shaded edge' on the color-gradient triangle.

If the results turned out nicely, I might find some use for the '3-color-with-shaded-edges' triangles in making 'embellished' Tk GUI's.

Barymetric math for other projects:

In any case, I will have developed the code for dealing with calculating barymetric coordinates for points within triangles.

That math (and Tcl-Tk code) will have application to some other projects on my things-to-do list --- image-warping/morphing projects and --- maybe ---- 3D modeling projects.


Some Images To Tell The Story

The code for doing the combination of

  • blending 3 vertex colors to get a color for an interior pixel of a triangle

and

  • shading the colors of the triangle toward a background color at the edges of the triangle

takes quite a lot to describe. For that description, I refer you to comments in the code below. And I present an overview in a couple of sections near the bottom of this page.

I skip the details for now and show some images of the GUI that I created to do the 'barymetric blending' and 'edge shading' outlined above.

Here is an image that shows what the 3-color-isosceles-triangle-making GUI (with barymetric-color-blending and edge-shading) looks like when it first comes up.

3colorGradientTriangleBary_Nequals2tenths_screenshot_465x412.jpg

Note that there are 3 color buttons across the top of the GUI for assigning a color to each of the vertices of the triangle. And there is a color button for assigning a background color --- which is also the color that this utility uses to create a 'shaded edge' on the triangle.

Also note that there is a 'scale' widget on the GUI that allows the user to set an exponent, N, that controls what I call 'extensity' --- the extent and intensity of the shading that occurs as one approaches the edges of the triangle.

The following image shows that when N is less than 1 (and near zero), there is very little apparent shading at the edges of the triangle. In fact, you can see the 'jaggies' of the edges of the triangle meeting the black background.

3colorGradientTriangleBary_Nequals1hundredth_screenshot_464x412.jpg

On the other hand, as you set N to numbers larger than 1, there is 'strong' shading at the edges of the triangle. That is, the background color is strongly weighted in blending the background color with any pixel color of the triangle --- and the pixel colors in the interior of the triangle are determined by a 'barymetric blend' of the 3 vertex colors of the triangle.

3colorGradientTriangleBary_Nequals1point2_screenshot_465x414.jpg

To 'turn things updside down', I changed the vertex colors from CMY colors to RGB colors --- and I changed the background color from black to white. Here is the result --- with N (the control of the 'edge shading') set to 0.6.

3colorGradientTriangleBary_whiteBkgd_Nequals6tenths_screenshot_701x272.jpg

The size and aspect-ratio of the triangle can be changed by resizing the window and clicking on the 'ReDraw' button. The new size of the canvas widget is used to determine the new locations of the vertices of the isosceles triangle.

---

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


The Code :

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,
     text-array-for-labels-etc).

  1a) Define ALL frames (and sub-frames, if any).
  1b) Pack   ALL frames and sub-frames.

  2) Define & pack all widgets in the frames, frame by frame.
              Within each frame, define ALL the widgets.
              Then pack the widgets.

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

  4) Define PROCS, if needed.

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

This Tk coding 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 other scripts (code re-use).

I call your attention to step-zero. One new thing that I have started doing recently is using a text-array for text in labels, buttons, and other widgets in the GUI. This can make it easier for people to internationalize my scripts. I will be using a text-array like this in most of my scripts in the future.


Experimenting with the GUI

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.

That helps me when I am initially testing the behavior of a GUI (the various widgets within it) as I resize the main window.

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.

Note: I have found that one can get some quite surprising self-changing movement of the slider-button on scale widgets if you allow a scale widget to expand and allow the window to expand. So you may want the scale widgets to stay fixed in length ('-fill none') and/or use '-expand 0' when packing scale widgets --- rather than using '-fill x -expand 1' with scale widgets and an expandable GUI window.


Additional experimentation: 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.


Some features in the code

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. The code is quite complex --- to handle the math for

  • the 'barymetric blending' of the 3 vertex colors

and

  • using the 'shading metric' to get a 3D-like, 'soft' edge on the triangle.

So, for now, I have NOT attempted to significantly reduce the number of 'put' commands used to draw an image. I am currently using a 'put' command for each pixel in the rectangular, in-memory 'photo' image structure.

Eventually, I plan to return to this code and, by using 'horizontal-scanlines' (of hex-colors), reduce the 'put' commands to either one 'put' per scanline --- or one 'put' for the entire image.

When using a 'put' of a hexcolor at each individual pixel, it takes about 1 to 10 seconds to draw the image --- depending on the size of the window (and thus the size of the canvas and the size of the 'photo' structure).

By using the 'horizontal-scanlines' technique with the 'put' command, I will probably be able to reduce the draw times to about half a second or less --- even for large images.

You can see the comments in the 'ReDraw' routine for details on the math and image building going on there.


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 the Falkland Islands' Funniest Home Videos.


 Code for Tk script 'draw_3colorGradient_isoscelesTriangle_create-imageWithShadedEdges.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: draw_3colorGradient_isoscelesTriangle_create-imageWithShadedEdges.tk
##
## PURPOSE:  This Tk GUI script 'draws' a color-filled 'isosceles triangle'
##           --- with color-shading around the three edges of the triangle, to a
##           background color. The pixel colors in the triangle are based on
##           3 user-selected colors assigned to the 3 vertices of the triangle ---
##           and the color at any pixel is an average of the 3 colors using
##           the barymetric coordinates of the point to compute the average.
##
##    For example, if (L1,L2,L3) are the barymetric coordinates of a pixel,
##    where L1 + L2 + L3 = 1, we use the RGB colors of the 3 vertices
##    to calculate the RGB color at the pixel, as follows:
##
##    Let the RGB values of vertex 1, 2, and 3 be
##       (R1,G1,B1) and (R2,G2,B2) and (R3,G3,B3).
##    Then
##       Rpixel = (L1 * R1) + (L2 * R2) + (L3 * R3)
##       Gpixel = (L1 * G1) + (L2 * G2) + (L3 * G3)
##       Bpixel = (L1 * B1) + (L2 * B2) + (L3 * B3)
##
##+#############################################
## EXAMPLES OF USE of this triangle-shaped image:
##
##    This 3D-like triangle could be used as
##
##       1) an icon or logo background. Nice quality text could be added
##          on top of the triangle.
## 
##       2) an unusual, triangle-shaped '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.
##
##+############################
## METHOD of creating the image, on the Tk canvas:
##
##    This script makes this color-shaded shape via  a Tk 'photo' image 'structure'
##    placed on a Tk canvas widget.
##
##    The in-memory image 'structure' is created by an 'image create photo' command.
##
##    Then the image is put on the canvas via a Tk canvas 'create image' command.
##
##    The image is (re)generated --- whenever there is a color change to a vertex,
##    or whenever the size of the triangle is changed.
##
##    The image regeneration is done via
##              put <hexcolor> -to $x $y
##    commands on the image 'structure',
##    where <hexcolor> could, eventually, be a list of lists of row-colors.
##
##    The rectangular 'photo' image-structure (which contains the triangle within
##    that rectangle) covers the entire canvas widget. The triangle-shape lies
##    within the canvas (and within the 'photo' image structure) ---
##    with a margin around a minimal, triangle-containing rectangle.
##
##    In the margin, outside the triangle-shape on the canvas, a user-selected
##    'background' color is applied. 
##
##+##########
## REFERENCES:
##
##  This Tk script is based on techniques used in my Tk scripts for drawing
##  other shapes --- which are 3D-like, color-gradient, edge-shadowed ---
##  on a color background --- scripts such as:
##
##  -   "GUI for Drawing 'Super-ellipses', with nice shaded edges"
##      at https://wiki.tcl-lang.org/37004
##  
##  -   "GUI for Drawing Rectangular 'Buttons' with nice shaded edges"
##      at https://wiki.tcl-lang.org/37143
##
##  -   "GUI for Drawing 'Super-formula' shapes, with nice shaded border"
##      at https://wiki.tcl-lang.org/37156
##
##  -   "GUI for Drawing 'Donut' shapes, with nice shaded border"
##      at https://wiki.tcl-lang.org/37214
##
##  Thanks go to 'ulis' (deceased in 2008, R.I.P.) whose super-ellipse script
##  on the Tcl-Tk wiki page 'Mathematics jewels' indicated to me that we could
##  get nicely shaded edges on geometric shapes --- using 'image create photo'
##  and a Tk canvas.
##
##+################################
## METHOD of doing the EDGE SHADING:
##
##  In my scripts above, I devised a 'color-shading-metric' v at each pixel point
##  x,y that is within the rectangle/super-ellipse/super-formula/donut shape ---
##  where v is between 0 and 1. And the value of v goes to 0 at the edges
##  of these shapes.
##
##  Then v and (1-v) are applied to a user-selected background color
##  and a user-selected 'fill' color for any pixel within the shape --- to get
##  a 'weighted-average' of the user-selected colors, for the color at a
##  given pixel x,y.
##
##  For this triangle, we use the barycentric coordinates of any pixel
##  within the triangle --- to formulate our metric.
##
##  For a given pixel x,y, let us say its barymetric coordinates
##  are (L1,L2,L3) where L1 + L2 + L3 = 1.
##
##  Note that at the edges (and vertices) of the triangle, at least one
##  of the 3 barymetric coordinates is 0. And note that we want our
##  metric, v(x,y) = v(L1,L2,L3), to be 0 at the edges of the triangle.
##  Also, we want v to be a maximum of 1.0 somewhere in the interior of
##  the triangle.
##
##  Note that the 'barymetric center' of the triangle is at the point/pixel
##  where (L1,L2,L3) = (1/3,1/3,1/3). And when one looks at any point
##  within the triangle which is not on that center, at least one of the
##  coordinates is less than 1/3.
##  
##  Based on these observations, for our 'color-shading-metric', we will use
##
##     v(x,y) = v(L1,L2,L3) = 3.0 * min(L1,L2,L3)
##
##  Note that v is 0 on the edges (and vertices) of the triangle, it is
##  1.0 at the barymetric center, and it is between 0 and 1 at other points
##  in the triangle.
##
##  So 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
##
##       1) Rpixel,Gpixel,Bpixel --- 'color-bary', say --- which is 
##          the barymetrically-determined color of the point/pixel x,y
##          calculated as indicated above ---  namely:
##          Let the RGB values of vertex 1, 2, and 3 be
##                  (R1,G1,B1) and (R2,G2,B2) and (R3,G3,B3).
##          Then
##                  Rpixel = (L1 * R1) + (L2 * R2) + (L3 * R3)
##                  Gpixel = (L1 * G1) + (L2 * G2) + (L3 * G3)
##                  Bpixel = (L1 * B1) + (L2 * B2) + (L3 * B3)
##     and
##       2) a user-selected background color, 'color-bkgd', say.
##
##     We calculate the 'shaded color' at x,y by calulating a weighted average
##     based on applying the factor v to 'color-bary' --- and applying
##     (1 - v) to 'color-bkgd'. That is,
##              shaded-color =  v * color-bary + (1 - v) * color-bkgd.
##
##     We actually calculate via formulas like
##        Rshaded =  v * Rpixel + (1 - v) * Rbkgd
##        Gshaded =  v * Gpixel + (1 - v) * Gbkgd
##        Bshaded =  v * Bpixel + (1 - v) * Bbkgd
##
##    Thus we will get the edge-shading (the 3D effect) for the
##    'triangle shape'.
##
##     Actually, it turns out that 1-v and v give 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), where N could be fractional rather than
##     integer.  For example, we could use N = 0.5 (a square root) just as
##     easily as we could use N = 2 (the square of v).
##
##     In fact, say we want to 'spread out' the influence of the pixel colors
##     (to dominate over the background color) in the center of the image.
##     Then, given the use of v and (1 - v) in the 3 equations above, we would
##     want N to be a fraction less than 1, like 0.5.
##
##+##############
## THE GUI DESIGN:
##
##     The GUI made by this Tk script contains the following widgets.
##
##     1) a rectangular CANVAS widget on which the color-filled triangle
##        shape will be drawn.
##
##     2) four color BUTTONS, to assign colors to the 3 triangle vertices
##        and to the background.
##
##           (Note that we could put another color selector button on the
##            GUI to select the border-shading color to be a different color
##            from the background color of the canvas area.)
##
##         There are also 'Exit', 'Help', and 'ReDraw' BUTTONS on the GUI.
##
##     3) a SCALE widget for the exponent N to control the extent
##        of the shading --- by using the two color-shading factors
##        v^N and  (1.0 - v^N).
##
##+##################################
## (RE)DRAW and RESIZE considerations:
##
##     A redraw includes clearing the canvas and redrawing the triangle shape
##     on the solid color background.
##
##     A redraw should be done whenever
##        (a) a color button is used to change a vertex or background color,
##        (b) the slider-button on the 'shading-exponent' scale is reset,
##        (c) the window (and thus the canvas) is resized ---
##           so that the triangle-shape is redrawn using new locations
##           of one  or more of the 3 vertices on the canvas.
##
##  Note that the user can resize the triangle on the canvas by resizing the
##  window. We could have put additional scale widgets on the GUI to allow
##  for precise setting of the dimensions of the canvas. And we could have
##  implemented a method by which the user could drag vertices on the canvas
##  --- even allowing the triangle to be non-isosceles.
##
##  But resizing the (isoceles) triangle by resizing the window will probably
##  serve for most uses of this color-shaded-triangle drawing utility.
##
##+##########################
## PERFORMANCE CONSIDERATIONS and the 'scale' widget:
##
##  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' widget.
##
##  For now, I have defined a button1-release binding on the
##  'shading-exponent' scale widget to trigger a redraw --- only
##  when the user finishes moving the slider-button of the scale.
##
##  However, it should be pointed out that if erasing the canvas
##  and (re)calculating-colors and putting the colors on the rectangular
##  'photo' image completes within a small fraction of a second, it
##  would be feasible to do the redraws 'dynamically' with each movement
##  of the slider-button, 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 PNG
##    or GIF 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/logo-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' or icons or logos in HTML pages --- or for icons on
##    desktops --- or for bullets or icons in (Tk) GUIs or 'toolchests'.
##
##+########################################################################
## '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, text-array-for-labels-etc).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack   ALL frames and sub-frames.
##
##  2) Define all widgets in the frames, frame by frame.
##     After all the widgets for a frame are defined, pack them in the frame.
##
##  3) Define keyboard or mouse/touchpad/touch-sensitive-screen 'event'
##     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'  '.fRcontrols'  '.fRcanvas'
##      No sub-frames.
##
##  1b) Pack ALL frames.
##
##  2) Define all widgets in the frames (and pack them):
##
##     - In '.fRbuttons':
##         'Exit','Help','ReDraw',
##         'Vertex1color','Vertex2color','Vertex3color', 'BkgdColor'
##
##     - In '.fRcontrols':
##         1 'label' and 1 'scale' widget --- for N
##         (where N is an exponent used to control the 'extensity'
##          of the 'to-background-shading'.)
##
##     - In '.fRcanvas':   1 'canvas' widget 
##
##  3) Define bindings:
##
##     - a button1-release on the 'shading-exponent' scale widget,
##       to do a redraw
##
##    NOTE3: 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
##                         triangle shape
##                         --- for the current values of the 3 vertex
##                         colors, the background color, and the
##                         'shading-exponent' scale parameter value
##                         --- and for the current size of the canvas.
##
##     'set_vertex_color1'   - shows a color selector GUI and uses the
##                             user-selected color to set the color
##                             of vertex1 --- and call 'ReDraw'.
##
##     'set_vertex_color2'   - shows a color selector GUI and uses the
##                             user-selected color to set the color
##                             of vertex2 --- and call 'ReDraw'.
##
##     'set_vertex_color3'   - shows a color selector GUI and uses the
##                             user-selected color to set the color
##                             of vertex3 --- and call 'ReDraw'.
##
##   'set_color_background'  - shows a color selector GUI and uses the
##                             user-selected color to set the background
##                             color --- and call 'ReDraw'.
##
##   '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.
##
##                             (If the <Configure> event causes extra
##                              redraws, which take extra processing
##                              and cause flickering or delays, then
##                              the binding to this proc may be commented
##                              --- and the 'ReDraw' button may be used,
##                              so that the user can force a redraw,
##                              after the user finishes resizing the window.)
##
##  5) Additional GUI initialization:  Execute proc 'ReDraw' once with
##                                     an initial, example set of parms
##                                     --- 3 vertex colors, 1 background
##                                     color, an intial value for the
##                                     'shading-exponent' N ---
##                                     to start with a triangle 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 redraws
##         as the window is resized.
##                                   (Actually, we would like to cause the
##         redraw when the user is finished resizing the window --- i.e.
##         on a button1-release from the window-manager-border. But that
##         event does not seem to be available to the 'wish' interpreter.)
##+########################################################################
## 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 2013aug26 Started coding the GUI.
## Changed by: Blaise Montandon 2013aug30 Implemented ReDraw proc --- i.e.
##                                        the barymetric calculations.
## Changed by: Blaise Montandon 2013sep04 Reviewed comments and code.
##                                        Did some experimenting with the
##                                        N exponent and scale limits.
##+#######################################################################

##+#######################################################################
## Set WINDOW TITLE and intial POSITION.
##+#######################################################################

wm title    . "3D-like, edge-shaded 'isosceles triangle' shape, on a single-color canvas"
wm iconname . "ShadedTriangle"

wm geometry . +15+30


##+######################################################
## Set the COLOR SCHEME for the window and its widgets ---
## and set the initial color for the triangle vertices
## and the canvas background (outside the triangle-shape).
##
## Alternatively, we may move the vertex and background
## color-setting to the 'Additional GUI Initialization'
## section at the bottom of this script.
##+######################################################

tk_setPalette "#e0e0e0"

## Initialize the triangle vertex1 color.

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

## Initialize the triangle vertex2 color.

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

## Initialize the triangle vertex3 color.

# set COLOR3r 0
# set COLOR3g 0
# set COLOR3b 255
set COLOR3r 0
set COLOR3g 255
set COLOR3b 255
set COLOR3hex [format "#%02X%02X%02X" $COLOR3r $COLOR3g $COLOR3b]

## Initialize the background color for the canvas.

# set COLORBKGDr 60
# set COLORBKGDg 60
# set COLORBKGDb 60
set COLORBKGDr 0
set COLORBKGDg 0
set COLORBKGDb 0
set COLORBKGDhex \
    [format "#%02X%02X%02X" $COLORBKGDr $COLORBKGDg $COLORBKGDb]

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


##+########################################################
## DEFINE (temporary) FONT NAMES.
##
## We use a VARIABLE-WIDTH FONT for LABEL and BUTTON widgets.
##
## We use a FIXED-WIDTH FONT for LISTBOXES (and ENTRY fields
## and TEXT widgets, 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. padding and borderwidths --- and 
##  width and height of canvas)
##+###########################################################

## CANVAS geom parameters:

set initCanWidthPx 400
set initCanHeightPx 300

# set BDwidthPx_canvas 2
  set BDwidthPx_canvas 0


## LABEL geom parameters:

set PADXpx_label 0
set PADYpx_label 0
set BDwidthPx_label 2


## BUTTON geom parameters:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## SCALE geom parameters:

set BDwidthPx_scale 2
set scaleThicknessPx 10
# set initScaleLengthPx 100



##+###################################################################
## Set  MIN-SIZE of the window (roughly).
##
## For WIDTH, allow for the minwidth of the '.fRbuttons' frame:
##            about 7 buttons (Exit,Help,ReDraw,Color1,Color2,Color3,ColorBkgd).
##            We want to at least be able to see the Exit button.
##
## For HEIGHT, allow
##             2 chars  high for the '.fRbuttons' frame,
##             2 char   high for the '.fRcontrols' frame,
##            24 pixels high for the '.fRcanvas' frame.
##+###################################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
   "Exit  Help  ReDraw  Vertex1  Vertex2  Vertex3  Background"]

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

set minWinWidthPx [expr {36 + $minWinWidthPx}]


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

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {4 * $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 {40 + $minWinHeightPx}]


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


##+####################################################################
## Set a TEXT-ARRAY to hold text for buttons & labels on the GUI.
##     NOTE: This can aid INTERNATIONALIZATION. This array can
##           be set according to a nation/region parameter.
##+####################################################################

## if { "$VARlocale" == "en"}

set aRtext(buttonEXIT) "Exit"
set aRtext(buttonHELP) "Help"
set aRtext(buttonDRAW) "ReDraw"
set aRtext(buttonCOLOR1) "Vertx1colr"
set aRtext(buttonCOLOR2) "Vertx2colr"
set aRtext(buttonCOLOR3) "Vertx3colr"
set aRtext(buttonCOLORBKGD) "BkgdColr"

set aRtext(labelEXPONENT) "N, exponent for 'extensity'
of edge shading:"

## END OF  if { "$VARlocale" == "en"}


##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : '.fRbuttons' '.fRcontrols'  '.fRcanvas'
##   Sub-frames: none
##+################################################################

## FOR TESTING: (to check size of frames as window is resized)
# 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 .fRcontrols -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRcanvas   -relief raised         -borderwidth 2


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

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

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


##+#########################################################
## Frames are defined and packed.
## Now we are ready to define the widgets in the frames.
##+#########################################################


##+#####################################################################
## In the '.fRbuttons' FRAME  ---  DEFINE-and-PACK
## BUTTONS (for Exit,Help,Vertex1color,Vertex2color,Vertex3color,
##          and BackgroundColor)
##+#####################################################################

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

button .fRbuttons.buttHELP \
   -text "$aRtext(buttonHELP)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {popup_msgVarWithScroll .topHelp "$HELPtext"}

button .fRbuttons.buttDRAW \
   -text "$aRtext(buttonDRAW)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command {ReDraw 0}

button .fRbuttons.buttCOLOR1 \
   -text "$aRtext(buttonCOLOR1)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_vertex_color1"

button .fRbuttons.buttCOLOR2 \
   -text "$aRtext(buttonCOLOR2)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_vertex_color2"

button .fRbuttons.buttCOLOR3 \
   -text "$aRtext(buttonCOLOR3)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_vertex_color3"

button .fRbuttons.buttCOLORBKGD \
   -text "$aRtext(buttonCOLORBKGD)" \
   -font fontTEMP_SMALL_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_background_color"

## COMMENTED, for now. (We color & label the buttons, instead.)

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

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

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttDRAW \
     .fRbuttons.buttCOLOR1 \
     .fRbuttons.buttCOLOR2 \
     .fRbuttons.buttCOLOR3 \
     .fRbuttons.buttCOLORBKGD \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

#     .fRbuttons.labelCOLORS \



##+##################################################################
## In the '.fRcontrols' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget for N, the 'shading-exponent'.
##+###################################################################

label .fRcontrols.label_N \
   -text "$aRtext(labelEXPONENT)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief raised \
   -bd $BDwidthPx_label

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

scale .fRcontrols.scale_N \
   -from 0.01 -to 2.0 \
   -resolution 0.01 \
   -length 200 \
   -font fontTEMP_SMALL_varwidth \
   -variable N \
   -showvalue true \
   -orient horizontal \
   -bd $BDwidthPx_scale \
   -width $scaleThicknessPx

## We do not supply the redraw command to this scale widget,
## because the redraw is probably not going to proceed within a
## fraction of a second. Instead, we supply a button1-release
## binding on the scale widget, to do the redraw.
#   -command "ReDraw"

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

pack .fRcontrols.scale_N \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+######################################################
## 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:
##+###############################

##+#########################################################
## If this button1-release on the scale widget is annoying
## to the user (causing redraws when the user does not want
## redraws to occur), we could comment this and have the
## user use the 'ReDraw' button instead.
##+#########################################################

bind .fRcontrols.scale_N  <ButtonRelease-1> "ReDraw 0"

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


##+######################################################################
## PROCS SECTION:
##
##  'ReDraw'            - Called by
##                        - the 'Additional GUI Initialization' section
##                              at the bottom of this script,
##                        - the set-color procs,
##                        - a button1-release binding on the scale widget.
##
##                        Draws the triangle shape on the canvas for
##                        the current color var values --- and for the
##                        current 'shading-exponent' scale parameter value.
##
##  'set_vertex_color1' - called by vertex1-color button '-command'
##
##  'set_vertex_color2' - called by vertex2-color button '-command'
##
##  'set_vertex_color3' - called by vertex3-color button '-command'
##
##  'set_background_color'  - called by background color button '-command'
##
##   'update_color_labels'  - called by the color procs to update
##                            colors and text on the color buttons
##
##  'ReDraw_if_canvas_resized' - called by 'bind' to canvas <Configure>
##
##  'popup_msgVarWithScroll' - called by 'Help' button
##
##+#######################################################################


##+#######################################################################
## proc ReDraw -
##
## PURPOSE:
##     Draws the triangle shape on the canvas.
##
##     We will use symmetry in the 'triangle 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 $x $y is measured (in pixels) relative to the middle of
##     the rectangular canvas/image area.
##
## CALLED BY:  by 4 set-color procs and by bindings (in the BINDINGS section)
##             --- 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 widget to
##       do the redraws 'dynamically' as a slider-button is moved.
##
##+#######################################################################
## METHOD OF SETTING THE VERTICES OF THE TRIANGLE:
##
## We set the vertices of the isoceles triangle by setting the coordinates
## according to the current canvas width and height ---
## canWidthPx and canHeightPx.
##
## Say vertex 1 (x1px,y1px) is at the peak of the triangle, and vertex 2 and 3
## (x2px,y2px) and (x3px,y3px) are at the base of the triangle.
## Then we let:
##     x1px = 0.5 * canWidthPx
##     y1px = 0.1 * canHeightPx
##     x2px = 0.1 * canWidthPx
##     y2px = 0.9 * canHeightPx
##     x3px = 0.9 * canWidthPx
##     y3px = 0.9 * canHeightPx
##+#######################################################################
## METHOD OF SETTING THE COLOR OF PIXELS:
##
## The color at any pixel in the triangle is an average of the 3 colors using
## the barycentric coordinates of the point to compute the average.
##
## For example, if (L1,L2,L3) is the barymetric coordinate of a pixel,
## where L1 + L2 + L3 = 1, we use the RGB colors of the 3 vertices
## to calculate the RGB color at the pixel, as follows:
##
## Let the RGB values of vertex 1, 2, and 3 be
##       (R1,G1,B1) and (R2,G2,B2) and (R3,G3,B3).
## Then
##       Rbary = (L1 * R1) + (L2 * R2) + (L3 * R3)
##       Gbary = (L1 * G1) + (L2 * G2) + (L3 * G3)
##       Bbary = (L1 * B1) + (L2 * B2) + (L3 * B3)
##
##+#######################################################################
## METHOD OF SHADING THE COLORS OF THE PIXELS:
##
## For shading the edges of the triangle, 
## we use a 'color-metric' v(x,y) is given by
##
##    v = 3.0 * min(L1,L2,L3)
##
## where the Li are the barycentric coordinates of a point x,y in the
## triangle.
##
## Then for a pixel whose color was calculated to be (Rbary,Gbary,Bbary),
## we calculate the 'shaded color' of the pixel via
##        Rshaded =  v * Rbary + (1 - v) * Rbkgd
##        Gshaded =  v * Gbary + (1 - v) * Gbkgd
##        Bshaded =  v * Bbary + (1 - v) * Bbkgd
##
## In the case when the background color is black, then
##  (Rbkgd,Gbkgd,Bbkgd) = (0,0,0), so the equations above become
##        Rshaded =  v * Rbary
##        Gshaded =  v * Gbary
##        Bshaded =  v * Bbary
##+#####################################################################
## METHOD OF CALCULATING THE BARYCENTRIC COORDINATES OF A POINT/PIXEL:
##
## FROM a PDF file titled:
##      The Simplex and Barycentric Coordinates
##            by   James Emery
##           Latest Edit: 8/30/2012
##
## We get the following formulas for computing the barymentric coordinates
## --- L1,L2,L3 --- of a point P relative to a triangle with vertices
## P1,P2,P3.
## 
## If all 3 barycentric coordinates are between 0 and 1, the point is
## inside the triangle.
##
## The general computation to determine an interior point, requires 11
## additions or subtractions, 6 multiplications, 2 divisions, and 3
## comparisons. The computation may be done as follows.
##
## We let P1=(x1,y1), P2=(x2,y2), P3=(x3,y3), P=(x,y). Let
##
##   a11 = x1 - x3
##   a21 = y1 - y3 
##   a12 = x2 - x3 
##   a22 = y2 - y3
##       and
##    b1 =  x - x3 
##    b2 =  y - y3 .
##
## Let D be the determinant
##     D = (a11 * a22) - (a21 * a12)
##
## Then we have
##      L1 = ((b1 * a22) - (b2 * a12)) / D
##
##      L2 = ((a11 * b2) - (a21 * b1)) / D
##
##      L3 = 1 - (L1 + L2)
##
## ----
## Furthermore Emery provides this C program code for the above calculations.
##
## //c+ bary2 barycentric coordinates of a point in the plane
## int bary2(double* p,double* p1,double* p2,double* p3,double* lambda){
## double d,a11,a12,a21,a22,b1,b2;
## int i;
## a11=p1[0]-p3[0];
## a21=p1[1]-p3[1];
## a12=p2[0]-p3[0];
## a22=p2[1]-p3[1];
## b1=p[0]-p3[0];
## b2=p[1]-p3[1];
## d=a11*a22-a21*a12;
## if(d == 0.){
##    return(0);
## }
## lambda[0]=(b1*a22 - b2*a12)/d;
## lambda[1]=(a11*b2-a21*b1)/d;
## lambda[2]=1.-lambda[0] - lambda[1];
## for(i=0;i<3;i++){
##    if((lambda[i] <= - EPSILON) || (lambda[i] >= 1.+ EPSILON)){
##       return(0);
##    }
##  }
## return(1);
## }
## ## END OF procedure 'bary2'
##+#########################################################################

## We may use EPSILON to check if the determinant is near zero.
set EPSILON 0.0005

## If we draw ovals at the vertex points, we may use this radius.
# set radiusPx 4

proc ReDraw {x} {

   global N EPSILON \
      COLOR1r COLOR1g COLOR1b COLOR1hex \
      COLOR2r COLOR2g COLOR2b COLOR2hex \
      COLOR3r COLOR3g COLOR3b COLOR3hex \
      COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex

   ###################################################
   ## Indicate that drawing calculations are starting.
   ###################################################

   wm title . "*BUSY* ... calculating pixel colors."

   # .fRbuttons.labelINFO configure -text "\
   #  **BUSY** ... CALCULATIONS IN PROGRESS."
   ## This 'update' makes sure that this label update is displayed.
   # update

   ## FOR TESTING: (to dummy out this redraw proc while
   ##               it is 'under consruction')
   #   return

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

   set t0 [clock milliseconds]

   ################################################
   ## Change the cursor to a 'watch' cursor.
   ################################################
   # . config -cursor watch
   ## Make the cursor visible.
   # update

   #########################################################################
   ## Delete the current image structure.
   ## We may 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 odd integer (pixels),
   ## so there are an equal number of pixels on either side of the center pixel.
   #############################################################################

   set imgWidthPx  $curCanWidthPx
   set imgHeightPx $curCanHeightPx
   if {$imgWidthPx  % 2 == 0} { incr imgWidthPx -1 }
   if {$imgHeightPx % 2 == 0} { incr imgHeightPx -1 }


   #########################################################
   ## Clear the canvas of previous pixels and vertex markers.
   #########################################################
   #  .fRcanvas.can delete all

   #####################################
   ## 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 have to be done just once, even though canvas may be resized?)
   ############################################################################

   .fRcanvas.can create image 0 0 -anchor nw -image imgID

   ######################################################################
   ## Get the half width and height of the image rectangle --- with which
   ## to calculate the pixel-coordinates of the origin.
   ######################################################################

   set xmidPx [expr {1 + int($imgWidthPx  / 2)}]
   set ymidPx [expr {1 + int($imgHeightPx / 2)}]

   ####################################################################
   ## We set the coordinates that will be used to contain
   ## the rectangle that contains the triangle. That rectangle defines
   ## a margin around the triangle that is the background color.
   ## These values also help us set the coordinates of the 3 vertices.
   ####################################################################

   set ytopPx [expr {int(0.1 * $imgHeightPx)}]
   set ybotPx [expr {int(0.9 * $imgHeightPx)}]
   set xleftPx  [expr {int(0.1 * $imgWidthPx)}]
   set xrightPx [expr {int(0.9 * $imgWidthPx)}]


   #####################################################################
   ## We define the coordinates of the 3 vertices.
   #####################################################################

   set vert1Xpx $xmidPx
   set vert1Ypx $ytopPx
   set vert2Xpx $xleftPx
   set vert2Ypx $ybotPx
   set vert3Xpx $xrightPx
   set vert3Ypx $ybotPx

   ########################################################
   ## Calculate the determinant, D, based on the 3 vertices.
   ########################################################

   set a11 [expr {$vert1Xpx - $vert3Xpx}]
   set a21 [expr {$vert1Ypx - $vert3Ypx}]
   set a12 [expr {$vert2Xpx - $vert3Xpx}]
   set a22 [expr {$vert2Ypx - $vert3Ypx}]
   set D [expr {double(($a11 * $a22) - ($a21 * $a12))}]

   ## FOR TESTING:
   #  puts "Determinant D: $D"


   #########################################################################
   ## HERE IS THE 'GUTS' OF DRAWING THE TRIANGLE (a double loop):
   ## 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, according to barymetric coordinates (L1,L2,L3) and
   ## according to our 'color-metric':  v = 3 * min(L1,L2,L3)
   ##
   ## We can use the symmetry of the triangle shape. We can loop over the
   ## pixels of the left half of the image and set the colors of the other
   ## half using the calculated pixel color in the left half.
   #######################################################################

   for {set yPx 0} {$yPx < $imgHeightPx} {incr yPx} {

      #######################################################
      ## Initialize var 'LISTcolors' to hold a row of colors.
      #######################################################

      # set LISTcolors {}

      for {set xPx 0} {$xPx <= $imgWidthPx} {incr xPx} {

         ###############################################################
         ## If we are on the left (or right) of the rectangle containing
         ## the triangle, set the pixel to the background color
         ## and skip out ('continue') to the next pixel.
         ###############################################################

         if {$xPx < $xleftPx || $xPx > $xrightPx || \
             $yPx < $ytopPx  || $yPx > $ybotPx} {
            imgID put $COLORBKGDhex -to $xPx $yPx
            # lappend LISTcolors $COLORBKGDhex
            continue
         }

         ###############################################################
         ## Determine whether the xPx,yPx point is within the triangle
         ## by determining its barymetric coordinates.
         ##
         ## If any of the 3 coordinates is less than 0 or greater than 1,
         ## the point is outside the triangle and we set its color to the
         ## background color and 'continue' to the next step in this loop.
         ###############################################################

         set b1 [expr {$xPx - $vert3Xpx}]
         set b2 [expr {$yPx - $vert3Ypx}]

         set L1 [expr { (($b1 * $a22) - ($b2 * $a12)) / $D }]
         set L2 [expr { (($a11 * $b2) - ($a21 * $b1)) / $D }]
         set L3 [expr {1.0 - ($L1 + $L2)}]

         ## FOR TESTING:
         #  puts "xPx: $xPx  yPx: $yPx  L1: $L1  L2: $L2  L3: $L3"

         if {$L1 < 0.0 || $L2 < 0.0 || $L3 < 0.0} {
            imgID put $COLORBKGDhex -to $xPx $yPx
            # lappend LISTcolors $COLORBKGDhex
            continue
         }

         #############################################################
         ## The L1,L2,L3<0 check above will probably catch most cases
         ## of the point being outside the triangle. This check may
         ## catch other cases.
         #############################################################

         if {$L1 > 1.0 || $L2 > 1.0 || $L3 > 1.0} {
            imgID put $COLORBKGDhex -to $xPx $yPx
            # lappend LISTcolors $COLORBKGDhex
            continue
         }

         #############################################################
         ## At this point, (xPx,yPx) is inside the triangle.
         ## Compute the color-average for the pixel at xPx,yPx --- by
         ## averaging based on the barymetric coordinates of xPx,yPx.
         #############################################################

         set Rbary [expr {int( ($L1 * $COLOR1r) + ($L2 * $COLOR2r) + ($L3 * $COLOR3r) )}]
         set Gbary [expr {int( ($L1 * $COLOR1g) + ($L2 * $COLOR2g) + ($L3 * $COLOR3g) )}]
         set Bbary [expr {int( ($L1 * $COLOR1b) + ($L2 * $COLOR2b) + ($L3 * $COLOR3b) )}]

         ##############################################################
         ## Now we need to 'shade' this color according to our
         ## shading metric :   v = 3.0 * min(L1,L2,L3)
         ##
         ## where the Li are the barycentric coordinates of a point x,y
         ## in the triangle.
         ##############################################################

         set v  [expr {3.0 * min($L1,$L2,$L3)}]

         set Vpow [expr {pow($v,$N)}]

         ## We could try changing the 'profile' ('rate'-of-change)
         ## of the image-color changes as the scale slider is moved,
         ## by applying a function to N ---
         ## like the squared function or square-root function
         ## i.e. we could use a 'double-power' function on v with N
         ## --- a power of v, using a power of N.

         # set Vpow [expr {pow($v,$N*$N)}]
         # set Vpow [expr {pow($v,sqrt($N))}]

         set oneMinusVpow [expr {1.0 - $Vpow}]

         ## FOR TESTING:
         # if {$xPx == $xmidPx} {
         #    puts "Vpow: $Vpow   oneMinusVpow: $oneMinusVpow"
         # }

         set Rshaded [expr { int( ($Vpow * $Rbary) + ($oneMinusVpow * $COLORBKGDr) ) }]
         set Gshaded [expr { int( ($Vpow * $Gbary) + ($oneMinusVpow * $COLORBKGDg) ) }]
         set Bshaded [expr { int( ($Vpow * $Bbary) + ($oneMinusVpow * $COLORBKGDb) ) }]

         set hexcolor [format #%02X%02X%02X $Rshaded $Gshaded $Bshaded]

         #############################################################
         ## Draw the color-shaded pixel at x,y.
         #############################################################

         imgID put $hexcolor -to $xPx $yPx

         #############################################################
         ## Alternatively, append the color to var 'LISTcolors'.
         #############################################################

         # lappend LISTcolors $hexcolor
 
      }
      ## END OF the x-loop

      ##################################################################
      ## Alternatively, draw an entire row here, using var 'LISTcolors'
      ## and prepare the LISTcolors var for the next row.
      ##################################################################

      # imgID put $LISTcolors -to 0 $yPx

      ## FOR TESTING: (show a horizontal line before going to the next one)
      #   update
 
   }
   ## END OF  the y-loop
   
   ## Reset the cursor from a 'watch' cursor.
   # . config -cursor {}

   #########################################################
   ## Change the title of the window to show execution time.
   #########################################################

   wm title . \
   "DONE drawing.  [expr [clock milliseconds]-$t0] milliseconds elapsed"

}
## END OF proc 'ReDraw'


##+#####################################################################
## proc 'set_vertex_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_vertex_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

   ## Update the colors on the color buttons.

   update_color_buttons

   ## Redraw the geometry in the new interior color.

   ReDraw 0

}
## END OF proc 'set_vertex_color1'


##+#####################################################################
## proc 'set_vertex_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 value to set an 'edge' color.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR2  button
##+#####################################################################

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

   ## Update the colors on the color buttons.

   update_color_buttons

   ## Redraw the geometry in the new edge color.

   ReDraw 0

}
## END OF proc 'set_vertex_color2'


##+#####################################################################
## proc 'set_vertex_color3'
##+##################################################################### 
## 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.buttCOLOR3  button
##+#####################################################################

proc set_vertex_color3 {} {

   global COLOR3r COLOR3g COLOR3b COLOR3hex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLOR3r: $COLOR3r"
   #    puts "COLOR3g: $COLOR3g"
   #    puts "COLOR3b: $COLOR3b"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLOR3r $COLOR3g $COLOR3b]

   #   $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 COLOR3hex "#$hexRGB"
   set COLOR3r $r255
   set COLOR3g $g255
   set COLOR3b $b255

   ## Update the colors on the color buttons.

   update_color_buttons

   ## Redraw the geometry in the new edge color.

   ReDraw 0

}
## END OF proc 'set_vertex_color3'


##+#####################################################################
## 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.buttCOLORBKGD  button
##+#####################################################################

proc set_background_color {} {

   global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex
   # global feDIR_tkguis

   ## FOR TESTING:
   #    puts "COLORBKGDr: $COLORBKGDr"
   #    puts "COLORBKGDg: $COLORBKGDb"
   #    puts "COLORBKGDb: $COLORBKGDb"

   set TEMPrgb [ exec \
       ./sho_colorvals_via_sliders3rgb.tk \
       $COLORBKGDr $COLORBKGDg $COLORBKGDb]

   #   $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 COLORBKGDhex "#$hexRGB"
   set COLORBKGDr $r255
   set COLORBKGDg $g255
   set COLORBKGDb $b255

   ## Update the colors on the color buttons.

   update_color_buttons

   ## Redraw the geometry in the new background color.

   ReDraw 0

}
## END OF proc 'set_background_color'


##+#####################################################################
## proc 'update_color_buttons'
##+##################################################################### 
## PURPOSE:
##   This procedure is invoked to update the colors and text on 4 set-color
##   buttons, to show current colors (and hex values of those colors) on
##   vertex1xolor, vertex2color, vertex3color, and background-color buttons.
##
##   This proc sets the background color of each of those buttons
##   to its current color --- and sets foreground color to a
##   suitable black or white color, so that the label text is readable.
##
## Arguments: global color vars
##
## CALLED BY:  4 colors procs:
##            'set_vertex_color1'
##            'set_vertex_color2'
##            'set_vertex_color3'
##            'set_background_color'
##             and the additional-GUI-initialization section at
##             the bottom of this script.
##+#####################################################################

proc update_color_buttons {} {

   global aRtext  \
      COLOR1r COLOR1g COLOR1b COLOR1hex \
      COLOR2r COLOR2g COLOR2b COLOR2hex \
      COLOR3r COLOR3g COLOR3b COLOR3hex \
      COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex

   ## Set background color on the COLOR1 button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLOR1 configure -bg $COLOR1hex
   .fRbuttons.buttCOLOR1 configure -text "$aRtext(buttonCOLOR1)
$COLOR1hex"

   set sumCOLOR1 [expr {$COLOR1r + $COLOR1g + $COLOR1b}]
   if {$sumCOLOR1 > 300} {
      .fRbuttons.buttCOLOR1 configure -fg #000000
   } else {
      .fRbuttons.buttCOLOR1 configure -fg #f0f0f0
   }


   ## Set background color on the COLOR2 button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLOR2 configure -bg $COLOR2hex
   .fRbuttons.buttCOLOR2 configure -text "$aRtext(buttonCOLOR2)
$COLOR2hex"

   set sumCOLOR2 [expr {$COLOR2r + $COLOR2g + $COLOR2b}]
   if {$sumCOLOR2 > 300} {
      .fRbuttons.buttCOLOR2 configure -fg #000000
   } else {
      .fRbuttons.buttCOLOR2 configure -fg #f0f0f0
   }


   ## Set background color on the COLOR3 button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLOR3 configure -bg $COLOR3hex
   .fRbuttons.buttCOLOR3 configure -text "$aRtext(buttonCOLOR3)
$COLOR3hex"

   set sumCOLOR3 [expr {$COLOR3r + $COLOR3g + $COLOR3b}]
   if {$sumCOLOR3 > 300} {
      .fRbuttons.buttCOLOR3 configure -fg #000000
   } else {
      .fRbuttons.buttCOLOR3 configure -fg #f0f0f0
   }


   ## Set background color on the COLORBKGD button, and
   ## put the background color in the text on the button, and
   ## set the foreground color of the button.

   .fRbuttons.buttCOLORBKGD configure -bg $COLORBKGDhex
   .fRbuttons.buttCOLORBKGD configure -text "$aRtext(buttonCOLORBKGD)
$COLORBKGDhex"

   set sumCOLORBKGD [expr {$COLORBKGDr + $COLORBKGDg + $COLORBKGDb}]
   if {$sumCOLORBKGD > 300} {
      .fRbuttons.buttCOLORBKGD configure -fg #000000
   } else {
      .fRbuttons.buttCOLORBKGD configure -fg #f0f0f0
   }

}
## END OF proc 'update_color_buttons'


##+#############################################################
## proc ReDraw_if_canvas_resized
##
## PURPOSE: To reduce the number of 'ReDraw' command executions
##          performed as the window border is being dragged.
##
## 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} {

      ######################################################
      ## This 'after' is intended to prevent too many
      ## redraws (and flickering and unnecessary processing)
      ## as the window is being moved.
      ######################################################

      after 200

      ReDraw 0
      set PREVcanWidthPx  $CURcanWidthPx
      set PREVcanHeightPx $CURcanHeightPx
   }

}
## END OF ReDraw_if_canvas_resized


##+########################################################################
## PROC 'popup_msgVarWithScroll'
##+########################################################################
## PURPOSE: Report help or error conditions to the user.
##
##       We do not use focus,grab,tkwait in this proc,
##       because we use it to show help when the GUI is idle,
##       and we may want the user to be able to keep the Help
##       window open while doing some other things with the GUI
##       such as putting a filename in the filename entry field
##       or clicking on a radiobutton.
##
##       For a similar proc with focus-grab-tkwait added,
##       see the proc 'popup_msgVarWithScroll_wait' in a
##       3DterrainGeneratorExaminer Tk script.
##
## REFERENCE: page 602 of 'Practical Programming in Tcl and Tk',
##            4th edition, by Welch, Jones, Hobbs.
##
## ARGUMENTS: A toplevel frame name (such as .fRhelp or .fRerrmsg)
##            and a variable holding text (many lines, if needed).
##
## CALLED BY: 'help' button
##+########################################################################
## To have more control over the formatting of the message (esp.
## words per line), we use this 'toplevel-text' method, 
## rather than the 'tk_dialog' method -- like on page 574 of the book 
## by Hattie Schroeder & Mike Doyel,'Interactive Web Applications
## with Tcl/Tk', Appendix A "ED, the Tcl Code Editor".
##+########################################################################

proc popup_msgVarWithScroll { toplevName VARtext } {

   ## global fontTEMP_varwidth #; Not needed. 'wish' makes this global.
   ## global env

   # bell
   # bell
  
   #################################################
   ## Set VARwidth & VARheight from $VARtext.
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################
 
   set VARlist [ split $VARtext "\n" ]

   ## For testing:
   #  puts "VARlist: $VARlist"

   set VARheight [ llength $VARlist ]

   ## For testing:
   #  puts "VARheight: $VARheight"


   #################################################
   ## To get VARwidth,
   ##    loop through the 'lines' getting length
   ##     of each; save max.
   #################################################

   set VARwidth 0

   #############################################
   ## LOOK AT EACH LINE IN THE LIST.
   #############################################
   foreach line $VARlist {

      #############################################
      ## Get the length of the line.
      #############################################
      set LINEwidth [ string length $line ]

      if { $LINEwidth > $VARwidth } {
         set VARwidth $LINEwidth 
      }

   }
   ## END OF foreach line $VARlist

   ## For testing:
   #   puts "VARwidth: $VARwidth"


   ###############################################################
   ## NOTE: VARwidth works for a fixed-width font used for the
   ##       text widget ... BUT the programmer may need to be
   ##       careful that the contents of VARtext are all
   ##       countable characters by the 'string length' command.
   ###############################################################


   #####################################
   ## SETUP 'TOP LEVEL' HELP WINDOW.
   #####################################

   catch {destroy $toplevName}
   toplevel  $toplevName

   # wm geometry $toplevName 600x400+100+50

   wm geometry $toplevName +100+50

   wm title     $toplevName "Note"
   # wm title   $toplevName "Note to $env(USER)"

   wm iconname  $toplevName "Note"


   #####################################
   ## In the frame '$toplevName' -
   ## DEFINE THE TEXT WIDGET and
   ## its two scrollbars --- and
   ## DEFINE an OK BUTTON widget.
   #####################################

   if {$VARheight > 10} {
      text $toplevName.text \
         -wrap none \
         -font fontTEMP_varwidth \
         -width  $VARwidth \
         -height $VARheight \
         -bg "#f0f0f0" \
         -relief raised \
         -bd 2 \
         -yscrollcommand "$toplevName.scrolly set" \
         -xscrollcommand "$toplevName.scrollx set"

      scrollbar $toplevName.scrolly \
         -orient vertical \
         -command "$toplevName.text yview"

      scrollbar $toplevName.scrollx \
         -orient horizontal \
         -command "$toplevName.text xview"
   } else {
      text $toplevName.text \
         -wrap none \
         -font fontTEMP_varwidth \
         -width  $VARwidth \
         -height $VARheight \
         -bg "#f0f0f0" \
         -relief raised \
         -bd 2 
   }

   button $toplevName.butt \
      -text "OK" \
      -font fontTEMP_varwidth \
      -command  "destroy $toplevName"

   ###############################################
   ## PACK *ALL* the widgets in frame '$toplevName'.
   ###############################################

   ## Pack the bottom button BEFORE the
   ## bottom x-scrollbar widget,

   pack  $toplevName.butt \
      -side bottom \
      -anchor center \
      -fill none \
      -expand 0


   if {$VARheight > 10} {
      ## Pack the scrollbars BEFORE the text widget,
      ## so that the text does not monopolize the space.

      pack $toplevName.scrolly \
         -side right \
         -anchor center \
         -fill y \
         -expand 0

      ## DO NOT USE '-expand 1' HERE on the Y-scrollbar.
      ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS
      ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA.
                
      pack $toplevName.scrollx \
         -side bottom \
         -anchor center \
         -fill x  \
         -expand 0

      ## DO NOT USE '-expand 1' HERE on the X-scrollbar.
      ## THAT KEEPS THE TEXT AREA FROM EXPANDING.

      pack $toplevName.text \
         -side top \
         -anchor center \
         -fill both \
         -expand 1
   } else {
      pack $toplevName.text \
         -side top \
         -anchor center \
         -fill both \
         -expand 1
   }


   #####################################
   ## LOAD MSG INTO TEXT WIDGET.
   #####################################

   ##  $toplevName.text delete 1.0 end
 
   $toplevName.text insert end $VARtext
   
   $toplevName.text configure -state disabled
  
}
## END OF PROC 'popup_msgVarWithScroll'


##+########################
## END of PROC definitions.
##+########################
## Set HELPtext variable.
##+########################

set HELPtext "\
*** HELP for this utility to
 ***      DRAW a 3-COLOR-GRADIENT Isosceles TRIANGLE
 ***      --- with 3D-like, Shaded Edges

This Tk GUI script draws a color-filled 'isosceles triangle'
--- with shading around the three edges of the triangle.

The pixel colors in the triangle are based on 3 user-selected colors
assigned to the 3 vertices of the triangle --- and the color at any
pixel is an average of the 3 colors using the barymetric coordinates
of the point (pixel) to compute the average.

For example, if (L1,L2,L3) are the barymetric coordinates of a pixel,
where L1 + L2 + L3 = 1, we use the RGB colors of the 3 vertices
to calculate the RGB color at the pixel, as follows:

Let the RGB values of vertex 1, 2, and 3 be

   (R1,G1,B1) and (R2,G2,B2) and (R3,G3,B3).

Then
      Rpixel = (L1 * R1) + (L2 * R2) + (L3 * R3)
      Gpixel = (L1 * G1) + (L2 * G2) + (L3 * G3)
      Bpixel = (L1 * B1) + (L2 * B2) + (L3 * B3)

**********************************************
EXAMPLES OF USE of this triangle-shaped image:

   This 3D-like triangle could be used as

   1) an icon or logo background. Nice quality text could be added
      on top of the triangle.
 
   2) an unusual, triangle-shaped '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.

********************************
RESIZE and REDRAW considerations:

The user can resize the (isosceles) triangle on the canvas by resizing the
window. Enlarging the window, enlarges the canvas and the triangle on that
canvas.

We could have put additional scale widgets on the GUI to allow for 
precise setting of the dimensions of the canvas. And we could have
implemented a method by which the user could drag vertices on the canvas
--- even allowing the triangle to be non-isosceles.

But resizing the (isosceles) triangle by resizing the window will probably
serve for most uses of this color-shaded-triangle drawing utility.

A redraw should be done whenever

   (a) a color button is used to change a vertex or background color,

   (b) the slider-button on the 'shading-exponent' scale is reset,

   (c) the window (and thus the canvas) is resized ---
       so that the triangle-shape is redrawn using new locations
       of one  or more of the 3 vertices on the canvas.

A redraw includes clearing the canvas and redrawing the triangle shape
on the solid color background. In the current implementation, the
redraw on a large canvas may take 10 seconds or more --- on a small
canvas, about 1 to 2 seconds.

In cases (a) and (b) above, a redraw is done automatically --- after
a color is chosen or after mouse-button1 is released from the slider
button of the 'scale' widget.

In case (c), the user can click on the 'ReDraw' button on the GUI.

If the automatic redraw in case (b) is annoying, you can comment
out that 'bind' command in this script and use the 'ReDraw' button.

****************************
CONTROLLING THE EDGE-SHADING:

In several similar scripts that do nice 'edge-shading' of various shapes,
namely

   -  \"GUI for Drawing 'Super-ellipses', with nice shaded edges\"
      at https://wiki.tcl-lang.org/37004
  
   -  \"GUI for Drawing Rectangular 'Buttons' with nice shaded edges\"
      at https://wiki.tcl-lang.org/37143

   -  \"GUI for Drawing 'Super-formula' shapes, with nice shaded border\"
      at https://wiki.tcl-lang.org/37156

   -  \"GUI for Drawing 'Donut' shapes, with nice shaded border\"
      at https://wiki.tcl-lang.org/37214


I devised a 'color-shading-metric' --- 'v' --- at each pixel point (x,y)
that is within the rectangle/super-ellipse/super-formula/donut shape ---
where v is between 0 and 1. And the value of v goes to 0 at the edges
of these shapes.

Then v and (1-v) are applied to a user-selected background color
and a user-determined 'fill' color for any pixel within the shape ---
to get a 'weighted-average' of the user-selected colors, for
the color at a given pixel x,y.

Let (L1,L2,L3) denote the barymetric coordinates of a pixel.
For the 'color-shading-metric', this script/utility uses

   v(x,y) = v(L1,L2,L3) = 3.0 * min(L1,L2,L3)

The 'shaded color' at x,y is determined by calulating a weighted average
based on applying the factor v to 'color-bary' of the pixel --- and by
applying (1 - v) to 'color-bkgd'. That is,

   shaded-color =  v * color-bary + (1 - v) * color-bkgd.

Actually there are 3 calculations:

   Rshaded =  v * Rbary + (1 - v) * Rbkgd
   Gshaded =  v * Gbary + (1 - v) * Gbkgd
   Bshaded =  v * Bbary + (1 - v) * Bbkgd

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

But, it turns out that 1-v and v give 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), where N could be fractional rather than
an integer.

For example, we could use N = 0.5 (a square root) just as
easily as we could use N = 2 (the square of v). (This is easy
because we are using the 'pow', power, function of Tcl.)

In fact, we may find that N < 1 gives nicer effects.
Say, in the center of the image, we want to 'spread out' the
influence of the pixel colors (to dominate over the background color).
Then, given the use of v and (1 - v) in the 3 equations above,
we would want N to be a fraction less than 1, like 0.5 or even 0.1.

So a scale widget is available on the GUI for adjusting the N (power)
exponent to change the 'extensity' of the 'shading-to-background-color'
over the triangle.

You may find that N = 0.4 gives nicer shading than N = 1.0.

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

***************************
FINE CONTROL OF THE SLIDER:

Most people know that you can drag the slider of the 'scale' widget
by clicking on the 'slider button' with mouse-button1 and dragging
the slider button. This is a rather coarse way of moving the
slider button.

It is not so obvious that the Tk scale widget also allows
you to move the 'slider button' by clicking in the 'trough'
on EITHER SIDE of the slider button. By repeated clicking
in the trough, the slider can be advanced one scale
resolution-unit per click.

Furthermore, one can RAPIDLY move the slider button ONE
RESOLUTION UNIT per click by clicking in the trough
and HOLDING down the mouse button. The slider moves
one resolution unit at a time UNTIL the mouse button
is RELEASED.

*****************************
CAPTURING THE GENERATED IMAGE:

   A screen/window capture utility (like 'gnome-screenshot'
   on Linux) can be used to capture the GUI image in a PNG
   or GIF 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 image could also be brightened by using the 'gamma
   correction' feature of an image editor like 'mtpaint'.

   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 icons on
   desktops --- or for the background of icons or buttons
   in (Tk) GUIs or 'toolchests'.
"


##+#####################################################
## ADDITIONAL GUI INITIALIZATION SECTION:
##+#####################################################

##+######################################################
## Set initial 'shading-exponent' scale widget variable.
##+######################################################

# set N 0.1
  set N 0.2
# set N 0.4
# set N 1.0
# set N 2.0

##+#######################################################
## Set the colors and text in the color buttons, based on
## the initial setting for the colors.
##+#######################################################

update_color_buttons

##+###################################################
## 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, the
## following bind command causes redraws whenever the canvas is resized.
##
## (If we find that this causes too many superfluous, CPU-time-consuming,
##  flicker-causing redraws, we may use a 'ReDraw' button on the GUI.
##  in place of this <Configure> technique.)
##
## (NOTE: A queued-events-update causes about 40 redraws (for all the
##        widgets on the GUI being resized), if we use '.' instead of
##        '.fRcanvas.can'.)
##
## COMMENTED for now. The 'ReDraw' button can be used.
##+#####################################################################

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


CREDITS :

I would like to give thanks to 'ulis' (deceased in 2008, R.I.P.) whose super-ellipse script on the Tcl-Tk wiki page Mathematics jewels indicated to me that we could get nicely shaded edges on geometric shapes --- using 'image create photo' and a Tk canvas.

(I notice that the link to his 'jewels' image has gone dead. I plan to make an image from my copy of his script and post the image there to replace the dead link. He deserves his image to be shown, to see what he wrought.)

Also --- thanks go to James Emery, who has some nice PDF's on math topics --- in particular, on barycentric coordinates --- at a www.stem2.org site. Emery seems to be active in STEM (Science, Technology, Engineering, and Math) education activities in the Kansas City area of the United States.


MATH FOR THE BARYMETRIC BLEND OF THREE COLORS ON THE TRIANGLE:

I have adapted the following presentation from a PDF file titled 'The Simplex and Barycentric Coordinates' by James Emery - Latest Edit: 8/30/2012

Below are formulas for computing the barymetric coordinates --- L1,L2,L3 --- of a point P relative to a triangle with vertices P1,P2,P3.

If all 3 barycentric coordinates are between 0 and 1, the point is inside the triangle.

The general computation to determine an interior point, requires 11 additions or subtractions, 6 multiplications, 2 divisions, and 3 comparisons. The computation may be done as follows.

We let P1=(x1,y1), P2=(x2,y2), P3=(x3,y3), P=(x,y). Let

   a11 = x1 - x3
   a21 = y1 - y3 
   a12 = x2 - x3 
   a22 = y2 - y3
      and
   b1 =  x - x3 
   b2 =  y - y3 .

Let D be the determinant:

    D = (a11 * a22) - (a21 * a12)

Then we have

   L1 = ((b1 * a22) - (b2 * a12)) / D

   L2 = ((a11 * b2) - (a21 * b1)) / D

   L3 = 1 - (L1 + L2)

Emery provided C program code for the above calculations, but I assembled the Tcl-Tk code, in the script above, from this set of formulas.


DERIVATION OF A 'SHADING METRIC' FOR THE TRIANGLE :

In my other 'shading metric' scripts above, I devised a 'color-shading-metric' --- 'v' --- at each pixel point x,y that is within the rectangle/super-ellipse/super-formula/donut shape --- where v is between 0 and 1. And the value of v goes to 0 (or 1) at the edges of these shapes.

Then v and (1-v) are applied to a user-selected background color and a user-selected 'fill' color for any pixel within the shape --- to get a 'weighted-average' of the user-selected colors, for the color at a given pixel x,y.

For the triangle of the script on this page, we use the barycentric coordinates of any pixel within the triangle --- to formulate our 'shading metric'.

For a given pixel x,y, let us say its barymetric coordinates are (L1,L2,L3) where L1 + L2 + L3 = 1.

Note that at the edges (and vertices) of the triangle, at least one of the 3 barymetric coordinates is 0. And let us say that we want our metric, v(x,y) = v(L1,L2,L3), to be 0 at the edges of the triangle.

Also, we want v to be a maximum of 1.0 somewhere in the interior of the triangle.

Note that the 'barymetric center' of the triangle is at the point/pixel where (L1,L2,L3) = (1/3,1/3,1/3). And when one looks at any point within the triangle which is not on that center, at least one of the coordinates is less than 1/3.

Based on these observations, for our 'color-shading-metric', we will use

   v(x,y) = v(L1,L2,L3) = 3.0 * min(L1,L2,L3)

Note that v is 0 on the edges (and vertices) of the triangle, it is 1.0 at the barymetric center, and it is between 0 and 1 at other points in the triangle.

So we have a suitable metric, v.

_____

USING THE SHADING-METRIC:

At a point x,y, we determine the 'shaded color' at the point by using a color interpolated between

  1) Rpixel,Gpixel,Bpixel --- 'color-bary', say --- which is 
     the barymetrically-determined color of the point/pixel x,y
     calculated as follows:

     Let the RGB values of vertex 1, 2, and 3 be
             (R1,G1,B1) and (R2,G2,B2) and (R3,G3,B3).
     Then
             Rpixel = (L1 * R1) + (L2 * R2) + (L3 * R3)
             Gpixel = (L1 * G1) + (L2 * G2) + (L3 * G3)
             Bpixel = (L1 * B1) + (L2 * B2) + (L3 * B3)

and

  2) a user-selected background color, 'color-bkgd', say.

We calculate the 'shaded color' at x,y by calculating a weighted average based on applying the factor v to 'color-bary' --- and applying (1 - v) to 'color-bkgd'. That is,

         shaded-color =  v * color-bary + (1 - v) * color-bkgd.

We actually calculate via formulas like

   Rshaded =  v * Rpixel + (1 - v) * Rbkgd
   Gshaded =  v * Gpixel + (1 - v) * Gbkgd
   Bshaded =  v * Bpixel + (1 - v) * Bbkgd

Thus we will get an edge-shading (the 3D or 'soft edge' effect) for the triangle shape.

Actually, it turns out that 1-v and v give 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), where N could be fractional rather than integer. For example, we could use N = 0.5 (a square root) just as easily as we could use N = 2 (the square of v).

This is easy to do in Tcl-Tk, by using the 'pow' (power) function.

In fact, say we want to 'spread out' the influence of the pixel colors (to dominate over the background color) in the center of the image. Then, given the use of v and (1 - v) in the 3 equations above, we would want N to be a fraction less than 1, like 0.5 --- or 0.2.


SOME POSSIBLE ENHANCEMENTS:

** Performance Improvement - The code above puts colors in the Tk 'photo' structure pixel by pixel. I can reduce the draw times (on the order of 1 to 10 seconds) down to less than a second --- if I build the entire image as a list-of-lists (a list containing lists of row-colors) --- and do one 'put' of the list into the 'photo' structure.

Such a list would probably be on the order of 8 Megabytes in size. In the old days, that would have been a no-no. But since users typically are dealing with over a Gigabyte of free memory on their computers, 8 Megabytes is less than one percent of that un-used memory. Doing a single 'put', rather than a 'put' for each pixel of the rectangular 'photo' structure, is high on my list of enhancements --- if I live to re-visit this code.

** Edge-Shading Improvement - I have to admit that I was a little disappointed that the 'shading-metric' that I am using does not seem to give me the kind of control on the edge shading that I was hoping for. I was hoping to create a sharper fall-off into the background shading, near the edges of the triangle.

I think I need to change the formulation of the 'shading-metric' --- perhaps some 'exponent-on-an-exponent' technique will work. I made a couple of attempts, but did not achieve the results I was looking for. I hope to live to re-visit this issue, as well as the performance issue.

** Non-Isosceles Triangles - I could do something like use 'create oval' on the canvas to make circular markers over the vertices. Then bindings could be made on those markers so that the user can drag them where they would like the vertices to be after the next re-draw.

I am not sure that spending time on this is worthwhile. If this utility proves to be more experimental than useful, then maybe it is better to put this kind of feature in a more general utility.

** More Re-Sizing Controls - In this intial implementation of this demo-and/or-utility, I allow the user to change the size and aspect-ratio of the isosceles triangle by simply changing the size of the containing window.

I could allow for more precise controls, including control of the margin of background color around the triangle, but, again, I am not sure that spending time on this is worthwhile. Re-sizing the triangle by changing the window size is probably sufficient for most uses of this demo-and/or-utility.

** Other - There are probably some other enhancements that I should mention here, but I just hit the bottom of my ideas-well.


IN CONCLUSION:

I would like to give my usual thanks to Ousterhout and the maintainers of the 'wish' interpreter for making all these mathematical and graphical utilities and experiments (that I have done and that are on my 'to-do' list) possible.


arjen - 2013-09-06 13:04:52

You may want to have a look at the tkpath extension as well. It has some great features - see some examples on the Wiki