GUI for Drawing a 'Super-Formula' Shape, with nice shaded border

uniquename - 2012oct18

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.

On the page GUI for Drawing 'Super-ellipses', with nice shaded edges, I presented code for a GUI that uses 'create image' on a canvas, along with 'put' commands, to implement the drawing of a 'super-ellipse' with nice 'edge shading' --- shading from a user-specified color for the super-ellipse to a user-specified color for a surrounding background.

(Thanks to 'ulis' for his donated script that led to that much-enhanced script.)

The edge-shading technique was based on using a 'color-metric' scalar parameter given by the equation for the super-ellipse:

            v = |x/a|^n + |y/b|^n

I was able to use this 'color-metric' technique to create another Tk script that provides nice 'edge shading' for a rectangle with a color gradient across it (in the x or y direction).

The code and comments are on the page GUI for Drawing Rectangular 'Buttons' with nice shaded edges. In that code, the color-metric that I used was of the form:

          v =  max(abs(x/xhalf), abs(y/yhalf))

In both the super-ellipse case and the color-gradient-rectangle case, I tranformed a script based on a canvas-'create line' technique to a script that used a canvas-'create image' technique.

I decided that I would return to the script at GUI for Drawing 'Superformula' shapes, with choice of colors and use that script --- that is based on a canvas-'create polygon' technique --- to create a superformula-shape drawing script that uses a canvas-'create image' technique.

By doing that, I would be able to get nicer looking superformula shapes --- that is, with nice 3D-looking edge shading --- for future use on Tk GUI's. I just needed to come up with a 'color-metric' to use for the superformula shape.

Here is a description of the 'color-metric' that I devised. It is a 'little-r-over-big-R' formula.

The equation for the border of the 'superformula shape' is:

  R(theta) = ( |cos(m*theta/4)/a| ^ n2 + |sin(m*theta/4)/b| ^ n3 ) ^ -1/n1

(You use this radius along with cos(theta) and sin(theta) to get the x,y coordinates of points on the border of the 'superformula shape'.)

Let us say that x,y coordinates on the 'super-formula shape' outline --- and anywhere on the canvas --- are measured from the center of the canvas.

The distance from the origin to the point x,y is given by the Pythagorean formula: r = sqrt( x^2 + y^2 ).

Furthermore, our point x,y makes an angle 'theta', say with the x-axis.

Then let us define our 'color-metric' to be

     v = r(x,y) / R(theta)

where 'theta' is the angle whose sine is y/r --- and cosine is x/r. 'theta' can be determined from the ratio y/r and the quadrant in which the point x,y lies.

Note that 'little-r' is the distance to x,y. And 'big-R' is the number given by the super-formula.

Note that for a point x,y in the interior of the 'super-formula shape', the ratio r/R is less than one --- because the point x,y lies on a radial line from 0,0 to the boundary of the 'super-formula shape', and the distance to that boundary is R, which is greater than r.

Furthermore, the metric is zero at the origin (x,y)=(0,0), because r = sqrt( 0^2 + 0^2) = 0.

Furthermore, on the boundary of the 'super-formula shape', the values r and R are the same (because they define the same point) --- so the metric is equal to 1.0.

And for x,y points outside the 'super-formula shape', v = r/R is greater than 1.0.

So we have a suitable metric, v.

________________

Now it was a matter of putting the pieces together. I took the 'pieces' from the 2 scripts at GUI for Drawing 'Superformula' shapes, with choice of colors and GUI for Drawing Rectangular 'Buttons' with nice shaded edges.

I ended up with the following GUI --- whose initial image is somewhat reminiscent of the hair-style of some of the Simpsons cartoon characters.

uperformula_shaded_defaults_disk12points_magentaONblack_884x533.jpg

Because I could not, in general, count on any symmetry (in the x or y directions) in the 'super-formula shape', I could NOT draw a horizontal scan-line at a time, with calls like:

           imgID put $hexcolorsLIST -to 0 $y

where 0 $y is the leftmost position of a horizontal line of hex-colors for the pixels.

Instead, I 'poked' a hex-color value into each pixel with calls like:

           imgID put $hexcolor -to $x $y

____

Note that I added an N-scale widget to the GUI to allow the user to control an exponent that is used to control the 'extent' of the shading at the border of the 'superformula shape'.

I also added a display of 'elapsed time' for each redraw --- by using the Tcl 'clock milliseconds' command.

Having learned my lesson (from development of the script at GUI for Drawing 'Super-ellipses', with nice shaded edges) about using braces with 'expr' statements for much better execution times (as Welch et. al. point out by page 7 in the 4th edition of 'Practical Programming in Tcl and Tk'), I consistently used braces with ALL 'expr' commands --- in particular, in the compute-intensive 'ReDraw' proc.

---

Note that I have supplied 2 buttons on the GUI with which to set the 'fill' color and the 'background' color. Those 2 buttons call on a color-selector-GUI script to set those colors. You can make that color-selector script by cutting-and-pasting the code from the page A non-obfuscated color selector GUI on this site.

_____________________________________________________________________

Below is the code that produced this GUI.

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

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

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

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

  2) Define & pack all widgets in the frames.

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

  4) Define PROCS, if needed.

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

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

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

_________________________________________________________________

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

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

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

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

Also, you could 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.

Furthermore, there are variables used to set geometry parameters of widgets --- parameters such as border-widths and padding. And you could change the '-relief' values for frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

_____________________________________________________________________

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

The most complex code is in the 'ReDraw' proc.

The copious comments in the code might help Tcl-Tk coding 'newbies' get started in making GUI's like this. Without the comments --- especially in the 'ReDraw' proc, the code might look even more cryptic than it already is.

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


 Code for the Tk script 'draw_superformulaShape_colorFilled_withShadedBorder.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: draw_superformulaShape_colorFilled_withShadedBorder.tk
##
## 
## PURPOSE:  This Tk GUI script facilitates the creation of a color-filled
##           'super-formula shape' with shading to a background color, around
##           the border of the shape.
##
##    This script makes this shaded shape via  a Tk image 'structure'
##    placed on a Tk canvas widget.
##
##    The image is put on the canvas via a Tk canvas 'image create' command
##    --- and the image is generated via 'put <hexcolor> -to $x $y'
##    commands on the image.
##
##    The rectangular image-structure covers the entire canvas widget. The
##    superformula-shape lies within the canvas (and the image structure)
##    with a margin around it.  In the margin, outside the
##    superformula-shape on the canvas, a user-selected 'background' color
##    is applied.
##
#########################
## REFERENCES and CREDITS:
##
##  This Tk script is PARTLY based on my Tk script at https://wiki.tcl-lang.org/36916
##  - 'GUI for Drawing 'Superformula' shapes, with choice of colors' ---
##  which, in turn, was based on a Tk script at the web page
##  called 'Superformula' at https://wiki.tcl-lang.org/12977 ---
##  by  Gerard Sookahet (GS) in 2004 November.  On that page, GS points
##  out that the equation for the 'super-formula' is:
##
##  R(theta) = ( |cos(m*theta/4)/a| ^ n2 + |sin(m*theta/4)/b| ^ n3 ) ^ -1/n1
##
## GS points out:
## "Superformula was proposed by Johan Gielis (a Belgian botanist) in 1997.
##  He has modified and generalized the superellipse formula discovered by
##  Gabriel Lamé in 1818 (a French mathematician). Superformula consists in
##  a simple 2D analytical expression allowing to draw a wide variety of
##  geometric and natural shapes (starfish, petals, snowflakes) by choosing
##  suitable values relevant to few parameters."
##
##  Those 2 previous scripts used canvas 'create polygon' commands to
##  generate the superformula-shape --- NOT 'create image'.
##
##  The canvas 'create image' (and 'put' commands) technique used in this
##  script is based on two of my scripts that used this technique to
##  make SHADED EDGES around 'super-ellipses' and around
##  color-gradient-rectangles.
##
##  See
##      https://wiki.tcl-lang.org/37004 -
##      GUI for Drawing 'Super-ellipses', with nice shaded edges
##  and
##      https://wiki.tcl-lang.org/ -
##      GUI for Drawing Rectangular 'Buttons' with nice shaded edges
##
##+############################################
## SOME NOTES ON THE 'SUPER-FORMULA' PARAMETERS:
## 
##  Note that 'm' generates protrusions/intrusions on the shape.
##  As theta goes from 0 to 2*pi in the formula above, and if m=2,
##  the angle m*theta/4 goes from 0 to pi --- one 'hump' of a sine
##  or cosine curve. In general, if m = 2*n, then there will be
##  (at least) n protrusions/intrusions.
##
##  We will make the 'm' scale the first scale of several, since it
##  fundamentally changes the shape.
##
##  By some experimenting with the GUI, one finds
##  that if  'a' and  'b' are kept equal 
##  and  if 'n2' and 'n3' are kept equal,
##  the shape will tend to be symmetric.
##  This makes sense by looking at the formula above.
##
##  We will make changing the 'a'  scale make the 'b'  scale change with it.
##  We will make changing the 'n2' scale make the 'n3' scale change with it.
##  Then the user can change the 'b' and 'n3' scales to make them
##  different from the 'a' and 'n2' scales, resp.
##
##  Note that the numerators of the 2 terms in absolute value marks (and
##  raised to the exponents n2 and n3) are sin and cos which have a max
##  value of 1. If n1 = n2 = n3 = 2 and a = b, for example, then the
##  formula looks like
##    R = ( |cos(...)/a| ^ 2 + |sin(...)/a| ^ 2 ) ^ -1/2
##  And the fundamental trigonometric identity
##          cos(...) ^ 2 + sin (...) ^ 2 = 1
##  allows us to simplify this to
##    R = (a ^ -2) ^ -1/2 = a 
##  So for R(theta) to be on the order of 200 (pixels), the denominators
##  (a and b) will generally need to be on the order of 200 --- when
##  n1 = n2 = n3 = 2 and a = b.  
##
##  Note that when n1 = 4 --- and n2 = n3 = 2 and a = b, then the
##  last equation above becomes
##    R = (a ^ -2) ^ -1/4 = a ^ 1/2 = sqrt( a )
##  So  (when a & b > 1) the bigger n1 becomes, the smaller the radius becomes.
##  And (when a & b < 1) the bigger n1 becomes, the larger the radius becomes
##  (but it is less than 1).
##  So n1 can be used to zoom in/out of the view of the image/shape.
##
##  If we look at the 'resolution' and range of the scales and 
##  initial values in the Gerard Sookahet demo code, we see:
##       Resolution   From   To   Initial Value
##       ----------   ----  ----  -------------
##  m  -     0.2       0     34      6.0
##  n1 -     0.1       0.1   17      3.0
##  n2 -     0.1       0     17      1.0
##  n3 -     0.1       0     17      1.0
##  a  -    0.01      0.1    10      1.0
##  b  -    0.01      0.1    10      1.0
##
##   (We will change resolution of 'm' to 2.0 --- to avoid 'cusps' from
##    appearing on the right of the shape, at theta = 0, as the m-scale
##    sliderbar is moved.
##    The shape should then remain smooth --- no partial humps with
##    sharp-pointed 'drop-offs' --- as 'm' is changed.)
##
##+#####################
## THE SHADING TECHNIQUE (for the 3D effect):
##
##     For detail on the shading technique applied to super-ellipses,
##     see  wiki.tcl.tk/37004 -
##     GUI for Drawing 'Super-ellipses', with nice shaded edges.
##
##     The edge-shading effect for the super-ellipse benefited from the equation
##     for a super-ellipse --- more precisely, the equation for its edge:
##              |x/a|^n + |y/b|^n   =  1
##
##     The interior of the super-ellipse is given by the inequality
##              |x/a|^n + |y/b|^n  <=  1
##
##     The edge shading (3D effect) was obtained by using a 'metric' on the
##     points x,y in the super-ellipse --- a value 'v', between 0 and 1,
##     given by:  
##                     v = |x/a|^n + |y/b|^n
##     for each point inside the super-ellipse.
##
##     The equation for v dictates that the value of v is 1 on the border of
##     the super-ellipse and declines to 0 towards the center.
##
##     (1.0 - $v) is applied to the user-selected 'unshaded' RGB-color for
##     the super-ellipse --- and  $v  applied to the user-selected RGB
##     background color. A weighted-average of these pairs of RGB values
##     gives us the color at any x,y point in the superellipse. (Note that the
##     values of x and y gave us the 'v' value to apply to get the 'shaded'
##     color at x,y.)
##
##     To keep the shading from being too spread out at the edge of
##     the super-ellipse, we used a POWER of v rather than v. Note that
##     a power of v, like v to the 12th, is still a floating point number
##     between 0 and 1.
##
##   NOW CONSIDER THE 'SUPER-FORMULA':
##     To do the shading of our 'super-formula shape', it would be great
##     if we had a 'metric' on the points x,y in our canvas that is 0.0 in
##     the middle of the 'super-formula shape' and 1.0 on the boundary of
##     the 'super-formula shape'.
##
##     We can use the formula for R(theta) --- on the boundary of the
##     'super-formula shape' --- to give us such a metric.
##
##     Let us say that x,y coordinates on the 'super-formula shape' outline
##     --- and anywhere on the canvas --- are measured from the center of
##     the canvas.
##
##     The distance from the origin to the point x,y is given by the
##     Pythagorean formula:  r = sqrt( x^2 + y^2 ).
##
##     Furthermore, our point x,y makes an angle 'theta', say with the
##     x-axis.
##
##     Then let us define our 'color-metric' to be
##
##          v = r(x,y) / R(theta)
##
##     where 'theta' is the angle whose sine is y/r --- and cosine is x/r.
##     OR ... 'theta' can be determined from the ratio y/r and the quadrant
##     in which the point x,y lies.
##
##     Note that little-r is the distance to x,y. And big-R is the number
##     given by the super-formula.
##
##     Note that for a point x,y in the interior of the 'super-formula shape',
##     the ratio r/R is less than one --- because the point x,y lies on
##     a radial line from 0,0 to the boundary of the 'super-formula shape',
##     and the distance to that boundary is R, which is greater than r.
##
##     Furthermore, the metric is zero at the origin (x,y)=(0,0), because
##     r = sqrt( 0^2 + 0^2) = 0.
##
##     Furthermore, on the boundary of the 'super-formula shape', the values
##     r and R are the same (because they define the same point) --- so the
##     metric is equal to 1.0.
##
##     And for x,y points outside the 'super-formula shape', v = r/R 
##     is greater than 1.0.
##
##     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 the user-selected 'fill' color
##     (color1) of the 'super-formula shape' and the user-selected
##     background color (color2).
##
##     We calculate the 'shaded color' at x,y by calulating a weighted average
##     based on applying the factor (1.0 - $v) to color1 --- and applying $v
##     to color2. That is, shaded-color = (1 - v) * color1 + v * color2.
##
##     We actually calculate via formulas like
##        shaded-R = (1 - v) * R1 + v * R2
##        shaded-G = (1 - v) * G1 + v * G2
##        shaded-B = (1 - v) * B1 + v * B2
##
##     Thus we will get the edge-shading (the 3D effect) for the
##     'super-formula shape'.
##
##     Actually, it turns out that 1-v/v gives a rather washed-out (too
##     gradual) shading effect. It is better if we raise v to a power N
##     and use v^N and (1 - v^N).  It turns out that N = 12 gives pretty nice
##     shading, but rather than hard-code the value of N, we provide a
##     scale widget on the GUI so that the user can set the value of N.
##
##+##############
## THE GUI DESIGN:
##
##           The GUI made by this Tk script contains a rectangular
##           canvas widget on which the color-filled superformula shape
##           will be drawn.
##
##           The GUI includes 6 'scale' widgets whose slider-bars can
##           be used to change the values of m, n1, n2, n3, a, and b.
##
##           The GUI also includes a button to call a color selector GUI
##           to set the 'fill' color of the super-formula shape.
##
##           Another button calls the same color selector GUI to set a
##           background color ---  which is also the color we gradiate
##           toward at the boundary of the 'super-formula shape'.
##
##           (Note that we could put another color selector button on the
##            GUI to select the border color to be a different color from
##            the background color of the canvas area.)
##
##           A redraw includes clearing the canvas and redrawing the
##           super-formula shape.
##
##           A redraw should be done (a) whenever any of the 6 scales change,
##           (b) whenever a color button is used to change a color, and
##           (c) whenever the window (and thus the canvas) is resized ---
##           so that the image/shape will be redrawn in the center of
##           the canvas.
##
##    PERFORMANCE CONSIDERATIONS:
##        Since the redraw has a lot of pixels to color, especially when
##        the canvas is expanded to a pretty large size, a redraw may
##        take several seconds.
##
##        So it is probably not going to be feasible/pleasing to do redraws
##        'dynamically' with the '-command' option of the 'scale' widgets.
##
##        For now, I have defined a button1-release binding on each of
##        the scale widgets to trigger a redraw --- only when the user
##        finishes dragging the sliderbar of any scale.
##
##        However, it should be pointed out that if erasing the
##        canvas and calculating-colors and putting the colors in the
##        super-formula shape completes within a small fraction of a second,
##        would be feasible to do the redraws 'dynamically' with each
##        sliderbar, via the '-command' option. (But it might heat up
##        the CPU doing those operations --- by quite a few degrees.)
##
############################
## USING THE GENERATED IMAGE:
##           A screen/window capture utility (like 'gnome-screenshot'
##           on Linux) can be used to capture the GUI image in a GIF
##           or PNG file, say.
##
##           If necessary, an image editor (like 'mtpaint' on Linux)
##           can be used to crop the window capture image.  The image
##           could also be down-sized --- say to make a 'bullet' image
##           file or an icon-background image file.
##
##           The colored image file could be used with a utility (like the
##           ImageMagick 'convert' command) to change the outer, background
##           color to TRANSPARENT, making a partially transparent GIF
##           (or PNG) file. Then the semi-transparent image file could be used,
##           for 'bullets' in HTML pages or in Tk GUI's --- or for the
##           background of icons or buttons for use in GUIs.
##
##           The image could also be taken into a scalable vector graphics
##           (SVG) editor (like Inkscape on Linux) and the SVG editor used
##           to add anti-aliased text to the image. OR, the shape can be
##           used as an underlying pattern to reproduce the shape as a
##           scalable shape, by using curve drawing tools of the SVG editor.
##
##+########################################################################
## 'CANONICAL' STRUCTURE OF THIS TK CODE:
##
##  0) Set general window & widget parms (win-name, win-position,
##     win-color-scheme, fonts, widget-geometry-parms, win-size-control).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack   ALL frames and sub-frames.
##
##  2) Define all widgets in the frames. Pack them.
##
##  3) Define keyboard or mouse/touchpad/touch-sensitive-screen action
##     BINDINGS, if needed.
##
##  4) Define PROCS, if needed.
##
##  5) Additional GUI INITIALIZATION (typically with one or two of
##     the procs), if needed.
##
##
## Some detail about the code structure of this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :  '.fRleft' , '.fRright'
##
##      Sub-frames: '.fRleft.fRbuttons'
##                  '.fRleft.fRscaleM'
##                  '.fRleft.fRscaleN1'
##                  '.fRleft.fRscaleN2'
##                  '.fRleft.fRscaleN3'
##                  '.fRleft.fRscaleA'
##                  '.fRleft.fRscaleB'
##                  '.fRleft.fRscaleN'
##                  '.fRleft.fRinfo'
##
##                  The canvas widget will go in '.fRright'.
##
##  1b) Pack ALL frames.
##
##  2) Define all widgets in the frames (and pack them):
##
##     - In '.fRleft.fRbuttons':
##                          1 button widget ('Exit'), (perhaps 'Help' someday)
##                            and
##                          2 buttons (for setting the super-formula shape's
##                                     fill & the background/canvas color),
##                            and
##                          1 label widget to display the current color
##                                  values (in hex).
##
##     - In '.fRleft.fRscaleX':
##                          1 'label' and 1 'scale' widget, in each scale-frame
##                             for superformula shape parameters ---
##                             m, n1, n2, n3, a, b --- and N
##                             (where N is an exponent used to control the
##                              extent of the edge-shading.)
##
##     - In '.fRleft.fRinfo':
##                          1 label widget to show some 'how-to' usage info.
##                          1 label widget to show the superformula --- and
##                            current super-formula parameter values.
##
##     - In '.fRright':     1 'canvas' widget 
##
##  3) Define bindings:
##
##       - a button1-release on each of the 7 scale widgets causes 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
##                           super-formula shape
##                           --- for the current values of the 7 scale
##                           parameters and the fill & background colors.
##
##     - set_scale_n3_to_n2_redraw  - to move n3 slider with n2 slider
##
##     - set_scale_b_to_a_redraw    - to move b slider with a slider
##
##     - 'set_shape_color1'   - shows a color selector GUI and uses the
##                                user-selected color to 'fill' the 
##                                super-formula shape on the canvas
##
##     - 'set_color_background' - shows a color selector GUI and uses the
##                                user-selected color to reset the color of
##                                the canvas background
##
##     - 'ReDraw_if_canvas_resized' - to do a redraw when a <Configure>
##                                    event is detected on the canvas ---
##                                    but only if the canvas has been resized.
##
##  5) Additional GUI initialization:  Execute proc 'ReDraw' once with
##                                     an initial, example set of parms
##                                     --- 7 acale vars, COLOR1hex,
##                                     COLORbkGNDhex ---
##                                     to start with a super-formula shape on
##                                     the canvas rather than a blank canvas.
##
##     Also in this section, we define a binding for
##       - a <Configure> (resize) event on the canvas --- to cause a redraw.
##+########################################################################
## DEVELOPED WITH:
##   Tcl-Tk 8.5 on Ubuntu 9.10 (2009-october release, 'Karmic Koala').
##
##   $ wish
##   % puts "$tcl_version $tk_version"
##                                  showed   8.5 8.5   on Ubuntu 9.10
##    after Tcl-Tk 8.4 was replaced by 8.5 --- to get anti-aliased fonts.
##+#######################################################################
## MAINTENANCE HISTORY:
## Created by: Blaise Montandon 2012oct18
## Changed by: ...... ......... 2012
##+#######################################################################

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

wm title    . "Edge-shaded 'Super-formula' shape, on a single-color canvas"
wm iconname . "Superformula"

wm geometry . +15+30


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

tk_setPalette "#e0e0e0"

## Initialize the super-formula shape 'fill' color.

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

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


## Initialize the background color for the canvas.

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

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


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

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

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

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


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

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

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



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

set initCanWidthPx 400
set initCanHeightPx 300
set minCanHeightPx 24

# set BDwidthPx_canvas 2
  set BDwidthPx_canvas 0


## BUTTON geom parameters:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2


## LABEL geom parameters:

set BDwidthPx_label 2


## SCALE geom parameters:

set BDwidthPx_scale 2
set initScaleLengthPx 300
set scaleWidthPx 10



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

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

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

set minWinWidthPx [expr 20 + $minWinWidthPx]


## MIN HEIGHT ---
## for the 9 sub-frames '.fRleft.fRbuttons', 7 '.fRleft.fRscaleX',
## and '.fRleft.fRinfo'.
## Allow
##    2 char   high for 'fRbuttons'
##    2 char   high for each of the 7 scale frames
##    6 char   high for 'fRinfo'

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr 10 * $CharHeightPx]

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

set minWinHeightPx [expr $minWinHeightPx + 64]


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

wm minsize . $minWinWidthPx $minWinHeightPx


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

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


##+################################################################
## DEFINE *ALL* THE FRAMES:
##
##   Top-level : '.fRleft'  '.fRright'
##
##   Sub-frames: '.fRleft.fRbuttons'
##                '.fRleft.fRscaleM' 
##                '.fRleft.fRscaleN1'
##                '.fRleft.fRscaleN2'
##                '.fRleft.fRscaleN3'
##                '.fRleft.fRscaleA'
##                '.fRleft.fRscaleB'
##                '.fRleft.fRscaleN'
##                '.fRleft.fRinfo'
##+################################################################

# set BDwidth_frame 2
# set RELIEF_frame raised

  set BDwidth_frame 0
  set RELIEF_frame flat

frame .fRleft    -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRright   -relief $RELIEF_frame  -borderwidth $BDwidth_frame


frame .fRleft.fRbuttons  -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRleft.fRscaleM   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRleft.fRscaleN1  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRleft.fRscaleN2  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRleft.fRscaleN3  -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRleft.fRscaleA   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRleft.fRscaleB   -relief $RELIEF_frame  -borderwidth $BDwidth_frame
frame .fRleft.fRscaleN   -relief $RELIEF_frame  -borderwidth $BDwidth_frame

frame .fRleft.fRinfo     -relief $RELIEF_frame  -borderwidth $BDwidth_frame


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

pack .fRleft \
   -side left \
   -anchor nw \
   -fill x \
   -expand 0

pack .fRright \
   -side left \
   -anchor nw \
   -fill both \
   -expand 1


##+##############################
## PACK the sub-FRAMES. 
##+##############################

pack .fRleft.fRbuttons \
     .fRleft.fRscaleM \
     .fRleft.fRscaleN1 \
     .fRleft.fRscaleN2 \
     .fRleft.fRscaleN3 \
     .fRleft.fRscaleA \
     .fRleft.fRscaleB \
     .fRleft.fRscaleN \
     .fRleft.fRinfo \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0


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


##+#####################################################################
## In the '.fRleft.fRbuttons' FRAME  ---  DEFINE-and-PACK
##    - an exit-button,
## and
##    - 2 buttons ( to specify colors)
## and
##   - a label widget, to show current color values (in hex)
##                     --- and if there is room, show the current
##                     values of the 6 scale parameters
##+#####################################################################

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

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

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

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


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

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

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

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

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

#     .fRleft.fRbuttons.buttHELP \

#     .fRleft.fRbuttons.buttCOLOR2 \


##+##################################################################
## In the '.fRleft.fRscaleM' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleM.label \
   -text "m" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleM.scaleM \
   -from 0 -to 34 \
   -resolution 2 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable m \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#    -command "ReDraw"

pack .fRleft.fRscaleM.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRleft.fRscaleM.scaleM \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+##################################################################
## In the '.fRleft.fRscaleN1' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleN1.label \
   -text "n1" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleN1.scaleN1 \
   -from 0.1 -to 17 \
   -resolution 0.1 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable n1 \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRleft.fRscaleN1.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRleft.fRscaleN1.scaleN1 \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+##################################################################
## In the '.fRleft.fRscaleN2' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleN2.label \
   -text "n2" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleN2.scaleN2 \
   -from 0 -to 17 \
   -resolution 0.1 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable n2 \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRleft.fRscaleN2.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRleft.fRscaleN2.scaleN2 \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+##################################################################
## In the '.fRleft.fRscaleN3' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleN3.label \
   -text "n3" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleN3.scaleN3 \
   -from 0 -to 17 \
   -resolution 0.1 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable n3 \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRleft.fRscaleN3.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRleft.fRscaleN3.scaleN3 \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+##################################################################
## In the '.fRleft.fRscaleA' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleA.label \
   -text "a" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleA.scaleA \
   -from 0.1 -to 1000 \
   -resolution 0.1 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable a \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRleft.fRscaleA.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRleft.fRscaleA.scaleA \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+##################################################################
## In the '.fRleft.fRscaleB' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleB.label \
   -text "b" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleB.scaleB \
   -from 0.1 -to 1000 \
   -resolution 0.1 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable b \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRleft.fRscaleB.label \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRleft.fRscaleB.scaleB \
   -side left \
   -anchor w \
   -fill x \
   -expand 1


##+##################################################################
## In the '.fRleft.fRscaleN' FRAME ----  DEFINE-and-PACK 
## 1 LABEL and 1 SCALE widget.
##+###################################################################

label .fRleft.fRscaleN.label \
   -text "N" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_label

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

scale .fRleft.fRscaleN.scaleN \
   -from 1 -to 30 \
   -resolution 1 \
   -length $initScaleLengthPx \
   -font fontTEMP_SMALL_varwidth \
   -variable N \
   -showvalue true \
   -orient hor \
   -bd $BDwidthPx_scale \
   -width $scaleWidthPx

#   -command "ReDraw"

pack .fRleft.fRscaleN.label \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

pack .fRleft.fRscaleN.scaleN \
      -side left \
      -anchor w \
      -fill x \
      -expand 1


##+######################################################
## DEFINE-and-PACK a 'label' widget
## in the '.fRleft.fRinfo' FRAME
##+######################################################


label .fRleft.fRinfo.labelFORMULA \
   -text "\
Click on the top four troughs to step through changes.
 Drag the slider of the bottom 3 scales to see significant change.

Super-formula:
R(theta) = ( |cos(m x theta/4) / a| ^ n2 + |sin(m x  theta/4) / b| ^ n3 ) ^ -1/n1
and the x,y coordinates on the super-formula shape curve are given by
R x cos(theta) & R x sin(theta).   N is an exponent; controls extent of shading." \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

label .fRleft.fRinfo.labelPROCESSING \
   -text "" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button


## The text for labelPROCESSING is set in the ReDraw proc.
## It is used to indicate when calculations are in progress,
## as well as to show the draw-time (elapsed) when
## calculation and redrawing is done.

pack .fRleft.fRinfo.labelFORMULA \
     .fRleft.fRinfo.labelPROCESSING \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0


##+######################################################
## DEFINE-and-PACK the 'canvas' widget
## in the '.fRright' FRAME
##+######################################################
## 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 .fRright.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0

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


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


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

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

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

bind .fRleft.fRscaleM.scaleM    <ButtonRelease-1> "ReDraw 0"
bind .fRleft.fRscaleN1.scaleN1  <ButtonRelease-1> "ReDraw 0"
bind .fRleft.fRscaleN2.scaleN2  <ButtonRelease-1> "set_scale_n3_to_n2_redraw 0"
bind .fRleft.fRscaleN3.scaleN3  <ButtonRelease-1> "ReDraw 0"
bind .fRleft.fRscaleA.scaleA    <ButtonRelease-1> "set_scale_b_to_a_redraw 0"
bind .fRleft.fRscaleB.scaleB    <ButtonRelease-1> "ReDraw 0"
bind .fRleft.fRscaleN.scaleN    <ButtonRelease-1> "ReDraw 0"


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


##+#####################################################################
## proc ReDraw -
##
## PURPOSE:
##     Draws the super-formula shape on the canvas.
##
##     Because we cannot count on any symmetry in the 'super-formula 
##     shape', this proc does NOT the draw a horizontal scan-line at a time,
##     with calls like: 
##           imgID put $hexcolorsLIST -to 0 $y
##     where 0 $y is the leftmost position of a horizontal line of
##     hexcolors for the pixels.
##
##     Instead, we 'poke' a hex-color value into each pixel with calls
##     like:
##           imgID put $hexcolor -to $x $y
##
## CALLED BY:  bindings (in the BINDINGS section) --- and by set-color procs
##             --- and by the GUI initialization section at the bottom of
##             this script.
##
## NOTE: The 'x' argument is to avoid an error when the scale '-command'
##       passes a scale value as an argument to the command. This is in
##       case we try using the '-command' option of the scale widgets to
##       do the redraws 'dynamically' as a sliderbar is moved.
##+#####################################################################
## For reference in the math statements below, the 'superformula' is:
## r(theta) =
##    ( |cos(m*theta/4)/a| ^ n2 + |sin(m*theta/4)/b| ^ n3 ) ^ -1/n1
##+#####################################################################

## Set the value of pi and 2-times-pi JUST ONCE.
set pi [expr {4.0 * atan(1.0)}]
set twopi [expr {2 * $pi}]

proc ReDraw {x} {

   global m n1 n2 n3 a b N \
         pi twopi \
         COLOR1r COLOR1g COLOR1b COLOR1hex \
         COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex

  ## COLOR2r COLOR2g COLOR2b COLOR2hex \

   set denomTOL 0.00001

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

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

   ## We could put the PROCESSING info at the bottom of the GUI,
   ## but that is a little too out-of-sight.
   #  .fRleft.fRinfo.labelPROCESSING configure -text "\
   # ** CALCULATIONS IN PROGRESS **"

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

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


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

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

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

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

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


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

   #########################################################################
   ## HERE IS THE 'GUTS':
   ## In a loop over yPx and xPx, where yPx and xPx are measured from the
   ## top left of the canvas/image rectangle, we calculate the hex-color
   ## for each pixel, from x = xPx - xmidPx and y = yPx - ymidPx,
   ## according to our 'color-metric':
   ##
   ##          v = r(x,y) / R(theta)
   ##
   ## where  r(x,y) = sqrt( x^2 + y^2 )
   ## and
   ## where 'theta' is the angle whose cosine is x/r --- and sine is y/r.
   ## OR ... 'theta' can be determined from the ratio y/r and the quadrant
   ## in which the point x,y lies.
   ##
   ## R(theta) is given by
   ##  ( |cos(m*theta/4)/a| ^ n2 + |sin(m*theta/4)/b| ^ n3 ) ^ -1/n1
   ##
   ## For each x,y, we calculate each theta between 0 and 2*pi radians.
   #######################################################################

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

         set x [expr {$xPx - $xmidPx}]
         set y [expr {$yPx - $ymidPx}]
         set r [expr {sqrt( ($x * $x)  + ($y * $y) )}]

         ## Determine 'theta'.
         ##
         ## First, we disregard the sign of $y and get theta
         ## as if x,y were in the first quadrant.
         ## So theta should be between 0 and pi/2.
         ##
         ## Ideally, we could simply use
         ##     set theta [expr {asin( abs($y) / $r )}]
         ## but we need to handle the case when $r is zero or very near. AND
         ## we do some checking to try to make sure asin does not throw an error
         ## say do to some precision errors in computing $yratio.)

         if { $r < $denomTOL } {
            set yratio 0.0
         } else {
            set yratio [expr {abs($y) / $r}]
         }

         if {$yratio > 1.0} {set yratio 1.0}
         set theta [expr {asin( $yratio )}]

         ## Adjust theta if x,y is in the 2nd quadrant.
         if { $x < 0.0 && $y >= 0.0 } {
            set theta [expr {$pi - $theta}]
         }

         ## Adjust theta if x,y is in the 3rd quadrant.
         if { $x <= 0.0 && $y < 0.0 } {
            set theta [expr {$pi + $theta}]
         }

         ## Adjust theta if x,y is in the 4th quadrant.
         if { $x > 0.0 && $y < 0.0 } {
            set theta [expr {$twopi - $theta}]
         }
 
         ## FOR TESTING:
         # if { $x == 20 && $y == 100 } {
         #    puts "ReDraw >   x: $x  y: $y   r: $r   theta: $theta"
         # }

         ## For this 'theta', calculate R --- piece by piece. Recall:
         ##  ( |cos(m*theta/4)/a| ^ n2 + |sin(m*theta/4)/b| ^ n3 ) ^ -1/n1

         set t  [expr {$m*$theta/4.0}]

         set term1 [expr {abs(cos($t)/$a)}]
         set term1 [expr {pow($term1,$n2)}]
         set term2 [expr {abs(sin($t)/$b)}]
         set term2 [expr {pow($term2,$n3)}]
 
         set R [expr {pow($term1+$term2,1.0/$n1)}]

         ## This statement essentially applies the
         ## minus sign of the exponent -1/n1.
         set R [expr {1.0/$R}]

         ## FOR TESTING:
         # if { $x == 20 && $y == 100 } {
         #    puts "ReDraw >   x: $x  y: $y     R: $R     t: $t"
         #    puts "           m: $m   n1: $n1   n2: $n2   n3: $n3   a: $a   b: $b"
         #    puts "           term1: $term1  term2: $term2"
         # }

         ## Calculate our 'color-metric':  v = r / R.

         set v [ expr {$r / $R}]

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

         if {$v > 1.0} {
            imgID put $COLORbkGNDhex -to $xPx $yPx
         } else {
            ## We should be inside or on the 'super-formula shape'.
            set vpow [expr {pow($v,$N)}]
            set oneMinusVpow [expr {1.0 - $vpow}]
            set R [expr {int(($vpow * $COLORbkGNDr) + ($oneMinusVpow * $COLOR1r))}]
            set G [expr {int(($vpow * $COLORbkGNDg) + ($oneMinusVpow * $COLOR1g))}]
            set B [expr {int(($vpow * $COLORbkGNDb) + ($oneMinusVpow * $COLOR1b))}]
            if {$R > 255} {set R 255}
            if {$G > 255} {set G 255}
            if {$B > 255} {set B 255}
            if {$R < 0} {set R 0}
            if {$G < 0} {set G 0}
            if {$B < 0} {set B 0}
            set hexcolor [format "#%02X%02X%02X" $R $G $B]

            ## Put the color at $xPx $yPx.
            imgID put $hexcolor -to $xPx $yPx
         }
         ## END OF  if {$v > 1.0} 
      }
      ## END OF   for {set xPx 0} {$xPx < $imgWidthPx} {incr xPx} 
   }
   ## END OF  for {set yPx 0} {$yPx < $imgHeightPx} {incr yPx}
   
   ## Make sure the text on the COLORS and PROCESSING label widgets
   ## is up to date.

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

   update

   ## We could put the PROCESSING info at the bottom of the GUI,
   ## but that is a little too out-of-sight.
   #  .fRleft.fRinfo.labelPROCESSING configure -text "\
   # DRAW TIME: [expr {[clock milliseconds] - $t0}] millisecs elapsed"

   ## We do not need to show the following. The user should be able to
   ## see the current parm values above each scale widget.
   ##
   # Current super-formula shape parameters:
   #  m = $m ; n1 = $n1 ; n2 = $n2 ; n3 = $n3 ; a = $a ; b = $b

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

}
## END OF proc 'ReDraw'


##+##########################################
## proc set_scale_n3_to_n2_redraw
##+##########################################

proc set_scale_n3_to_n2_redraw {x} {
   global n2 n3
   set n3 $n2
   ReDraw 0
}
## END OF proc 'set_scale_n3_to_n2_redraw'


##+##########################################
## proc set_scale_b_to_a_redraw
##+##########################################

proc set_scale_b_to_a_redraw {x} {
   global a b
   set b $a
   ReDraw 0
}
## END OF proc 'set_scale_b_to_a_redraw'


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

proc set_shape_color1 {} {

   global COLOR1r COLOR1g COLOR1b COLOR1hex
   # global feDIR_tkguis

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

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

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

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

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

   ## Redraw the geometry in the new interior color.

   ReDraw 0

}
## END OF proc 'set_shape_color1'


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

proc set_shape_color2 {} {

   global COLOR2r COLOR2g COLOR2b COLOR2hex
   # global feDIR_tkguis

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

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

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

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

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

   ## Redraw the geometry in the new edge color.

   ReDraw 0

}
## END OF proc 'set_shape_color2'


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

proc set_background_color {} {

   global COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex
   # global feDIR_tkguis

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

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

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

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

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

   ## Redraw the geometry in the new background color.

   ReDraw 0


}
## END OF proc 'set_background_color'


##+#############################################################
## proc ReDraw_if_canvas_resized
##
## CALLED BY: bind .fRright.can <Configure> 
##            at bottom of this script.
##+#############################################################

proc ReDraw_if_canvas_resized {} {
   global  PREVcanWidthPx PREVcanHeightPx

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

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

      ReDraw 0
      set PREVcanWidthPx  $CURcanWidthPx
      set PREVcanHeightPx $CURcanHeightPx
   }

}
## END OF ReDraw_if_canvas_resized


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

## Set initial scale widget variables.
set m 12
set n1 6.0
set n2 6.0
set n3 6.0
set a 150.0
set b 150.0
set N 12

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

update
ReDraw 0


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

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


By making parameter 'pairs' like a and b (or n2 and n3) different, you can get some effects like putting some 'indentations' in the initial disk-like shape --- giving a star-fish-like shape:

superformula_shaded_starfish6legs_redONblack_885x524.jpg

And by adjusting the exponent m to a high value, you can add a lot of 'protrusions' to the initial disk-like shape --- giving a sea-urchin-like shape:

superformula_shadedEdge_disk34points_cyanOnblack_882x522.jpg

(This image seems to have a glowing, optical-illusion type of effect.)

The display of the elapsed time for the draw is seen at the bottom of the GUI. I later changed that display to the top of the GUI --- so that it is more noticeable, even if the user shrinks the window size so that the bottom of the GUI is no longer showing. (See the image at the top of this page.)

I found that with any parameter combinations that I tried, with the initial default window size, the draw times were between 2.8 and 3.2 seconds.

And when I increased the window to my full monitor size (1024x768), the draw times were still less than 5.1 seconds.

So, in a quite reasonable amount of time, with this utility, you can get some nice high-quality images --- to use for 'buttons', 'bullets', icon-backgrounds, and logo-backgrounds.

And I have the option of enhancing the Tk script on this page, to provide a few more capabilities to this 'button' generator. That is a really great thing about Tk scripts. You have the code, hence you can change the utility to do what you want or need.

______

On the subject of trying to get really nice shading at the border of the 'superformula shape':

I put the N-scale widget on the GUI to adjust a 'pow' function exponent to change the 'extensity' of the shading at the edge of the 'superformula shape'.

You will find that low powers (like 1 or 4 or even 6) give a really 'washed-out' border to the shape. It takes a rather high value (like 12) to get a good-looking edge.

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