#!/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 -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 http://wiki.tcl.tk/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 -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 -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 -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 (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 ## puts a selected line of the ## listbox in a specified var and executes a ## specified command/proc. ##+##################################################### bind $w.fRopts.listbox "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 "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 "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 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 ## ##+####################################################################### ##+##################################################################### ## 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 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 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 ## 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 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 ' 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 "ReDraw_if_canvas_resized"