GUI for Drawing 'Super-ellipses', with nice shaded edges

uniquename - 2012oct01

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 color options and other controls, I presented code for a GUI that uses 'create line' on a canvas to implement the drawing of a 'super-ellipse' with quite a bit of user-control over size and shape of the super-ellipse and over color --- both of the super-ellipse and the background.

That page was based on the Mathematics jewels page of ulis, where he pointed out that 'super-ellipses' are given by the following equation, which is a generalization of the equation for an ellipse.

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

Some interesting applications of the superellipse when n=2.5 are seen in images collected on the page GUI for Drawing 'Super-ellipses', with color options and other controls.

---

Unfortunately, the 'create line' technique on that page was plagued by 'jaggies' --- and in some experimenting with 'Gaussian blur' of various (high) degrees, I have found that trying to get rid of the jaggies by using 'blur' is not very satisfactory.

To totally remove the jaggies, the blur needs to be of high-degree --- but that tends to spread out the edge of the superellipse, rather than blurring it to a 'crisp' edge.

I noticed that the 'image create' technique that 'ulis' used seemed to give pretty crisp edges. But there was obviously little control on the 'demo' GUI of 'ulis' --- not even a button. He was using a single background color (white) and he effectively was adding shading to the edge of the superellipse with one color (black) --- and the superellipse color was set at a single color.

I wanted to keep the color, exponent, and size controls of my superellipse 'create line' GUI --- but I needed to change the 'ReDraw' proc to use an 'image create' technique like 'ulis' used.

Unfortunately, the code of the 'jewel' proc that 'ulis' used was not documented clearly --- and 'ulis' was prone to use some programming techniques that are rather 'unusual' to say the least --- such as his 'double-dollar-sign' method --- as in $($c). In fact, on a couple of his pages of this wiki, some people have pointed out that his techniques were prone to lead to readability problems, if not problems with failures in various versions of Tcl-Tk.

In the 'ulis' demo script, he drew the superellipses by creating 'photo' images and filling in the shape with 'put' commands. So I set about to use that method, but starting from a 'clean page'.

I have to admit that my first attempts needed about 4 seconds to draw the superellipse, whereas the 'ulis' demo came up in a fraction of a second.

I found that even though I had tried to make sure that I was not doing too much unnecessary math in a double-loop over x and y, I was still getting slow draws. I even tried reducing the number of 'put' commands to one for each scan-line.

I finally found that most of the problem was that I should be using braces with 'expr' statements --- as Welch et. al. point out by page 7 in the 4th edition of 'Practical Programming in Tcl and Tk'. (Doh and double-doh.)

So, finally, I was able to do a drawing of a superellipse, like that in the following GUI image, within a second --- and I was getting better shading at the edge of the superellipse than I got using the 'create line' technique combined with anti-aliasing via 'blur' in an image editor.

superellipse_n6_colorShaded_magentaOnBlack_602x439.jpg

Note that I have supplied 2 buttons on the GUI with which to set the superellipse '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.

I used a 'minilistbox' widget to provide the choice of exponents. I have used that 'minilistbox' widget in the script at A two-color rounded-POLYGON-maker GUI (equilateral and not so equilateral) and in GUI for Drawing 'Gradient Spheres' (lighted disks), with lots of control.

As I mentioned on those pages, I got the technique for making a new widget, from existing, built-in Tk widgets, from the 'spinner' widget of Richard Suchenwirth that he presented on the spinbox page. As he described it, the 'spinner' is "a concoction of a 1-line high listbox with two tiny buttons, to approximate the effects of a spinbox.".

My 'minlistbox' widget is more like a 3-line listbox, and I had to enhance Suchenwirth's demo code with font variables and width variables and other parameters.

As I mentioned on the A two-color rounded-POLYGON-maker GUI (equilateral and not so equilateral) page, I may extract the 'minilistbox' code someday and put it in a simpler demo script, like Suchenwirth did with his 'spinner' proc. For now, people who want to use this 'minilistbox' --- or make a similar widget --- can extract the proc and the calling code from this much larger script.

_____________________________________________________________________

Below is the code that produced this GUI.

There are comments above the sample 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 or mouse 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).

To make the 'minilistbox' widget to be used in step (2), I essentially inserted a new step:

1c) Define any procs to be used in making widgets.

_________________________________________________________________

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

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

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

If anyone wants to change the way the GUI configures itself as the main window size is changed, they can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various widgets --- to get the widget behavior that they want. For example, you may want the checkbutton widget to stay in the middle of the window rather than being on the left of the window, near the 'minilistbox' widget.

Furthermore, there are variables used to set geometry parameters of widgets --- parameters such as border-widths and padding. Feel free to experiment with those parameters as well.

_____________________________________________________________________

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

I used a 'create image' technique --- with 'put' commands --- somewhat like in the 'jewel' proc of 'ulis' --- I think. Since I found it difficult to decipher what his several loops were intended to do, I devised a quite different superellipse drawing proc that I called 'ReDraw'.

When I finally worked all the bugs out and got a first SHADED superellipse image up, I was pleased to note that I had indeed avoided the 'jaggies' that I was getting with the 'create line' method. Furthermore, I was getting a 'crisper' edge than I was getting from applying high-levels of 'Gaussian blur' to the images from the 'create line' technique, where high-levels were needed to get rid of almost all traces of the jaggies.

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 too cryptic (as cryptic as the 'ulis' superellipse code was, and still is, to me). Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads --- to watch videos of people doing things they should not try at home.


 Code of the Tk script 'draw_superEllipse_colorShaded_onColorBkgnd.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: draw_superEllipse_colorShaded_onColorBkgnd.tk
##
## 
## PURPOSE:  This Tk GUI script facilitates the creation of a color-filled
##           'super-ellipse' --- via a Tk image 'structure' put on a canvas
##           widget via a canvas 'image create' command --- and via
##           'put <hexcolor(s)-list> -to $x $y' commands on the image.
##
##           Outside the 'super-ellipse' on the canvas-filling image,
##           a user-selected 'background' color is applied.
##
##           The superellipse is drawn with color-shading toward the
##           edges of the superellipse. The user is given the option of
##           turning the color shading off, via a checkbutton.
##
##           This Tk script is based on a Tk script at the web page
##           called 'Mathematics jewels' at https://wiki.tcl-lang.org/10414 ---
##           by 'ulis' in 2003 November.  On that page, 'ulis' points
##           out that the equation that describes a 'super-ellipse' is:
##
##              |x/a|^n + |y/b|^n = 1
##
##           And 'ulis' pointed out that:
##
##    * With n = 1 you obtain a rhombus (diamond shape).
##    * With n = 2 you already got an ellipse.
##    * With n > 2 you obtain a rounded rectangle! The more the power,
##                                                 the more the rectangle.
##    * With n = 2/3 you obtain an 'astroid'.
##
##+############
## A 3D EFFECT:
##    'ulis' gave his drawings of the filled super-ellipse a shaded, 3D effect.
##     As he explained it:
##
##           The 3D effect is fairly simple to obtain:
##
##           Just compute the value  
##                     v = |x/a|^n + |y/b|^n
##           for each point inside the shape.
##
##           By definition the value of v is 1 on the border and declines
##           to 0 towards the center.
##
##           Computing 255 * (1.0 - $v) [ applied to the superellipse color
##           --- and 255 * $v  applied to the background color --- ] gives us
##           the color component of the 3D effect.
##
##     'ulis' was effectively using black as the 'background color'.
##
##+#######
## METHOD:   The GUI made by this Tk script contains a rectangular
##           canvas widget on which the color-filled super-ellipse will be
##           drawn.
##
##    Note that the values of x,y such that 
##         |x/a|^n + |y/b|^n  is less-than-or-equal-to 1
##    are the values that fill the interior of the super-ellipse.
##
##    Furthermore, note that for all points in the interior of the superellipse
##        x is between -a and a      and       y is between -b and b.
##
##    For the purpose of drawing the image on the canvas,
##    we let x, y, -a, a, -b, and b be given in pixels (integers) rather than 
##    floating point numbers.
##
##    To do the drawing on the canvas, we use the 'image create' command to
##    put an image on the entire canvas.
##    
##    We put the top and bottom of the background on the canvas by using
##           'put <background-hexcolor> -to 0 $y'
##    commands on the image. This technique counts on the 'put'
##    command to 'tile' the background color completely across the image.
##
##    We fill the interior of the super-ellipse (and the background color
##    to the left and right of the superellipse) by using
##           'put <hexcolor(s)-list> -to $x $y'
##    commands on the image where each hexcolor in the 'scanline list' is 
##    a weighted mixture of 2 user-selected colors --- the background
##    color and the super-ellipse color.
##
##    The weighting is achieved by applying 'v' and '1.0 - v' to the RGB
##    components of the 2 colors, where 'v' is given by
##                  v = |x/a|^n + |y/b|^n
##    Actually we use a power of v to get 'crisper' edge shading.
##
##    We can use the symmetry of the superellipse to allow us to do the
##    calculations of colors for the pixels in just ONE quadrant ---
##    the upper-right quadrant. That gives us the right-half of an
##    upper scan line.
##
##    We build the left-half of the upper scan line at the same time
##    as we build the right-half of the scan line.
##    And we use the upper scan line to make a lower scan line.
##    We use one 'put' command to draw each scan line.
##
##    The GUI includes a 'minilistbox' widget that displays a list
##    of useful options for the exponent 'n' --- such as
##    0.5, 0.667, 0.8, 1, 2, 2.5, 3, 4, 5, 6, 8, 10, 12, ...
##
##    There is a binding to the 'minilistbox' widget that 
##    is used to call the redraw proc whenever a new value of the
##    exponent 'n' is chosen.
##
##    We set the value of a and b to be about 80% of
##    half the current width and height of the canvas --- and
##    change the size of the super-ellipse by changing the size
##    of the canvas (by resizing the window) --- with a redraw
##    being done whenever the window is resized. (We take this
##    approach, for now, to help simplify the GUI.)
##
##           The GUI could include 2 'scale' widgets whose slider-bars can
##           be used to change the values of a and b --- to a max of
##           half the current width and height of the canvas widget, say.
##
##    The GUI also includes a button to call a color selector GUI
##    to set the 'fill' color of the super-ellipse.
##
##    Another button calls the same color selector GUI to set a
##    'background' color --- effectively, the color of the canvas.
##
##    There is a checkbox on the GUI to choose whether to use the
##    'v/1-v' shading --- or to simply put the user-selected superellipse color
##    at each pixel where |x/a|^n + |y/b|^n is less than or equal to 1.
##
##    The redraw includes clearing the canvas (deleting the image),
##    recreating the image structure according to the current canvas size,
##    and redrawing (re-filling) the image with 'put <hexcolor(s)> -to' commands.
##
##             (In the case that we use scale widgets to set a & b:
##              If the redraw takes more than half-a-second, then we can use
##              a button1-release binding on the scale widgets for a and b
##              to trigger the redraw --- only when the user finishes dragging
##              the sliderbar of either scale.
##
##              If erasing the canvas and redrawing the super-ellipse
##              completes within a very small fraction of a second, it will
##              be feasible to do the redraws 'dynamically' with the sliderbar.)
##
##+#########################
## 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 image file.
##
##           The editor could also be used to blur the image slightly to
##           'feather' the edges of the superellipse --- for example,
##           if the 'v/1-v' shading is not used.
##
##           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 for use in Tk GUIs and web pages.
##
##           The image could also be taken into a scalable vector graphics
##           (SVG) editor (like Inkscape on Linux) and the SVG editor used
##           to add anti-aliased text to the image. 
##
##+########################################################################
## '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.
##  1b) Pack   ALL frames and sub-frames.
##
##  2) Define all widgets in the frames --- typically going through the
##     frames top-to-bottom and/or left-to-right --- as well as defining the
##     widgets within a frame top-to-bottom and/or left-to-right.
##     Pack the widgets after each frame has its widgets defined.
##
##  3) Define keyboard or mouse action BINDINGS, if needed.
##
##  4) Define PROCS, if needed.
##
##  5) Additional GUI INITIALIZATION (with procs), if needed.
##
##
## Some detail about the code structure of this particular script:
##
##  1a) Define ALL frames:
## 
##      Top-level :  '.fRbuttons' , '.fRimgspecs' , '.fRcan'
##
##      Sub-frames: none
##
##  1b) Pack ALL frames.
##
##  1c) Define a 'minilistbox' proc that is used to make a
##      COMPACT LIST-SELECTION WIDGET for use in step 2 below --- to serve
##      in place of the old-fashioned 'tk_optionMenu' widget, and yet
##      to avoid using a newer widget like 'spinbox' that is
##      not available to users of older 8.x wish interpreters
##      or the really-old 7.x interpreters.
##
##  2) Define all widgets in the frames (and pack them):
##
##     - In '.fRbuttons':   1 button widget ('Exit'),
##                            and
##                          2 buttons (for setting the super-ellipse color
##                                     and the background/canvas color),
##                            and
##                          1 label widget to display current color values
##
##     - In '.fRimgspecs': 1 'minilistbox' widget to specify the exponent 'n'
##                            for a super-ellipse --- from a list such as
##                            0.5, 0.667, 0.8, 1, 2, 2.5, 3, 4, 5, 6, 8, 10, 12.
##                         and
##                         1 checkbox widget, to specify shading (or not)
##                         and
##                         1 label widget to display current super-ellipse
##                                  parameter values such as n, a, and b.
##
##     - In '.fRcan':       1 'canvas' widget 
##
##  3) Define bindings:
##
##       - button1-release on a shading checkbutton widget
##
##       Note: A <Configure> (in particular, a canvas-resize) event on the window
##             should cause a redraw.
##
##  4) Define procs:
##
##     - 'ReDraw'          - to clear the canvas/image and redraw the
##                           background color and the super-ellipse
##                           --- for the current values of a, b, n, and
##                           the 2 colors.
##
##     - 'set_ellipse_color1'   - shows a color selector GUI and uses the
##                                user-selected ellipse-color to 'fill' the 
##                                super-ellipse on the canvas.
##
##     - 'set_ellipse_color2'   - (NOT USED, yet)
##                                If we decide to implement a color for
##                                the edge of the superellipse (when
##                                creating the 3D image effect) different
##                                from the background color, this proc would
##                                show a color selector GUI and use the
##                                user-selected color to redraw the
##                                shaded super-ellipse on the canvas.
##
##     - 'set_color_background' - shows a color selector GUI and uses the
##                                user-selected color as the color of
##                                the canvas/image background --- and for
##                                the shading at the edge of the superellipse.
##
##  5) Additional GUI initialization:  Execute proc 'ReDraw' once with
##                                     an initial, example set of parms
##                                     --- a, b, n, COLOR1hex,
##                                     COLORbkGNDhex ---
##                                     to start with a super-ellipse on
##                                     the canvas rather than a blank canvas.
## 
##+########################################################################
## 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 2012sep28
## Changed by: ...... ......... 2012oct01 Put braces in 'expr' commands of
##                                        the 'ReDraw' proc to speed up
##                                        execution.
##+#######################################################################

##+#######################################################################
## Set general window parms (title,position,size,color-scheme,fonts,etc.).
##+#######################################################################

wm title    . "Color-Shaded 'Super-Ellipse' on a single-color Canvas"
wm iconname . "SuperEllipse"

wm geometry . +15+30


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

tk_setPalette "#e0e0e0"

## Initialize the super-ellipse color (or 2 gradient colors)
## and the background color for the canvas.

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

## Activate this if we implement allowing the user to choose a
## superellipse 'edge color' different from the background color
## --- by adding a 3rd color-selector button to the GUI.
if { 1 == 0 } {
# set COLOR2r 255
# set COLOR2g 255
# set COLOR2b 0
set COLOR2r 255
set COLOR2g 255
set COLOR2b 255
set COLOR2hex [format "#%02X%02X%02X" $COLOR2r $COLOR2g $COLOR2b]
}

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


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


## LISTBOX geom parameters:

set listboxWIDTHchars 3


##+###################################################################
## Set a MINSIZE of the window (roughly).
##
## For width, allow for the minwidth of the '.fRbuttons' frame:
##            about 3 buttons (Exit,Color1,ColorBkgnd), and
##            a label with current current color values info.
##
## For height, allow for a canvas at least 24 pixels high, and
##             3 small-chars high for the 'minilistbox' height in the
##             '.fRimgspecs' frame, and
##             2 chars high for the widgets in the '.fRbuttons' frame.
##+###################################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
   "Exit Super-ellipse Background  Colors:  Fill - #FF00FF  Background - #000000"]

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

set minWinWidthPx [expr 40 + $minWinWidthPx]


## MIN HEIGHT ---
## for the 3 frames 'fRbuttons'  'fRimgspecs'  'fRcan'.
## Allow
##    2 'regular-sized' chars  high for 'fRbuttons'
##    2 'regular-sized' chars  high for 'fRimgspecs'
##   24 pixels high for 'fRcan'

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr 24 + 4 * $CharHeightPx]

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

set minWinHeightPx [expr $minWinHeightPx + 52]


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

wm minsize . $minWinWidthPx $minWinHeightPx


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

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


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

# 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 .fRimgspecs  -relief raised         -borderwidth 2

frame .fRcan       -relief $RELIEF_frame  -borderwidth $BDwidth_frame


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

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

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


##+#########################
## DEFINE PROC 'minilistbox'
## (for use in making a couple of widgets below)
##+##############################################################
## By using the global variables
##          - fontTEMP_SMALL_fixedwidth 
##          - fontTEMP_SMALL_varwidth
##          - listboxBKGD
## for the decorative & geometric elements/parameters of the GUI,
## we keep the arguments of this widget-made-on-the-fly down
## to the 6 MAIN ELEMENTS/VARIABLES --- 4 INPUT AND 1 OUTPUT AND 1 CMD:
##
## - the parent widget/window,
##
## - an option/line at which to initially position the list in
##   the listbox (with the 'see' command),
##
## - an options list,
##
## - width (in chars) of the listbox
##
## - the name of the variable that is to hold the user-selected option,
##   i.e. a list-line (the result/output)
##   --- retrieved from the listbox with 'curselection' and 'get',
##
## - a command (proc --- and parameters, if any) to be executed at a
##   button1-release on this widget's frame.        
##+##############################################################

proc minilistbox {w opt1 optslist listboxWIDTHchars seloptvar mlbProc} {

   global fontTEMP_SMALL_fixedwidth fontTEMP_SMALL_varwidth \
          listboxBKGD

   ##+#####################################
   ## DEFINE-and-PACK the widget SUB-FRAMES:
   ## '.fRup-down' for 2 up and down buttons
   ## and '.fRopts' for the listbox.
   ## Pack them side by side.
   ##+#####################################

   frame $w.fRup-down -relief flat -bd 2
   frame $w.fRopts    -relief flat -bd 2

   pack $w.fRup-down \
        $w.fRopts \
      -side left \
      -anchor w \
      -fill y \
      -expand 0


   ##+####################################################
   ## In FRAME '.fRup-down',
   ## DEFINE-and-PACK a top-spacer label and 2 buttons.
   ##+####################################################

   ## We comment-out this label definition (and its pack statement)
   ## to reduce the height of this 'minilistbox' widget.
   ## See the label definition statement for frame .fRopts, below.
   # label $w.fRup-down.label \
   #    -text " " \
   #    -anchor w \
   #    -relief flat

   button $w.fRup-down.buttUP \
      -text "Up" \
      -font fontTEMP_SMALL_varwidth \
      -width 3 -height 1 \
      -pady 1 \
      -padx 0 \
      -command [list $w.fRopts.listbox yview scroll -1 unit]

   button $w.fRup-down.buttDOWN \
      -text "Dn" \
      -width 3  -height 1 \
      -font fontTEMP_SMALL_varwidth \
      -pady 1 \
      -padx 0 \
      -command [list $w.fRopts.listbox yview scroll +1 unit]

   # pack $w.fRup-down.label \
   #   -side top \
   #   -anchor n \
   #   -fill none \
   #   -expand 0

   pack $w.fRup-down.buttUP \
        $w.fRup-down.buttDOWN \
      -side top \
      -anchor n \
      -fill none \
      -expand 0


   ##+####################################################
   ## In FRAME '.fRopts',
   ## DEFINE-and-PACK an info label and a listbox widget.
   ##+####################################################

   ## We comment-out this label definition (and its pack statement)
   ## to reduce the height of this 'minilistbox' widget.
   ## The user could supply a label, say to the left of this
   ## 'minilistbox' widget, using a label-def in their Tk script.
   # label $w.fRopts.label \
   #   -text "Up/dwn ; click a line:" \
   #   -font fontTEMP_SMALL_varwidth \
   #   -anchor w \
   #   -relief flat

   listbox $w.fRopts.listbox \
      -font fontTEMP_SMALL_fixedwidth \
      -height 3 \
      -width $listboxWIDTHchars \
      -bg "$listboxBKGD" \
      -state normal

   foreach optline $optslist {
      $w.fRopts.listbox insert end $optline
   }

   # pack $w.fRopts.label \
   #   -side top \
   #   -anchor n \
   #   -fill x \
   #   -expand 0

   pack $w.fRopts.listbox \
      -side top \
      -anchor n \
      -fill x \
      -expand 0


   ##+###################################################
   ## POSITION the list at the 'opt1' line, using 'see'.
   ##   And make the opt1 line the default selection. (?)
   ##+###################################################

   set INDEXofOPT1 [ lsearch -exact $optslist $opt1 ]
   
   if { "$INDEXofOPT1" != "-1" } {

      set seeINDEX [expr $INDEXofOPT1 - 1 ]
      if { "$seeINDEX" < "0" } { 
         set seeINDEX "0"
      }

      $w.fRopts.listbox see $seeINDEX

      ## Comment this to de-activate it?
      $w.fRopts.listbox selection set $INDEXofOPT1

   }
   ## END OF if { "$INDEXofOPT1" != "-1" }


   ##+########################################################
   ## PROC for the following button1-release BINDING:  getline
   ##+########################################################

   proc getline {w outvar passedproc} {

      ## This 'upvar' associates the local var 'selectline' with
      ## the outer var that is to contain the listbox selection.
      ## It is like an EQUIVALENCE statement in a FORTRAN subroutine.
      upvar #0 $outvar selectline

      set sel_index [ $w.fRopts.listbox curselection ]

      ## FOR TESTING:
      #  puts "sel_index: $sel_index"

      if { $sel_index != "" } {
         set selectline [ $w.fRopts.listbox get $sel_index ]
      } else {
         set selectline ""
      }

      eval set $outvar "$selectline"

      ## FOR TESTING:
      #   puts "selectline: $selectline"
      ##   puts "EXPn: $EXPn"
      #   puts "outvar: [expr \$$outvar]"


      eval $passedproc
   }
   ## END OF proc getline


   ##+#####################################################
   ## SET BINDING on the listbox in this new-widget so that
   ##         <ButtonRelease-1> puts a selected line of the
   ##        listbox in a specified var and executes a
   ##        specified command/proc.
   ##+#####################################################

   bind  $w.fRopts.listbox <ButtonRelease-1> "getline $w $seloptvar \"$mlbProc\""

}
## END OF 'minlistbox' PROC



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


##+#####################################################################
## In the '.fRbuttons' FRAME  ---  DEFINE-and-PACK
##    - an exit-button,
## and
##    - 2 (or 3) buttons ( to specify colors)
## and
##   - a label widget, to show image parameters
##+#####################################################################

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

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

## Activate this if we implement allowing the user to choose a
## superellipse 'edge color' different from the background color
## --- by adding a 3rd color-selector button to the GUI.

if { 1 == 0} {
button .fRbuttons.buttCOLOR2 \
   -text "\
Gradient
 Color at edge" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief raised \
   -bd $BDwidthPx_button \
   -command "set_ellipse_color2"
}

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


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


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

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

#     .fRbuttons.buttCOLOR2 \


##+##################################################################
## In the '.fRimgspecs' FRAME ----  DEFINE-and-PACK 
##   - a LABEL widget for the 'minilistbox'
##   - a 'minilistbox' widget for exponent of the super-ellipse
##   - a CHECKBUTTON widget to turn on/off shading
##   - a LABEL widget to show current superellipse parms and info
##   - (perhaps someday) 2 LABEL & SCALE widgets, for the a & b parms
##+###################################################################

label .fRimgspecs.labelEXPn \
   -text "\
Exponent 'n' of
 the Super-ellipse :" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button


## DEFINE the 'minilistbox' widget for light-reflection location
## on the sphere/disk.

frame .fRimgspecs.fRexponent -relief flat -bd 0
set EXPopts { 0.5 0.667 0.8 1 2 2.5 3 4 5 6 8 10 12 14 16 18 20 22}
set EXPn 3
minilistbox .fRimgspecs.fRexponent $EXPn $EXPopts 6 EXPn "ReDraw 0"


set shade0or1 1

checkbutton .fRimgspecs.chkbuttSHADE \
   -text "\
Shaded edges on
 the super-ellipse" \
   -font  fontTEMP_varwidth \
   -variable shade0or1 \
   -selectcolor "#cccccc" \
   -relief flat \
   -padx 10


## Activate scale widget definition like the following,
## if we decide to provide a & b via two scale widgets.

if { 1 == 0} {
##+################################
## DEFINE the 'a'-scale widget
## including a 'label' widget.
##+################################

## Set the init value for the a-scale  var.

set curAvalue 100

## Set the MAX UNITS for the a-scale,
## i.e. the upper limit of the range of values,

# set scaleMaxUnits [expr [winfo height .] / 2]
  set scaleMaxUnits [expr [winfo height .fRcan.can] / 2]

## Define a label widget to precede the a-scale,
## followed by the scale:

label .fRimgspecs.labelSCALE1 \
   -text "\
\ \ \ 'a' (in pixels):" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -relief flat \
   -bd $BDwidthPx_button

scale .fRimgspecs.scale1 \
   -orient horizontal \
   -digits 0 \
   -from 0 -to $scaleMaxUnits \
   -length $initScaleLengthPx \
   -variable curAvalue \
   -command "ReDraw"
}
## END OF if { 1 == 0 }


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


## PACK the widgets of FRAME .fRimgspecs ---
## label ; minilistbox-frame 

pack .fRimgspecs.labelEXPn \
     .fRimgspecs.fRexponent \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

pack .fRimgspecs.chkbuttSHADE \
      -side left \
      -anchor w \
      -fill none \
      -expand 0

pack .fRimgspecs.labelPARMS \
      -side right \
      -anchor e \
      -fill none \
      -expand 0


## Activate scale widget packings like the following,
## if we decide to provide a & b via two scale widgets.

# pack  .fRimgspecs.labelSCALE1 \
#      -side left \
#      -anchor w \
#      -fill none \
#      -expand 0

# pack .fRimgspecs.scale1 \
#       -side left \
#       -anchor w \
#       -fill x \
#       -expand 1

# pack  .fRimgspecs.labelSCALE2 \
#      -side left \
#      -anchor w \
#      -fill none \
#      -expand 0

# pack .fRimgspecs.scale2 \
#       -side left \
#       -anchor w \
#       -fill x \
#       -expand 1


##+######################################################
## DEFINE-and-PACK the 'canvas' widget
## in the '.fRcan' FRAME
##+######################################################

canvas .fRcan.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief raised \
   -borderwidth $BDwidthPx_canvas

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


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


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

bind .fRimgspecs.chkbuttSHADE <ButtonRelease-1> "ReDraw 0"

## The following bind may cause an extra ReDraw when the
## GUI is first configured via an 'update' below
## in the GUI initialization section.
##
## We move this statement to the bottom of this script
## --- and we put the call to 'ReDraw' in a proc that
## checks if the canvas size has indeed changed.

# bind .fRcan.can <Configure> "ReDraw_if_canvas_resized"



##+######################################################################
## PROCS SECTION:
##
##  - ReDraw            - Draws the super-ellipse on the canvas a HORIZONTAL
##                        SCANLINE at a time, for the given
##                        EXPn var and current color var values --- and
##                        current values for a & b, determined from the
##                        current canvas dimensions.
##
##                        Called by the <Configure> binding mentioned above,
##                        by button1-release on the 'minilistbox' widget
##                        for exponent 'n', by button1-release on the 
##                        shading checkbox widget, and by the set-color
##                        procs below.
##
##  - ReDraw_pixelBYpixel  - NOT USED, but was orignally used to draw the
##                           superellipse AND the background a pixel at a
##                           time. Was too slow. Took about 4 secs to draw,
##                           before braces were added to 'expr' statements.
##                           This could be activated by renaming this
##                           proc to 'ReDraw' and renaming the one above,
##                           say, to 'ReDraw_byScanline'.
##
##  - set_ellipse_color1   - called by color1 button '-command'
##
##  - set_ellipse_color2   - called by color2 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 - (could be called ReDraw_byScanline)
##
## PURPOSE:
##     Draws the super-ellipse on the canvas, 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 line of hexcolors of pixels.
##
## CALLED BY:  a <Configure> binding on the canvas widget,
##             a button1-release binding on the shading checkbutton,
##             a button1-release binding in the 'minilistbox' proc
##             --- and by set-color procs.
##
## 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 ever want to implement 2 scale widgets for the a & b parms
##       and use '-command ReDraw'. Thus for small movements of the
##       scale widget's sliderbar, we could do a redraw --- if the redraw
##       proceeds fast enough.
##+#####################################################################

proc ReDraw {x} {

   global EXPn shade0or1 COLOR1r COLOR1g COLOR1b COLOR1hex \
               COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex
   #           COLOR2r COLOR2g COLOR2b COLOR2hex

   ## Delete the current image on the canvas.
   ## 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  .fRcan.can]
   set curCanHeightPx [winfo height .fRcan.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 }
   if {$imgHeightPx % 2 == 1} { incr imgHeightPx }

   ## Make the new image structure.
   image create photo imgID -width $imgWidthPx -height $imgHeightPx
   
   ## Put the (currently empty) image on the canvas.
   .fRcan.can create image 1 1 -anchor nw -image imgID

   ## Get the half width and height of the image --- with which
   ## we will set a & b.
   set xmidPx [expr {$imgWidthPx  / 2}]
   set ymidPx [expr {$imgHeightPx / 2}]

   ## Set the a & b parms of the eqn --- to about
   ## 80% of the image half-width & half-height, resp.
   set factor 0.8
   set aPx [expr {round($factor * double($xmidPx))}]
   set bPx [expr {round($factor * double($ymidPx))}]

   set aPx_float [expr {double($aPx)}]
   set bPx_float [expr {double($bPx)}]

   ## FOR TESTING:
   #   puts "imgID: $imgID"
   #   puts "xmidPx: $xmidPx ;  ymidPx: $ymidPx"
   #   puts "aPx : $aPx  ;  bPx : $bPx"

   ## We now draw the super-ellipse according to the inequality:
   ##   LHS = |x/a|^n + |y/b|^n  is less-than-or-equal-to 1
   ##
   ## We get the colors of horizontal lines in the upper-right quadrant
   ## of the image, and use symmetry to set the colors of pixels in the other
   ## 3 quadrants according to pixel colors in the upper-right quadrant.
   ##
   ## In more detail:
   ## We iterate over the upper-right quadrant of the image, starting
   ## at the top of the image y=0 and going to the mid-height of the
   ## image. We build the hexcolor values of the horizontal scanline
   ## at a given y, in 2 string variables: $hexcolorsSTR_right  and
   ## $hexcolorsSTR_left. For each value of y, two horizontal
   ## scanlines are drawn --- at y and at (image-height - y) --- using
   ## the concatenation of $hexcolorsSTR_left and $hexcolorsSTR_right into a
   ## list --- placing that list on the left of the image at the two y-heights.

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

      ## Reset the 2 string-vars for the 2 halves of the next scanline.
      set hexcolorsSTR_left  ""
      set hexcolorsSTR_right ""

      for {set xPx $xmidPx} {$xPx < $imgWidthPx} {incr xPx} {

         ## Evaluate the expression |x/a|^n + |y/b|^n

         set LHS [ expr {pow( abs( ($xPx - $xmidPx) / $aPx_float ) , $EXPn) + \
                      +  pow( abs( ($ymidPx - $yPx) / $bPx_float ) , $EXPn)} ]

         ## According to the value of LHS, set the pixel color that we will add
         ## to the 2 string-vars holding hexcolors.

         if { $LHS > 1.0} {
            set hexcolor $COLORbkGNDhex
            ## If we are at xPx=xmidPx, because the top edge of the superellipse
            ## declines to the right, the entire horizontal line is the bkgnd color.
            ## We can let the 'put' command 'tile' this color to the entire line.
            if {$xPx == $xmidPx} {
               ## Draw the scanline at height $yPx.  
               imgID put [list $hexcolor] -to 0 $yPx
               ## Draw the scanline at height ($imgHeightPx - $yPx).
               set y2 [expr {$imgHeightPx - $yPx}]
               imgID put [list $hexcolor] -to 0 $y2
               ## Skip to the next y.
               continue
            }
         } else {
            if {$shade0or1 == 0} {
               set hexcolor $COLOR1hex
            } else {
               ## The shading at the edges falls off too slowly if we use
               ## LHS or LHS squared. Even LHS cubed looks a little 'soft'.
               ## So we try the power 4.
               set LHSpow [expr {pow($LHS,4)}]
               set oneMinusLHSpow [expr {1.0 - $LHSpow}]
               set R [expr {int(($LHSpow * $COLORbkGNDr) + ($oneMinusLHSpow * $COLOR1r))}]
               set G [expr {int(($LHSpow * $COLORbkGNDg) + ($oneMinusLHSpow * $COLOR1g))}]
               set B [expr {int(($LHSpow * $COLORbkGNDb) + ($oneMinusLHSpow * $COLOR1b))}]
               set hexcolor [format "#%02X%02X%02X" $R $G $B]
            }
         }

         ## Make the right half of the scanline.
         set hexcolorsSTR_right "$hexcolorsSTR_right $hexcolor"

         ## Make the left half of the scanline.
         set hexcolorsSTR_left "$hexcolor $hexcolorsSTR_left"

         ## FOR TESTING:
         # if {$xPx == $xmidPx} {
         #    puts "xPx: $xPx  yPx: $yPx  LHS: $LHS  hexcolor: $hexcolor"
         # }
      }
      ## END OF xPx loop

      ## Make a list from the left and right color strings.
      set scanlineSTR "$hexcolorsSTR_left $hexcolorsSTR_right"
      set scanlineLIST [list $scanlineSTR]

      ## Draw the scanline at height $yPx.
      imgID put $scanlineLIST -to 0 $yPx

      ## Draw the scanline at height ($imgHeightPx - $yPx).
      set y2 [expr {$imgHeightPx - $yPx}]
      imgID put $scanlineLIST -to 0 $y2

      ## FOR TESTING: (show the progress after drawing each pair of horizontal
      ##               scanlines for a yPx value)
      # update
   }
   ## END OF yPx loop

   ## FOR TESTING:
   #  imgID put [list {#888888 #888888 #888888 #888888 #888888} ] -to 0 [expr {$ymidPx - 5}]

   ## FOR TESTING:
   #   puts "hexcolorsSTR_right:  $hexcolorsSTR_right"
   #   puts "hexcolorsSTR_left:   $hexcolorsSTR_left"

   ## Make sure the text on the COLORS and PARMS label widgets
   ## is up to date.

   .fRbuttons.labelCOLORS configure -text "\
Colors:  Superellipse - $COLOR1hex   Background - $COLORbkGNDhex"

   .fRimgspecs.labelPARMS configure -text "\
Current Super-ellipse Parameters:
 n = $EXPn ; a = $aPx ; b = $bPx
 in equation  |x/a|^n + |y/b|^n = 1"

}
## END OF proc 'ReDraw' (by Scanline)


##+#####################################################################
## proc ReDraw_pixelBYpixel -
##
## PURPOSE:
##     Draws the super-ellipse on the canvas, a pixel per each call
##     like:     imgID put $hexcolor -to $x $y
##
## CALLED BY:  a <Configure> binding on the canvas widget,
##             a button1-release binding on the shading checkbox,
##             a button1-release binding in the 'minilistbox' proc
##             --- and by set-color procs.
##
## 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 ever want to implement 2 scale widgets for the a & b parms
##       and use '-command ReDraw'. Thus for small movements of the
##       scale widget's sliderbar, we could do a redraw --- if the redraw
##       proceeds fast enough.
##+#####################################################################

proc ReDraw_pixelBYpixel {x} {

   global EXPn shade0or1 COLOR1r COLOR1g COLOR1b COLOR1hex \
               COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex
   #           COLOR2r COLOR2g COLOR2b COLOR2hex

   ## Delete the current image on the canvas.
   ## 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  .fRcan.can]
   set curCanHeightPx [winfo height .fRcan.can]

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

   ## Make the new image structure.
   image create photo imgID -width $imgWidthPx -height $imgHeightPx
   
   ## Put the image on the canvas.
   .fRcan.can create image 1 1 -anchor nw -image imgID

   ## Get the half width and height of the canvas --- with which
   ## we will set a & b.
   set xmidPx [expr {$curCanWidthPx  / 2}]
   set ymidPx [expr {$curCanHeightPx / 2}]

   ## Set the a & b parms of the eqn.
   set factor 0.8
   set aPx [expr {round($factor * double($xmidPx))}]
   set bPx [expr {round($factor * double($ymidPx))}]

   set aPx_float [expr {double($aPx)}]
   set bPx_float [expr {double($bPx)}]

   ## FOR TESTING:
   #   puts "imgID: $imgID"
   #   puts "xmidPx: $xmidPx ;  ymidPx: $ymidPx"
   #   puts "aPx : $aPx  ;  bPx : $bPx"

   ## We now draw the super-ellipse according to the inequality:
   ##  |x/a|^n + |y/b|^n  is less-than-or-equal-to 1
   ##
   ## We iterate over the upper right quadrant of the canvas,
   ## and use symmetry to set the colors of pixels in the other
   ## 3 quadrants according to pixel colors in the upper-right quadrant.

   for {set xPx 1} {$xPx <= $xmidPx} {incr xPx} {
      for {set yPx 1} {$yPx <= $ymidPx} {incr yPx} {

         ## Evaluate the expression |x/a|^n + |y/b|^n

         set LHS [ expr {pow(abs($xPx / $aPx_float) , $EXPn) + \
                     +   pow(abs($yPx / $bPx_float) , $EXPn)} ]

         ## According to the value of LHS (and the setting of the shading switch),
         ## set the pixel color to be used in 4 quadrants.

         if { $LHS > 1.0} {
            set hexcolor $COLORbkGNDhex
         } else {
            if {$shade0or1 == 0} {
               set hexcolor $COLOR1hex
            } else {
               ## The shading at the edges falls off too slowly if we use
               ## LHS or LHS squared. Even LHS cubed looks a little 'soft'.
               ## So we try the power 4.
               set LHSpow [expr {pow($LHS,4)}]
               set oneMinusLHSpow [expr {1.0 - $LHSpow}]
               set R [expr {int(($LHSpow * $COLORbkGNDr) + ($oneMinusLHSpow * $COLOR1r))}]
               set G [expr {int(($LHSpow * $COLORbkGNDg) + ($oneMinusLHSpow * $COLOR1g))}]
               set B [expr {int(($LHSpow * $COLORbkGNDb) + ($oneMinusLHSpow * $COLOR1b))}]
               set hexcolor [format "#%02X%02X%02X" $R $G $B]
            }
         }

         ## Calculate the coordinates (in canvas units) to be used for
         ## the 4 points in the 4 quadrants, corresponding to the current
         ## values of $xPx and $yPx.

         set xrightPx  [expr { $xPx + $xmidPx}]
         set xleftPx   [expr {-$xPx + $xmidPx + 1}]
         set ytopPx    [expr {-$yPx + $ymidPx + 1}]
         set ybottomPx [expr {$yPx + $ymidPx}]

         ## Set the pixel color at the 4 points in the 4 quadrants.
         ##
         ## bottom-right: xmidPx+xPx,ymidPx+yPx
         ## top-right:    xmidPx+xPx,1+ymidPx-yPx
         ## top-left:     1+xmidPx-xPx,1+ymidPx-yPx
         ## bottom-left:  1+xmidPx-xPx,ymidPx+yPx

         imgID put $hexcolor -to $xrightPx $ybottomPx
         imgID put $hexcolor -to $xrightPx $ytopPx
         imgID put $hexcolor -to $xleftPx  $ytopPx
         imgID put $hexcolor -to $xleftPx  $ybottomPx

         ## FOR TESTING:
         #   puts "LHS: $LHS"
         #   puts " $xrightPx $ybottomPx

         ## FOR TESTING: (slow down the drawing of the 4-pixels per loop)
         #   after 100

      }
      ## END OF yPx loop

      ## FOR TESTING: (show the progress after each pass thru y values for an x value)
      # update
   }
   ## END OF xPx loop


   ## Make sure the text on the COLORS and PARMS label widgets
   ## is up to date.

   .fRbuttons.labelCOLORS configure -text "\
Colors:  Superellipse - $COLOR1hex   Background - $COLORbkGNDhex"

   .fRimgspecs.labelPARMS configure -text "\
Current Super-ellipse Parameters:
 n = $EXPn ; a = $aPx ; b = $bPx
 in equation  |x/a|^n + |y/b|^n = 1"

}
## END OF proc 'ReDraw' (pixelBYpixel)


##+#####################################################################
## proc 'set_ellipse_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 for the superellipse.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR1  button
##+#####################################################################

proc set_ellipse_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 superellipse (and background) with the new
   ## superellipse 'fill' color.

   ReDraw 0

}
## END OF proc 'set_ellipse_color1'


##+#####################################################################
## proc 'set_ellipse_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 color2 of the 2 colors for a
##   color gradient from color1 to color2.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLOR1  button
##+#####################################################################

proc set_ellipse_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 superellipse (and background) with the new
   ## superellipse 'edge' color.

   ReDraw 0

}
## END OF proc 'set_ellipse_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 the superellipse lies.
##
## Arguments: none
##
## CALLED BY:  .fRbuttons.buttCOLORbkGND  button
##+#####################################################################

proc set_background_color {} {

   global COLORbkGNDr COLORbkGNDg COLORbkGNDb COLORbkGNDhex
   # global feDIR_tkguis

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

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

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

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

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

   ## Redraw the background (and the superellipse) with the
   ## new background color.
   ReDraw 0

}
## END OF proc 'set_background_color'


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

proc ReDraw_if_canvas_resized {} {
   global  PREVcanWidthPx PREVcanHeightPx

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

   if { $CURcanWidthPx  != $PREVcanWidthPx ||
        $CURcanHeightPx != $PREVcanHeightPx} {
      ReDraw 0
      set PREVcanWidthPx  $CURcanWidthPx
      set PREVcanHeightPx $CURcanHeightPx
   }

}
## END OF proc ReDraw_if_canvas_resized


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

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


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

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


Here is an image that shows that the 'jaggies' return with a vengeance when the 'Shading' checkbutton on the GUI is un-checked.

superellipse_n6_noShading_redOnWhite_601x438.jpg

(For a little variety, I changed the superellipse color from magenta to red and the background color from black to white.)

Here is an image for large n (n=22) that shows that one can get quite pleasing rounded and shaded buttons for use in Tk GUI's and web pages, by using this Tk GUI utility.

superellipse_n22_shadedEdge_magentaOnBlack_602x440.jpg

So it appears that one could make some interesting 'button', 'bullet', icon-background, and logo-background images with this utility.

And I have the option of enhancing the Tk script on this page, to provide a few more capabilities to this superellipse generator --- such as adding a 3rd color button, to allow for shading at the edge of the superellipse with a color other than the background color.

Furthermore, I could add a couple of scale widgets to the GUI, to allow for setting the 'a' and 'b' parameters of the superellipse formula more directly --- instead of setting them by resizing the window.

And I can adjust a 'pow' (power function) exponent to change the 'intensity' ('extensity' may be more properly descriptive, though a non-word) of the shading at the edge of the superellipse. A widget could be added to the GUI to allow the user to easily set that exponent.