uniquename - 2014mar15

In 2003, ulis provided Tcl-Tk code on a wiki page titled Merging images with transparency.

I thought I might find it handy to have a utility that merges two images --- images in JPEG and PNG files, as well as GIF files.

However, in his 'demo' code, 'ulis' did not offer a file selector GUI. The names of two test GIF files were hard-coded in the code.

Although he allowed for the two images to be of different sizes, the code reveals that the two images are merged by 'aligning' the 2 rectangular images with their top-left corners matched-up. In other words, there is no flexibility in the alignment of the 2 images.

Furthermore, the weighting factor for merging the two images was hard-coded.

In fact the GUI consisted only of a canvas holding the new image --- no buttons, no entry fields, no controls of any kind on the GUI.

I have had a 'merge-two-images' Tk script (with GUI control widgets) on my 'to do' list --- at the bottom of my 'bio' page uniquename --- for about a year now.

I started the GUI for that script in August 2013. I set the script aside as I worked on other Tk scripts. I returned to the script and started-and-finished the coding-and-testing of the procs for the script this month (about 7 months later, 2014 March) --- and that code is presented below.

---

THE GOALS

My main goals for the Tcl-Tk script were:

1) Provide a GUI for selecting two image files.

2) Provide the user a way to easily select a weighting factor for merging the 2 images.

3) Allow the 2 images to be of different sizes --- and provide the user a simple way to 'align' the 2 images.

I am currently not concerned with handling transparency in GIF and PNG images. So, in the code below, I have not included code to handle transparency information in either image file.


SCREENSHOT OF THE GUI

On the basis of the goals above, which implied the need for widgets on the GUI to

1) enter two image filenames

2) specify 'alignment' of the two images

3) specify a weighting factor

I ended up with the GUI seen in the following image.

merge2images_pre-merge_screenshot_777x582.jpg

Note that there are nine 'compass-point' radiobuttons that allow the user to quickly specify a method-of-alignment. And there is a 'scale' widget on the GUI to easily set the weighting factor.

---

TYPICAL SEQUENCE OF OPERATIONS WITH THE GUI

STEP 1:

Select the 2 image files to be merged. This is most conveniently done with the 'Browse...' buttons on the GUI.

STEP 2:

With the 'alignment' radiobuttons on the GUI, select a 'compass point' which will be used to align the 2 images with respect to each other.

(If the 2 images are the same size, any of the choices should yield the same result.)

STEP 3:

As indicated in a brief 'guide' on the GUI, the user can 'right-click' (with mouse-button-3) on EITHER filename entry field to cause the 2 image files to be read and their images shown on the 'canvas'.

The images will be located on the canvas according to the user-selected 'alignment' option.

(The 2nd image may completely cover the first, so to be able to check the alignment of the 2 images, the user can CLICK ANYWHERE ON THE CANVAS to cause the 2 images to 'flash' over-under each other several times. Thus the user can see the 'common merge rectangle' of each image --- whose pixels will be averaged together to get the merged image.)

STEP 4:

Adjust the setting of the 'weight' slider and click on the 'Merge' button to cause the merge (which occurs in about one second).

Experiment with the 'weight' slider and the 'Merge' button to see the effect of weighting image1 more or less relative to image2.

---

In the sample image above, the second image was bigger (both horizontally and vertically) than the first image.

Although the user can see the 'image1' when it is loaded to the canvas --- because there is a pause of about 1 second before loading 'image2' --- the 2nd image, in this case, then completely hides the first.

In this case, the 'flash' feature was handy to be able to confirm the alignment of the two images, according to the 'center' radiobutton.

After clicking on the 'Merge' button, within about 1 second, one gets a merged image, like the following.

merge2images_post-merge_screenshot_777x583.jpg

One way to see the 2nd image (if it is covered by the 1st image) --- besides using the 'flash' feature --- is to set the weighting factor to 1.0 and click on the 'Merge' button --- to get an image like the following.

merge2images_image2_screenshot_775x582.jpg

In fact, by setting the weighting factor to 0.0 and clicking 'Merge' the 2nd image is shown without any contribution from the 1st image. So if the 1st image is larger than the 2nd, this technique could be used to show the 2nd image (which is 'hiding' underneath the first).


THE CODE

Below, I provide the Tk script code for this 'merge-two-images' utility.

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-for-widgets, widget-geometry-parms,
     text-array-for-labels-etc, win-size-control).

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

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

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

  4) Define PROCS, if needed.

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

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

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

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


EXPERIMENTING WITH THE GUI

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

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

I think that I have used a pretty nice choice of the 'pack' parameters. The label and button and radiobutton and scale widgets stay fixed in size and relative-location if the window is re-sized --- while the two filename entry widgets expand/contract horizontally whenever the window is re-sized horizontally.

And the canvas expands both horizontally and vertically when the window is resized.

For example, if the user clicks on the Maximize button of the window, the window-manager expands the window to screen-size --- and the two entry fields expand to maximum size horizontally, and the canvas expands to maximum size both horizontally and vertically.

You can experiment with the '-side', '-anchor', '-fill', and '-expand' parameters on the 'pack' commands for the various frames and widgets --- to get the widget behavior that you want.

___

Additional experimentation: You might want to change the fonts used for the various GUI widgets. For example, you could change '-weight' from 'bold' to 'normal' --- or '-slant' from 'roman' to 'italic'. OR change font families.

In fact, you may NEED to change the font families, because the families I used may not be available on your computer --- and the default font that the 'wish' interpreter chooses may not be very pleasing.

I use variables to set geometry parameters of widgets --- parameters such as border-widths and padding. And I have included the '-relief' parameter on the definitions of frames and widgets. Feel free to experiment with those 'appearance' parameters as well.

If you find the gray 'palette' of the GUI is not to your liking, you can change the value of the RGB parameter supplied to the 'tk_setPalette' command near the top of the code.

---

Note that the 'BackgroundColor' button on the GUI calls on an RGB-color-selector-GUI script to set the canvas background color. You can make that RGB-color-selector script by cutting-and-pasting the code from the page A non-obfuscated color selector GUI on this site.


SOME FEATURES IN THE CODE

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

You can look at the top of the PROCS section of the code to see a list of the procs used in this script, along with brief descriptions of how they are called and what they do.

The main procs are

   'get_img1_filename'     - called by the 'Browse...' button beside
                             the entry field for the image1 file

   'get_img2_filename'     - called by the 'Browse...' button beside
                             the entry field for the image2 file

   'get_chars_before_last' - called by the 'get_*_filename' procs and
                             the 'checkFile_convertToGIF' proc

  'checkFile_convertToGIF' - called by the 'get_*_filename' procs

   'prep_imgsANDcanvas_forMerge'  - called by button1-release or <Return> on
                                    either of the 2 filename entry fields

   'create_img1_photoID'     - called by the 'prep_imgsANDcanvas_forMerge' proc

   'create_img2_photoID'     - called by the 'prep_imgsANDcanvas_forMerge' proc

   'set_canvas_size'         - called by the 'prep_imgsANDcanvas_forMerge' proc

   'set_anchorPointOnCanvas' - called by the 'prep_imgsANDcanvas_forMerge' proc

   'place_2images_onCanvas'  - called by the 'prep_imgsANDcanvas_forMerge' proc

   'set_merge_area_limits'   - called by the 'prep_imgsANDcanvas_forMerge' proc

   'merge2images'      - called by a click on the '(re)Merge' button (or, someday?,
                         by button1-release on the 'scale' widget) --- to
                         make a new image on the canvas according to the 2 files
                         and the current settings of the scale and radiobuttons.

   'raise-lower_images'     - called by a button1-release binding on the canvas.

   'new_compass_point'      - called by button1-release bindings on the
                              compass-point radiobuttons

   'set_background_color'   - called by the 'BackgroundColor' button.

   'update_color_button'    -  called by proc 'set_background_color' and
                               called in the 'ADDITIONAL-GUI-INITIALIZATION'
                               section at the bottom of this script.

   'popup_msgVarWithScroll' - used to show messages to the user, such as
                              the HELPtext for this utility via the 'Help' button.

---

Modularity of procs

One of the trickiest things about this GUI involved finding a way to break up the necessary operations into a 'modular' form in the procs --- so that the groups-of-operations would support the various user-actions that might be needed via the GUI widgets.

Comments at the top of the code indicate how I outlined the sequence of operations to be implemented and how I grouped those operations into separate procs.

Even if it is necessary to change, somewhat, the way the operation-groups are performed via 'events' on the widgets of the GUI, the 'granularity' of the modular break-down of the operations into procs will probably serve to facilitate a change to the set of operations triggered by any particular widget-event.

---

JPEG and PNG (and other non-GIF image formats)

Another challenge was to be able to handle JPEG and PNG files as well as GIF files --- without requiring the user to install a '3rd party' Tk-extension to handle reading JPEG files.

I settled on using the 'exec' command to issue the ImageMagick 'convert' command.

Code fragment in proc 'checkFile_convertToGIF':

  set RETcode [catch {exec convert "$INfilename" -colors 256 "$tempFilename"} CatchMsg]

where 'tempFilename' contains a name that ends with '.gif'.

In fact, the proc 'checkFile_convertToGIF' includes an 'exec' of the 'file' command to determine if the $INfilename file is a GIF file --- via use of the Tcl 'string match' command.

If the file is determined to be a GIF file, then 'convert' is not used. But, for any other file, the file is converted to a GIF file.

So this utility will actually merge any of the 100-plus types of image file supported by the ImageMagick 'convert' command --- by converting such files to a new '.gif' file. Reference: http://www.imagemagick.org/script/formats.php

So this utility will convert PGM (Portable Gray Map), PPM (Portable Pixel Map), TIFF (Tagged Image File Format), TGA (Targa), XWD (X Window Dump) and other types of image files to '.gif' files --- and do the merge with those GIF files.

---

"Flashing" the 2 images

Implementation of the 'flash-the-two-images' option (when the user clicks on the canvas) was done by the following two statements in proc 'raise-lower_images':

      .fRcanvas.can raise TAGimg1

      .fRcanvas.can raise TAGimg2

where these statements are separated by an 'after 500' statement, and each is followed by an 'update' statement.

---

Handling huge images

To be able to scroll huge images, a '-scrollregion' parameter is used to configure the (scrollable) canvas --- in proc 'set_canvas_size'.


There are probably other noteworthy 'features' of the code that could and should be mentioned here. But enough for now.


It is my hope that the copious comments in the code will help Tcl-Tk coding 'newbies' get started in making GUI's like this.

Without the comments, potential young Tcler's might be tempted to return to their iPhones and iPads and iPods --- to watch videos of life with the Kardashians --- leading to wondering what the Kardashian men do for a living. (Well, the 'white' men anyway. Kanye and Lamar seem to have quite lucrative jobs in the music and basketball 'industries'. Are the white Kardashian men on welfare?)


 Code for Tk script 'merge2images.tk' :
#!/usr/bin/wish -f
##
## SCRIPT: merge2images.tk
##
## PURPOSE:  This Tk GUI script allows the user to select 2 image files
##           (GIF or PNG or JPEG). The two files are read and colors of
##           'overlaying' pixels are averaged to create a new image that
##           is shown as a Tk 'photo' image on a Tk canvas of the GUI.
##
##           The GUI includes a slider-bar that allows the user to choose
##           a fraction between 0 and 1 that is used to weight the colors
##           of one image relative to the other image.
##
##           This utility allows the 2 images selected to be of different
##           sizes. There are 'anchor' radiobuttons on the GUI that allow
##           the user to specify the positioning of the 2 images relative
##           to each other. The radiobuttons have compass directions to
##           indicate which points on the 2 images to align to each other.
##
##           The radiobuttons offer the following 9 alignment options:
##
##                  n  ne  e  se  s  sw  w  nw  center
##
##+#################################
## METHOD USED to merge the 2 images:
##
## 1)  The user puts two image filenames (fully-qualified) in the two
##     filename entry fields.
##     IMPLEMENTATION: in 2 'get_*_filename' procs.
##
## 2)  The Tk 'image create photo' command is used to create two Tk
##     'photo' image structures (and their identifiers, ID's) from the
##     two user-selected image files.
##     IMPLEMENTATION: in 2 'create_*_photoID' procs
##
## 3)  The width and height of the (scrollable) canvas are determined
##     by the max-width and max-height of the widths/heights from the two
##     'photo' IDs.
##     IMPLEMENTATION: in a 'set_canvas_size' proc
##     (A background color for the canvas can be applied in this proc.)
##
## 4)  The dimensions of the 2 areas (on the 2 images) to be merged are
##     determined by the min-width and min-height of the widths/heights
##     from the two 'photo' IDs. Start-End limits on the 2 images are determined
##     that can be used to merge the 'common area' on the two images.
##     IMPLEMENTATION: in a 'set_merge_area_limits' proc
##
## 5)  The two images are placed on the scrollable canvas according
##     to the currently specified 'compass point'. For example, if
##     the compass point is 'nw', the upper-left corners of both images
##     are placed at the upper-left corner of the scrollable canvas.
##     Second example: If the compass point is 'center', the center points
##     of both images are placed at the center point of the scrollable
##     canvas. Third example: If the compass point is 'se', the lower-right
##     corners of both images are placed at the lower-right corner of the
##     scrollable canvas.
##     IMPLEMENTATION: in a 'place_2images_onCanvas' proc
##
## 6)  The merge parameters determined in Step4 are used to determine
##     the x-pixel and y-pixel start and end locations in a double-loop
##     over the columns and rows of the same-sized merge areas of the
##     two images. In other words, THIS IS THE MERGE STEP THAT CREATES
##     THE MERGED IMAGE ON THE CANVAS.
##     IMPLEMENTATION: in a 'merge2images' proc
##
## SOME NOTES ON THE APPEARANCE OF THE CANVAS AFTER THE MERGE:
##
##     Parts of the canvas that are not covered by either of the two
##     images show as the (SOLID) background color applied to the canvas.
##
##     Parts of either of the two images that 'peek out' beyond
##     the other image are not changed (not merged). Those parts, on
##     the canvas, appear as they did in the original images.
##
##     The pixels in the areas of the two images that are
##     'overlaying each other' are averaged according to the weighting
##     factor that is selected by the user with the 'scale' widget.
##
##     A button1-release binding on the 'scale' widget causes the
##     'merge2images' proc to be executed whenever the user moves
##     the slider of the 'scale' widget and then releases mouse-button-1.
##
## NOTE ON GIF versus PNG versus JPEG:
##
##     Since Tk 'image create photo' does not support reading JPEG-JFIF
##     files without resorting to Tk extensions (and since 'image create photo'
##     did not support reading PNG files until late 2013, in Tk version 8.6),
##     this utility assumes the ImageMagick 'convert' command is available
##     and uses that program to convert JPEG and PNG files to GIF files.
##     Then 'image create photo' is used on the resultant GIF file.
##
##     This can result in a loss of image quality --- especially when there
##     are many more than 256 color shades in the JPEG or PNG file.
##     A common effect in these cases is 'color banding' in the converted
##     image.
##
##     For example, 'computer desktop wallpaper' images, which often consist
##     of gradual gradiations of colors across the large image, are subject
##     to 'color banding' when converted to GIF files. And landscape and other
##     nature photographs (usually in JPEG format) typically consist
##     of many more than 256 colors and result in rather 'grainy'/'aliased'
##     images when they are converted to GIF files.
##
##     When a version of the Tk 'wish' interpreter is available that 'natively'
##     supports both JPEG-JFIF-read and PNG-read, then this utility could
##     easily be changed to eliminate the use of the 'convert' program.
##
##+########
## CREDITS:  This Tk script was inspired by a Tk script at the web page
##           'Merging images with transparency' - http://wiki.tcl.tk/10038 -
##           by 'ulis', posted 2003 Sep.
##
##           He did not offer a file selector GUI, the two images
##           had to be overlaid with the top-left corners matching,
##           and the weighting factor was hard-coded.
##
##           In fact the GUI consisted only of a canvas holding the
##           new image --- no buttons, no entry fields, no controls
##           of any kind on the GUI.
##
##+#########################
## USING THE GENERATED IMAGE:
##           A screen/window capture utility (like 'gnome-screenshot'
##           on Linux) can be used to capture the GUI image in a PNG
##           or GIF file, say.
##
##           If necessary, an image editor (like 'mtpaint' on Linux)
##           can be used to crop the window capture image.  The image
##           could also be down-sized --- say to make a smaller image
##           suitable for a web page or an email.
##
##           The colored image file could be used with a utility (like the
##           ImageMagick 'convert' command) to change a color of the image
##           to TRANSPARENT, making a partially transparent GIF
##           (or PNG) file.
##
##+########################################################################
## 'CANONICAL' STRUCTURE OF THIS TK CODE:
##
##  0) Set general window & widget parms (win-name, win-position,
##     win-color-scheme, fonts, widget-geometry-parms,
##     text-array-for-labels-etc, win-size-control).
##
##  1a) Define ALL frames (and sub-frames, if any).
##  1b) Pack   ALL frames and sub-frames (that are to show inititally).
##
##  2) Define all widgets in the frames, frame-by-frame.
##                        When ALL the widgets for a frame are defined,
##                        pack ALL the widgets in the frame.
##
##  3) Define keyboard and mouse/touchpad/touch-senisitive-screen 'event'
##     BINDINGS, if needed.
##
##  4) Define PROCS, if needed.
##
##  5) Additional GUI INITIALIZATION (typically with one or two of the procs),
##     if needed/wanted.
##
##+####################################
## MORE DETAIL ABOUT THE CODE STRUCTURE of this particular script:
##
##  1a) Define ALL frames:
##
##      Top-level :  '.fRbuttons'
##                   '.fRfile1'
##                   '.fRfile2'
##                   '.fRguide'
##                   '.fRradbutts'
##                   '.fRcanvas'
##
##      Sub-frames: none for any of these frames
##
##  1b) Pack ALL the frames --- top to bottom.
##
##  2) Define all widgets in the frames (and pack them):
##
##     - In '.fRbuttons':   1 BUTTON widget ('Exit'),
##                          1 BUTTON widget ('Help'),
##                          1 BUTTON widget ('(re)Merge'),
##                          1 LABEL-and-SCALE widget for the color
##                            weighting factor between the 2 images
##
##     - In '.fRfile1':     LABEL, ENTRY, and 'Browse...' BUTTON widgets
##
##     - In '.fRfile2':     LABEL, ENTRY, and 'Browse...' BUTTON widgets
##
##     - In '.fRguide':     one LABEL widget
##
##     - In '.fRradbutts':  for 9 RADIOBUTTONs for compass-points
##
##     - In '.fRcanvas':    to contain 1 (scrollable) CANVAS widget
##
##  3) Define BINDINGS: button1-release bindings on the compass-point radiobuttons,
##                      to call on procs 'set_merge_area_limits' and
##                      'place_2images_onCanvas' below.
##
##  4) Define PROCS:
##
##    Some of the main procs follow. See the comments at the top of the
##    PROCS section for a few more procs that were added during testing.
##
##   'get_img1_filename' - called by the 'Browse...' button beside
##                         the entry field for the image1 file
##
##   'get_img2_filename' - called by the 'Browse...' button beside
##                         the entry field for the image2 file
##
##   'prep_imgsANDcanvas_forMerge'  - called by button1-release or <Return> on
##                               either of the 2 filename entry fields
##
##   'create_img1_photoID' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'create_img2_photoID' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'set_canvas_size'     - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'set_merge_area_limits' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'place_2images_onCanvas' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'merge2images'      - called by a click on the '(re)Merge' button or by
##                         button1-release on the 'scale' widget --- to
##                         make a new image on the canvas according to the 2 files
##                         and the current settings of the scale and radiobuttons.
##
##   'set_background_color'   -called by the 'BackgroundColor' button
##
##   'popup_msgVarWithScroll' - used to show messages to the user, such as
##                              the HELPtext for this utility via the 'Help' button
##
##  5) Additional-GUI-initialization: See that section at the bottom of this script.
##
##+########################################################################
## 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 2013aug09 Started initial layout of the GUI,
##                                        with procs dummied out.
## Changed by: Blaise Montandon 2014mar13 Started developing-testing the
##                                        procs.
## Changed by: Blaise Montandon 2014mar15 Moved some error/warning message
##                                        strings to the 'aRtext' array.
##                                        Added code to 'convert' JPEG & PNG
##                                        files to GIF files.
##                                        Finished the 'HELPtext' variable.
##                                        Add 'Clear' button.
##+#######################################################################

##+#######################################################################
## Set WINDOW TITLES.
##+#######################################################################

wm title    . \
   "Merge 2 Images (GIF/PNG/JPEG) - with image-weighting & image-alignment options"

wm iconname . "Merge2Imgs"


##+#######################################################################
## Set WINDOW POSITION.
##+#######################################################################

wm geometry . +15+30


##+######################################################
## Set the COLOR SCHEME for the window ---
## and background colors for some of its widgets.
##+######################################################

## For grayish palette.
if {1} {
   set Rpal255 210
   set Gpal255 210
   set Bpal255 210
}

## For bluish palette.
if {0} {
   set Rpal255 200
   set Gpal255 200
   set Bpal255 255
}

set hexPALcolor [format "#%02X%02X%02X" $Rpal255 $Gpal255 $Bpal255]

tk_setPalette "$hexPALcolor"


##+#####################################
## Set color background for some widgets.
##+#####################################

set entryBKGD   "#f0f0f0"
set scaleBKGD   "#f0f0f0"
set textBKGD    "#f0f0f0"
set radbuttBKGD "#c0c0c0"
# set chkbuttBKGD "#c0c0c0"
# set listboxBKGD "#f0f0f0"


##+################################################
## Initialize the background color for the canvas.
##+################################################

## For black canvas background:
if {1} {
   set COLORBKGDr 0
   set COLORBKGDg 0
   set COLORBKGDb 0
}

## For white canvas background:
if {0} {
   set COLORBKGDr 255
   set COLORBKGDg 255
   set COLORBKGDb 255
}

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


##+##########################################################
## Set (temporary) FONT-NAMES.
##
## We use a VARIABLE-WIDTH FONT for LABEL and BUTTON widgets
## --- and the numeric values shown by SCALE widgets.
##
## We use a FIXED-WIDTH FONT for TEXT widgets (to preserve
## alignment of columns in text), LISTBOX widgets (to preserve
## alignment of characters in lists), and ENTRY fields
## (to make it easy to position the text cursor at narrow
## characters like i, j, l, and the number 1).
##+##########################################################

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 GEOMETRY PARAMETERS for the various widget definitions.
## (e.g. padding & borderwidths & relief for Buttons and Labels)
##
## Relief must be flat, groove, raised, ridge, solid, or sunken.
##+###########################################################

## BUTTON geom parameters:

set PADXpx_button 0
set PADYpx_button 0
set BDwidthPx_button 2
## We generally default to relief "raised" for all 'button' widgets.
## BUT, in case you want to experiment:
set RELIEF_button "raised"

## LABEL geom parameters:

set PADXpx_label 0
set PADYpx_label 0
# set BDwidthPx_label 0
  set BDwidthPx_label 2
set RELIEF_label_lo "flat"


## SCALE geom parameters:

set BDwidthPx_scale 2
set initScaleLengthPx 300
set scaleThickPx 10


## ENTRY geom parameters:

set BDwidthPx_entry 2
## We default to relief "sunken" for all 'entry' widgets.
set initImgfileEntryWidthChars 25


## RADIOBUTTON geom parameters:

set PADXpx_radbutt 0
set PADYpx_radbutt 0
set BDwidthPx_radbutt 1
set RELIEF_radbutt_hi "raised"


## CHECKBUTTON geom parameters:

# set PADXpx_chkbutt 0
# set PADYpx_chkbutt 0
# set BDwidthPx_chkbutt 1
# set RELIEF_chkbutt_hi "raised"


## For (small) TEXT widgets:

set BDwidthPx_text 2
# set RELIEF_numtext "sunken"
  set RELIEF_numtext "ridge"
# set RELIEF_numtext "groove"


## CANVAS geom parms:

set initCanWidthPx 400
set initCanHeightPx 300
# set BDwidthPx_canvas 2
  set BDwidthPx_canvas 0
set RELIEF_canvas "flat"


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

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

## For the '.fRbuttons' frame:

set aRtext(buttonEXIT) "Exit"
set aRtext(buttonHELP) "Help"
set aRtext(buttonCOLORBKGD) "Background
Color"
set aRtext(buttonMERGE) "(re)Merge"

set aRtext(labelSCALE)  "Pixel-Color Weighting Factor (for image1
pixels versus image2 pixels - 0.0 to 1.0):"


## For the '.fRfile1' and '.fRfile2' frames:

# set aRtext(labelFILE1)    "Img1 Filename (GIF/PNG/JPEG):"
set aRtext(labelFILE1)    "Img1 Filename:"
set aRtext(buttonBROWSE)  "Browse ..."
# set aRtext(labelFILE2)    "Img2 Filename (GIF/PNG/JPEG):"
set aRtext(labelFILE2)    "Img2 Filename:"


## For the '.fRradbutts' frame:

set aRtext(labelRADBUTTS) "Image alignment points:"

set aRtext(radbuttN)  "n "
set aRtext(radbuttNE) "ne "
set aRtext(radbuttE)  "e "
set aRtext(radbuttSE) "se "
set aRtext(radbuttS)  "s "
set aRtext(radbuttSW) "sw "
set aRtext(radbuttW)  "w "
set aRtext(radbuttNW) "nw "
set aRtext(radbuttCENTER)  "center"
set aRtext(buttonCLEAR) "Clear"

## For the '.fRguide' frame:

set Nflashes 5

set aRtext(labelGUIDE) \
"After selecting/keying-in 2 filenames, 'right-click' on either filename\
entry field to cause the images to be loaded to the canvas.
Click on the canvas to 'flash' img1 & img2 over-under each other\
$Nflashes times, to confirm alignment."


## For popup messages in proc 'checkFile_convertToGIF':

set aRtext(MSGfilecheck) \
"The 'file' command failed on checking the file-type of file"

set aRtext(MSGconvert) \
"The ImageMagick 'convert' command failed on trying to make a GIF file from file"

set aRtext(MSGfileexists1) \
"A file already exists with the following name:"

set aRtext(MSGfileexists2) \
"This utility wants to use that name to 'convert' an image file to a GIF file.
Delete or rename the existing file, and try again."


## For popup messages in proc 'prep_imgsANDcanvas_forMerge':

set aRtext(MSGentry1) \
"The entry-field for image-file-1 is empty.
Select/enter a filename."

set aRtext(MSGentry2) \
"The entry-field for image-file-2 is empty.
Select/enter a filename."


## For popup messages in procs 'create_img*_photoID':

set aRtext(MSGnotfound1) \
"The image-file for the filename in entry-field-1 was NOT FOUND."

set aRtext(MSGnotfound2) \
"The image-file for the filename in entry-field-2 was NOT FOUND."


## For popup messages in proc 'new_compass_point':

set aRtext(MSGentry1forAlign) \
"The entry-field for image-file-1 is empty.
Select/enter a filename.
(There is no point in aligning images that have not been specified.)"

set aRtext(MSGentry2forAlign) \
"The entry-field for image-file-2 is empty.
Select/enter a filename.
(There is no point in aligning images that have not been specified.)"

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


##+###################################################################
## Set a MINSIZE of the window (roughly).
##
## For WIDTH, allow for a minwidth of the '.fRbuttons' frame:
##            about 5 buttons (Exit,Help,reDraw), and
##            a label-and-scale widget-pair.
##
## For HEIGHT, allow
##             2 chars  high for the '.fRbuttons'  frame
##             1 char   high for the '.fRfile1'    frame
##             1 char   high for the '.fRfile2'    frame
##             1 char   high for the '.fRradbutts' frame
##             2 char   high for the '.fRguide'    frame
##            24 pixels high for the '.fRcanvas'   frame.
##+#######################################################################
## We allow the window to be resizable and we pack the canvas with
## '-fill both -expand 1' so that the canvas can be enlarged to
## (try to) accomodate the max-size of the 2 images loaded.
##+#######################################################################

set minWinWidthPx [font measure fontTEMP_varwidth \
   "$aRtext(buttonEXIT) $aRtext(buttonHELP) $aRtext(buttonMERGE) \
Pixel-Color Weighting Factor (for"]

## Add some pixels to account for right-left-side window decoration
## (about 8 pixels), about 4 widgets x 2 pixels/widget for borders/padding
## --- for 4 widgets --- 3 buttons and 1 label.
## Add in pixels for the horizontal scale's length.

set minWinWidthPx [expr {16 + $initScaleLengthPx + $minWinWidthPx}]


## MIN HEIGHT ---
##             2 chars  high for the '.fRbuttons'  frame
##             1 char   high for the '.fRfile1'    frame
##             1 char   high for the '.fRfile2'    frame
##             1 char   high for the '.fRradbutts' frame
##             2 char   high for the '.fRguide'    frame
##           ~24 pixels high for the '.fRcanvas'   frame.

set CharHeightPx [font metrics fontTEMP_varwidth -linespace]

set minWinHeightPx [expr {24 + (7 * $CharHeightPx)}]

## Add about 28 pixels for top-bottom window decoration. Also add
## about 5 frames x 4 pixels/frame for each of the 5 stacked frames
## and their widgets (their borders/padding).

set minWinHeightPx [expr {48 + $minWinHeightPx}]


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

wm minsize . $minWinWidthPx $minWinHeightPx


## 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'    '.fRfile1'     'fRfile2'
##                '.fRradbutts'   '.fRguide'     '.fRcanvas'
##
##   Sub-frames: none
##+###################################################################

# set BDwidth_frame 2
# set RELIEF_frame raised

set BDwidth_frame 0
set RELIEF_frame flat

frame .fRbuttons  -relief $RELIEF_frame  -bd $BDwidth_frame

frame .fRfile1    -relief $RELIEF_frame  -bd $BDwidth_frame
frame .fRfile2    -relief $RELIEF_frame  -bd $BDwidth_frame

  frame .fRradbutts -relief $RELIEF_frame  -bd $BDwidth_frame
# frame .fRradbutts -relief raised         -bd 2

# frame .fRguide    -relief $RELIEF_frame  -bd $BDwidth_frame
  frame .fRguide    -relief raised         -bd 2

# frame .fRcanvas   -relief $RELIEF_frame  -bd $BDwidth_frame
  frame .fRcanvas   -relief raised         -bd 2


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

pack .fRbuttons \
     .fRfile1 \
     .fRfile2 \
     .fRradbutts \
     .fRguide \
   -side top \
   -anchor nw \
   -fill x \
   -expand 0

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


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


##+#########################################################
## In the '.fRbuttons' FRAME  -
## DEFINE BUTTONS (Exit, Help, reMerge)
## and a LABEL-and-SCALE pair of widgets.
## THEN PACK THEM.
##+#########################################################

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

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

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

button .fRbuttons.buttMERGE \
   -text "$aRtext(buttonMERGE)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief $RELIEF_button \
   -bd $BDwidthPx_button \
   -command {merge2images 0}

label .fRbuttons.labelWEIGHT \
   -text "$aRtext(labelSCALE)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -relief $RELIEF_label_lo \
   -bd $BDwidthPx_label

set WEIGHT_img1 0.5

scale .fRbuttons.scaleWEIGHT1 \
   -orient horizontal \
   -from 0.00 -to 1.00 \
   -resolution 0.01 \
   -digits 3 \
   -length $initScaleLengthPx \
   -variable WEIGHT_img1 \
   -font fontTEMP_SMALL_varwidth \
   -showvalue true \
   -bd $BDwidthPx_scale \
   -relief flat \
   -highlightthickness 0 \
   -width $scaleThickPx \
   -troughcolor $scaleBKGD


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

pack .fRbuttons.buttEXIT \
     .fRbuttons.buttHELP \
     .fRbuttons.buttCOLORBKGD \
     .fRbuttons.buttMERGE \
     .fRbuttons.labelWEIGHT \
     .fRbuttons.scaleWEIGHT1 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+##################################################
## In FRAME '.fRfile1' -
## DEFINE 3 widgets - LABEL, ENTRY, BUTTON.
## THEN PACK THEM.
##+##################################################

label .fRfile1.labelFILE1 \
   -text "$aRtext(labelFILE1)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -relief $RELIEF_label_lo \
   -bd $BDwidthPx_label


set ENTRYfilename1 ""

entry .fRfile1.entFILENAME1 \
   -textvariable ENTRYfilename1 \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -width $initImgfileEntryWidthChars \
   -relief sunken \
   -bd $BDwidthPx_entry

button .fRfile1.buttBROWSE \
   -text "$aRtext(buttonBROWSE)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief $RELIEF_button \
   -bd $BDwidthPx_button \
   -command {get_img1_filename}


## Pack the '.fRfile1' widgets.

pack  .fRfile1.labelFILE1 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfile1.entFILENAME1 \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack  .fRfile1.buttBROWSE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+#######################################################
## In FRAME '.fRfile2' -
## DEFINE 3 widgets - LABEL, ENTRY, BUTTON.
## THEN PACK THEM.
##+#######################################################

label .fRfile2.labelFILE2 \
   -text "$aRtext(labelFILE2)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -relief $RELIEF_label_lo \
   -bd $BDwidthPx_label


set ENTRYfilename2 ""

entry .fRfile2.entFILENAME2 \
   -textvariable ENTRYfilename2 \
   -bg $entryBKGD \
   -font fontTEMP_fixedwidth \
   -width $initImgfileEntryWidthChars \
   -relief sunken \
   -bd $BDwidthPx_entry

button .fRfile2.buttBROWSE \
   -text "$aRtext(buttonBROWSE)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief $RELIEF_button \
   -bd $BDwidthPx_button \
   -command {get_img2_filename}


## Pack the '.fRfile2' widgets.

pack  .fRfile2.labelFILE2 \
   -side left \
   -anchor w \
   -fill none \
   -expand 0

pack .fRfile2.entFILENAME2 \
   -side left \
   -anchor w \
   -fill x \
   -expand 1

pack  .fRfile2.buttBROWSE \
   -side left \
   -anchor w \
   -fill none \
   -expand 0


##+##################################################################
## In the '.fRradbutts' FRAME -
## DEFINE (and PACK) 9 RADIOBUTTON widgets --- for the compass-points:
## n ne e se s sw s nw center.
##+###################################################################

label .fRradbutts.labelRADBUTTS \
   -text "$aRtext(labelRADBUTTS)" \
   -font fontTEMP_varwidth \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -relief $RELIEF_label_lo \
   -bd $BDwidthPx_label

## The 'compassPoint' variable is used for these radiobuttons.

set compassPoint "center"

radiobutton .fRradbutts.radbuttN \
   -text "$aRtext(radbuttN)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "n" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttNE \
   -text "$aRtext(radbuttNE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "ne" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttE \
   -text "$aRtext(radbuttE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "e" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttSE \
   -text "$aRtext(radbuttSE)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "se" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttS \
   -text "$aRtext(radbuttS)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "s" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttSW \
   -text "$aRtext(radbuttSW)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "sw" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttW \
   -text "$aRtext(radbuttW)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "w" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttNW \
   -text "$aRtext(radbuttNW)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "nw" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

radiobutton .fRradbutts.radbuttCENTER \
   -text "$aRtext(radbuttCENTER)" \
   -font fontTEMP_varwidth \
   -anchor w \
   -variable compassPoint \
   -value "center" \
   -selectcolor "$radbuttBKGD" \
   -padx $PADXpx_radbutt \
   -pady $PADYpx_radbutt \
   -relief $RELIEF_radbutt_hi \
   -bd $BDwidthPx_radbutt

button .fRradbutts.buttCLEAR \
   -text "$aRtext(buttonCLEAR)" \
   -font fontTEMP_varwidth \
   -padx $PADXpx_button \
   -pady $PADYpx_button \
   -relief $RELIEF_button \
   -bd $BDwidthPx_button \
   -command {.fRcanvas.can delete all}


## Pack ALL widgets in the '.fRradbutts' frame.

pack .fRradbutts.labelRADBUTTS \
     .fRradbutts.radbuttN \
     .fRradbutts.radbuttNE \
     .fRradbutts.radbuttE \
     .fRradbutts.radbuttSE \
     .fRradbutts.radbuttS \
     .fRradbutts.radbuttSW \
     .fRradbutts.radbuttW \
     .fRradbutts.radbuttNW \
     .fRradbutts.radbuttCENTER \
   -side left \
   -anchor nw \
   -fill none \
   -expand 0

pack .fRradbutts.buttCLEAR \
   -side right \
   -anchor ne \
   -fill none \
   -expand 0


##+######################################################
## In the '.fRguide' frame -
## DEFINE 1 LABEL widget.
## THEN PACK IT.
##+######################################################

label .fRguide.labelGUIDE \
   -text "$aRtext(labelGUIDE)" \
   -font fontTEMP_SMALL_varwidth \
   -justify left \
   -anchor w \
   -padx $PADXpx_label \
   -pady $PADYpx_label \
   -relief $RELIEF_label_lo \
   -bd $BDwidthPx_label \
   -bg "#ffcccc"

## Pack ALL widgets in the '.fRguide' frame.

pack .fRguide.labelGUIDE \
   -side left \
   -anchor nw \
   -fill x \
   -expand 1


##+######################################################
## In the '.fRcanvas' frame -
## DEFINE 1 CANVAS widget with x,y SCROLLBARS.
## THEN PACK THEM.
##+######################################################
## We set '-highlightthickness' and '-borderwidth' to
## zero, to avoid covering some of the viewable area
## of the canvas, as suggested on page 558 of the 4th
## edition of 'Practical Programming with Tcl and Tk'.
##
## We provide x-y scrollbars on the canvas in case either
## of the images is so large (horizontally or vertically)
## that it exceeds the size of the maximum canvas that
## will fit on the monitor screen.
##+###################################################

canvas .fRcanvas.can \
   -width $initCanWidthPx \
   -height $initCanHeightPx \
   -relief flat \
   -highlightthickness 0 \
   -borderwidth 0 \
   -yscrollcommand ".fRcanvas.scrolly set" \
   -xscrollcommand ".fRcanvas.scrollx set"

scrollbar .fRcanvas.scrolly \
   -orient vertical \
   -command ".fRcanvas.can yview"

scrollbar .fRcanvas.scrollx \
   -orient horizontal \
   -command ".fRcanvas.can xview"

##+#######################################################
## PACK the widgets in frame '.fRcanvas'.
##
## NOTE:
## NEED TO PACK THE SCROLLBARS BEFORE THE CANVAS WIDGET.
## OTHERWISE THE CANVAS WIDGET TAKES ALL THE FRAME SPACE.
##+#######################################################

pack .fRcanvas.scrolly \
   -side right \
   -anchor e \
   -fill y \
   -expand 0

pack .fRcanvas.scrollx \
   -side bottom \
   -anchor s \
   -fill x \
   -expand 0

## !!!NEED TO USE '-expand 0' FOR THE X AND Y SCROLLBARS, so that
## the canvas is allowed to fill the remaining frame-space nicely
## --- without a gap between the canvas and its scrollbars.

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

## Alternatives for packing the canvas:
#  -side top \
#  -anchor center \
##
#   -side left \
#   -anchor nw \


##+####################################################
## END OF the DEFINITION OF THE GUI FRAMES-and-WIDGETS.
##+####################################################
## Ready to define BINDINGS and PROCS.
##+####################################################


##+#################################################
## BINDINGS SECTION:
##
##  button1-release bindings on the compass-point radiobuttons,
##  to call on procs 'set_merge_area_limits' and
##  'place_2images_onCanvas' below.
##
##+#################################################

bind .fRradbutts.radbuttN  <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttNE <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttE  <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttSE <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttS  <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttSW <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttW  <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttNW <ButtonRelease-1> {new_compass_point}

bind .fRradbutts.radbuttCENTER <ButtonRelease-1> {new_compass_point}


## BINDINGS on the filename entry fields:

bind .fRfile1.entFILENAME1 <ButtonRelease-3>  {prep_imgsANDcanvas_forMerge}
bind .fRfile1.entFILENAME1 <Return>           {prep_imgsANDcanvas_forMerge}

bind .fRfile2.entFILENAME2 <ButtonRelease-3>  {prep_imgsANDcanvas_forMerge}
bind .fRfile2.entFILENAME2 <Return>           {prep_imgsANDcanvas_forMerge}


## BINDINGS on the canvas:
## 'Flash' img1 and img2 several times, to help see how they
## are positioned on the canvas (and relative to each other).

bind .fRcanvas.can <ButtonRelease-1> {raise-lower_images}


##+#############################################################################
## BINDING on the scale widget:
##     (It can be commented/de-activated so that we can avoid unwanted
##      re-merges for 'intermediate-accidental' changes of the image weighting.
##      We can use the 'Merge' button of the GUI to make sure that we only
##      do the merge processing when we have definitely set the weight we want.)
##+#############################################################################

# bind .fRbuttons.scaleWEIGHT1 <ButtonRelease-1>  {merge2images}


##+######################################################################
## PROCS SECTION:
##
##   'get_img1_filename' - called by the 'Browse...' button beside
##                         the entry field for the image1 file
##
##   'get_img2_filename' - called by the 'Browse...' button beside
##                         the entry field for the image2 file
##
##  'get_chars_before_last' - called by the 'get_*_filename' procs and
##                            the 'checkFile_convertToGIF' proc
##
##  'checkFile_convertToGIF' - called by the 'get_*_filename' procs
##
##   'prep_imgsANDcanvas_forMerge'  - called by button1-release or <Return> on
##                               either of the 2 filename entry fields
##
##   'create_img1_photoID' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'create_img2_photoID' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'set_canvas_size'     - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'set_anchorPointOnCanvas' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'place_2images_onCanvas' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'set_merge_area_limits' - called by the 'prep_imgsANDcanvas_forMerge' proc
##
##   'merge2images'      - called by a click on the '(re)Merge' button  (or, someday?,
##                         by button1-release on the 'scale' widget) --- to
##                         make a new image on the canvas according to the 2 files
##                         and the current settings of the scale and radiobuttons.
##
##   'raise-lower_images'   - called by a button1-release binding on the canvas.
##
##   'new_compass_point'      - called by button1-release bindings on the
##                              compass-point radiobuttons
##
##   'set_background_color'   - called by the 'BackgroundColor' button.
##
##   'update_color_button'    -  called by proc 'set_background_color' and
##                               called in the 'ADDITIONAL-GUI-INITIALIZATION'
##                               section at the bottom of this script.
##
##   'popup_msgVarWithScroll' - used to show messages to the user, such as
##                              the HELPtext for this utility via the 'Help' button.
##
##+#######################################################################



##+#########################################################################
## PROC:  'get_img1_filename'
##+#########################################################################
## PURPOSE: To get the name of an image file (GIF/PNG/JPEG) and put the
##          filename into global var 'ENTRYfilename1'.
##
## CALLED BY: the '-command' option of the file1 'Browse ...' button.
##+#########################################################################

proc get_img1_filename {} {

   global ENTRYfilename1 curDIR1
   # global env

   ## FOR TESTING: (to dummy out this proc)
   # return

   ####################################
   ## Offer selector for an image file.
   ####################################

   set fName [tk_getOpenFile -parent . \
      -title "Select Image-1 file (GIF/PNG/JPEG)" \
      -initialdir "$curDIR1" ]

   ## FOR TESTING:
   #   puts "fName : $fName"

   ####################################################
   ## If the filename from the file selector exits,
   ## put the name in the entry widget for file1, and
   ## extract the current directory name for 'curDIR1'.
   ####################################################

   if {[file exists "$fName"]} {
      set ENTRYfilename1 [checkFile_convertToGIF "$fName"]
      .fRfile1.entFILENAME1 xview end
      set curDIR1 [ get_chars_before_last / in "$ENTRYfilename1" ]
   }

}
## END OF proc 'get_img1_filename'


##+#########################################################################
## PROC:  'get_img2_filename'
##+#########################################################################
## PURPOSE:  To get the name of an image file (GIF/PNG/JPEG) and put the
##           filename into global var 'ENTRYfilename2'.
##
## CALLED BY: the '-command' option of the file2  'Browse ...' button.
##+#########################################################################

proc get_img2_filename {} {

   global ENTRYfilename2 curDIR2
   # global env

   ## FOR TESTING: (to dummy out this proc)
   # return

   ## Offer selector for an image file

   set fName [tk_getOpenFile -parent . \
      -title "Select Image-2 file (GIF/PNG/JPEG)" \
      -initialdir "$curDIR2" ]

   ## FOR TESTING:
   #   puts "get_img_filename2 >  fName : $fName"


   ####################################################
   ## If the filename from the file selector exits,
   ## put the name in the entry widget for file2, and
   ## extract the current directory name for 'curDIR2'.
   ####################################################

   if {[file exists "$fName"]} {
      set ENTRYfilename2 [checkFile_convertToGIF "$fName"]
      .fRfile2.entFILENAME2 xview end
      set curDIR2 [ get_chars_before_last / in "$ENTRYfilename2" ]
   }

}
## END OF proc 'get_img2_filename'


##+######################################################################
## PROC:  'get_chars_before_last'
##+######################################################################
## INPUT:  A character and a string.
##
##         Note: The 'in' parameter below is there only for clarity
##               --- makes the 'call' self-documenting.
##
## OUTPUT: Returns all of the characters in the string 'strng' that
##         are BEFORE the last occurence of the characater 'char'.
##
## EXAMPLE CALL: To extract the directory from a fully qualified file name:
##
## set directory [ get_chars_before_last "/" in "/home/abc01/junkfile" ]
##
##      $directory will now be the string "/home/abc01"
##+######################################################################
## CALLED BY: the 'get_*_filename' procs
##+######################################################################

proc get_chars_before_last { char in strng } {

   set IDXlast [ expr [string last $char $strng ] - 1 ]
   set output [ string range $strng 0 $IDXlast ]

   ## FOR TESTING:
   # puts "From 'get_chars_before_last' proc:"
   # puts "STRING: $strng"
   # puts "CHAR: $char"
   # puts "RANGE up to LAST CHAR - start: 0   end: $IDXlast"

   return $output

}
## END OF PROC 'get_chars_before_last'


##+######################################################################
## PROC:  'checkFile_convertToGIF'
##+######################################################################
## PURPOSE:  For a fully-qualified filename passed as the argument
##           of this proc, this proc checks the file the file
##           to see if it is a GIF file.
##
##           This proc uses the Linux/Unix/BSD/Mac 'file' command
##           to check the file type. For the 3 common types of image
##           files (GIF,PNG,JPEG), the 'file' command typically returns:
##
##           <filename>: GIF image data, version 89a, 351 x 371
##           <filename>: PNG image, 351 x 371, 8-bit/color RGB, non-interlaced
##           <filename>: JPEG image data, JFIF standard 1.01
##
##           If the file is not a GIF, this proc 'tries' to make a GIF file
##           from the specified file, using the ImageMagick 'convert' command.
##
##           This proc 'tries' to put the new file in the same directory
##           with the input file.
##
##           This proc returns the fully-qualified name of the new file
##           --- with a '.gif' suffix. (If the file was indeed a GIF file,
##           this proc returns the filename that was passed to this proc.)
##
## CALLED BY: the 'get_*_filename' procs
##########################################################################

proc checkFile_convertToGIF {INfilename} {

   global aRtext

   set holdFilename "$INfilename"

   ############################################
   ## Get the string to check for file-type.
   ####################################################################
   ## SOME SYNTAX NOTES on running the viewer program via a Tcl 'exec':
   ####################################################################
   ## On page 105 of the 4th edition of 'Practical Programming in Tcl & Tk',
   ## is the following quote on the Tcl 'exec' command:
   ##
   ## "The 'exec' command runs programs from your Tcl script. For example:
   ##      set d [exec date]
   ## The standard output of the program is returned as the value of
   ## the 'exec' command. However, if the program writes to its standard
   ## error channel or exits with a nonzero status code, then 'exec'
   ## raises an error. If you do not care about the exit status, or you
   ## use a program that insists on writing to standard error, then you
   ## can use 'catch' to mask the errors:
   ##   catch {exec program arg arg} result"
   ###################################################################
   ##
   ## Page 83 of the same book says:
   ## "'catch' returns zero if there was no error caught,
   ##   or a nonzero error code if it did catch an error."
   ###################################################################

   set RETcode [catch {set strFILEtype [exec file "$INfilename"]} CatchMsg]

   ## FOR TESTING:
   if {0} {
      puts ""
      puts " PROC 'checkFile_convertToGIF' - message from 'file' command:"
      puts ""
      puts "CatchMsg   : $CatchMsg"
      puts ""
      puts "strFILEtype: $strFILEtype"
   }

   ###########################################
   ## Check for error from the 'file' command.
   ###########################################

   if {$RETcode != 0} {
      set ERRmsg "$aRtext(MSGfilecheck)

$INfilename
"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   ########################################################
   ## Check the 'file' output string for the string 'GIF'.
   ## If a GIF, return the input filename and done.
   ########################################################

   set RETcode [string match {*GIF*} "$strFILEtype"]

   ## FOR TESTING:
   if {0} {
      puts ""
      puts "PROC  'checkFile_convertToGIF' checking for GIF:"
      puts "strFILEtype: $strFILEtype"
      puts "for file"
      puts "INfilename: $INfilename"
      puts "yielded"
      puts "RETcode: $RETcode"
   }

   if {$RETcode == 1} {return "$INfilename"}

   ##################################################################
   ## If we get here, the file was not a GIF.
   ## Try to make a GIF file with ImageMagick 'convert'.
   #################################################################
   ## First, make a name for the new GIF file.
   #################################################################

   # set curDIR  [ get_chars_before_last "/" in "$INfilename" ]
   # set tempFilename  "$curDIR/[clock seconds].gif"

   set preName [ get_chars_before_last "." in "$INfilename" ]
   set tempFilename  "${preName}.gif"

   ## FOR TESTING:
   if {0} {
      puts ""
      puts " PROC 'checkFile_convertToGIF' - filename created for new GIF file:"
      puts ""
      puts "tempFilename: $tempFilename"
   }

   ########################################################
   ## Check that the filename for the new GIF file does not
   ## exist already.
   ########################################################

   if {[file exists "$tempFilename"]} {
      set ERRmsg "$aRtext(MSGfileexists1)

$tempFilename

$aRtext(MSGfileexists2)
"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return

   }

   ########################################################
   ## Issue the 'convert' command.
   ########################################################

   set RETcode [catch {exec convert "$INfilename" -colors 256 "$tempFilename"} CatchMsg]

   ## FOR TESTING:
   if {0} {
      puts ""
      puts " PROC 'checkFile_convertToGIF' - message from 'convert' command:"
      puts ""
      puts "CatchMsg: $CatchMsg"
   }

   ##############################################
   ## Check for error from the 'convert' command.
   ##############################################

   if {$RETcode != 0} {
      set ERRmsg "$aRtext(MSGconvert)

$INfilename

CatchMsg: $CatchMsg
"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   #######################################
   ## Return the name of the new GIF file.
   #######################################

   return "$tempFilename"

}
## END OF PROC  'checkFile_convertToGIF'


##+#####################################################################
## PROC:  'prep_imgsANDcanvas_forMerge'
##+#####################################################################
## PURPOSE: From the 2 image filenames in the 2 entry fields:
##          - Create 'photo' images with IDs 'IDimg1' and 'IDimg2'.
##          - Set canvas size based on max-size of the 2 images.
##          - Set merge-area-parms based on the sizes of the 2 images.
##            and the currently chosen compass-position of the 2 images.
##          - Place the 2 images on the canvas --- without merging.
##
##          Note: The 'merge' will be done by the 'merge2images' proc.
##
## CALLED BY: button1-release or <Return> on
##            either of the 2 filename entry fields
##+#####################################################################

proc prep_imgsANDcanvas_forMerge {} {

   global ENTRYfilename1 ENTRYfilename2 compassPoint aRtext

   ## FOR TESTING: (to dummy out this proc)
   # return

   if {"$ENTRYfilename1" == ""} {
      set ERRmsg "$aRtext(MSGentry1)"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   if {"$ENTRYfilename2" == ""} {
      set ERRmsg "$aRtext(MSGentry2)"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   ## FOR TESTING:
   if {0} {
      puts "PROC 'prep_imgsANDcanvas_forMerge' is starting to execute procs :"
      puts "  - create_img1_photoID"
      puts "  - create_img2_photoID"
      puts "  - set_canvas_size"
      puts "  - set_anchorPointOnCanvas"
      puts "  - place_2images_onCanvas"
      puts "  - set_merge_area_limits"
   }

   ## Put make the 2 'Tk image structures' and load them.

   create_img1_photoID
   create_img2_photoID

   ## Set a canvas size large enough to hold both images.

   set_canvas_size

   ## Set a point on the canvas to which to anchor
   ## images placed on the canvas --- according to the
   ## current, user-selected setting of 'compassPoint'
   ## via the radiobuttons.

   set_anchorPointOnCanvas

   ## Put img1 and img2 on the canvas according to the
   ## canvas 'anchor point' and the value of 'compassPoint'.

   place_2images_onCanvas

   ## Calculate parameters used to determine the location
   ## of the 'merge rectangle' on img1 and img2.
   ## (These parameters are to be used by the 'merge2images'
   ##  proc --- for example, when the user clicks the 'Merge' button.)

   set_merge_area_limits

   ## OK, we have parameters we need to do a 'merge'.
   ## Activate the 'Merge' button.

   .fRbuttons.buttMERGE configure -state normal

}
## END OF PROC 'prep_imgsANDcanvas_forMerge'


##+#####################################################################
## PROC:  'create_img1_photoID'
##+#####################################################################
## PURPOSE:  From image filename #1 in its entry field,
##           create 'photo' image with ID 'IDimg1'.
##
##
## CALLED BY: the 'prep_imgsANDcanvas_forMerge' proc
##+####################################################################

proc create_img1_photoID {} {

   global ENTRYfilename1 IDimg1 aRtext

   ####################################################
   ## Check that the file with filename1 is accessible.
   ####################################################

   if { ![file exists "$ENTRYfilename1"] } {
      set ERRmsg "$aRtext(MSGnotfound1)"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   ###########################################################
   ## Define-and-load a Tk 'photo' image 'structure' for file1.
   ##
   ## (Maybe we should keep using the same imageID ---
   ##  'IDimg1' --- for file1.)
   ###########################################################

   set IDimg1 [image create photo -file "$ENTRYfilename1"]

   ## Alternative:  (so that we do not keep generating new ID's)
   # image create photo IDimg1 -file "$ENTRYfilename1"

   ## FOR TESTING:
   #   puts "PROC 'create_img1_photoID' >  IDimg1: $IDimg1"

}
## END OF PROC 'create_img1_photoID'


##+#####################################################################
## PROC:  'create_img2_photoID'
##+#####################################################################
## PURPOSE:  From image filename #2 in its entry field,
##           create 'photo' image with ID 'IDimg2'.
##
## CALLED BY: the 'prep_imgsANDcanvas_forMerge' proc
##+#####################################################################

proc create_img2_photoID {} {

   global ENTRYfilename2 IDimg2 aRtext

   ####################################################
   ## Check that the file with filename2 is accessible.
   ####################################################

   if { ![file exists "$ENTRYfilename2"] } {
      set ERRmsg "$aRtext(MSGnotfound2)"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   ############################################################
   ## Define-and-load a Tk 'photo' image 'structure' for file2.
   ##
   ## (Maybe we should keep using the same imageID ---
   ##  'IDimg2' --- for file2.)
   #####################################################

   set IDimg2 [image create photo -file "$ENTRYfilename2"]

   ## Alternative:  (so that we do not keep generating new ID's)
   # image create photo IDimg2 -file "$ENTRYfilename2"

   ## FOR TESTING:
   #   puts "PROC 'create_img2_photoID' >  IDimg2: $IDimg2"

}
## END OF PROC 'create_img2_photoID'


##+#####################################################################
## PROC:  'set_canvas_size'
##+#####################################################################
## PURPOSE: Set canvas size based on max-size of the 2 images ---
##          IDimg1 and IDimg2.
##
## CALLED BY: the 'prep_imgsANDcanvas_forMerge' proc
##+#####################################################################

proc set_canvas_size {} {

   global IDimg1 IDimg2 MAXIMGwidthPx MAXIMGheightPx \
      IMG1widthPx IMG1heightPx IMG2widthPx IMG2heightPx

   #############################################################
   ## Get the size of the 2 images currently in memory.
   #############################################################

   set IMG1widthPx  [image width  $IDimg1]
   set IMG1heightPx [image height $IDimg1]

   set IMG2widthPx  [image width  $IDimg2]
   set IMG2heightPx [image height $IDimg2]


   ############################################################
   ## Set the 'max-size', that will hold both images.
   ############################################################

   set MAXIMGwidthPx  $IMG1widthPx
   if { $IMG2widthPx > $MAXIMGwidthPx } {set MAXIMGwidthPx  $IMG2widthPx}

   set MAXIMGheightPx $IMG1heightPx
   if { $IMG2heightPx > $MAXIMGheightPx } {set MAXIMGheightPx  $IMG2heightPx}

   ## FOR TESTING:
   if {0} {
      puts "PROC 'set_canvas_size' has found the dimensions of img1,img2:"
      puts "IMG1widthPx : $IMG1widthPx"
      puts "IMG1heightPx: $IMG1heightPx"
      puts "IMG2widthPx : $IMG2widthPx"
      puts "IMG2heightPx: $IMG2heightPx"
      puts "and found the MAXIMUM x,y dimensions to be :"
      puts "MAXIMGwidthPx : $MAXIMGwidthPx"
      puts "MAXIMGheightPx: $MAXIMGheightPx"
   }

   ###############################################################
   ## Size the canvas to show as much of the 2 images as possible.
   ###############################################################

   .fRcanvas.can configure -width $MAXIMGwidthPx -height $MAXIMGheightPx \
      -scrollregion "0 0 $MAXIMGwidthPx $MAXIMGheightPx"

   ## FOR TESTING:
   if {0} {
      puts "PROC 'set_canvas_size' has set the 'scrollregion' to"
      puts "     0 0 $MAXIMGwidthPx $MAXIMGheightPx"
   }

}
## END OF PROC 'set_canvas_size'


##+####################################################################
## PROC:  'set_anchorPointOnCanvas'
##+####################################################################
## PURPOSE: Set a point (x,y) of the canvas according to the
##          current value of the 'compassPoint' variable.
##
## CALLED BY: the 'place_2images_onCanvas' proc
##+####################################################################

proc set_anchorPointOnCanvas {} {

   global compassPoint MAXIMGwidthPx MAXIMGheightPx \
      canvasANCHORxPx canvasANCHORyPx

   ##########################################################
   ## Get coordinates of the center of the 'max-canvas' area,
   ## for use in setting the 'anchor point' on the canvas.
   ##########################################################

   set MIDCANVASxPx  [expr {int( $MAXIMGwidthPx  / 2.0 )}]
   set MIDCANVASyPx  [expr {int( $MAXIMGheightPx / 2.0 )}]

   ## FOR TESTING:
   if {0} {
      puts "PROC 'set_anchorPointOnCanvas' has found the middle"
      puts "      of the 'max-canvas' to be"
      puts "MIDCANVASxPx: $MIDCANVASxPx"
      puts "MIDCANVASyPx: $MIDCANVASyPx"
   }

   ############################################################
   ## Set the 'anchor point' of the canvas according to the
   ## current, user-selected value of variable 'compassPoint'.
   ############################################################

   if {"$compassPoint" == "nw"} {
      set canvasANCHORxPx 0
      set canvasANCHORyPx 0
      return
   }

   if {"$compassPoint" == "n"} {
      set canvasANCHORxPx $MIDCANVASxPx
      set canvasANCHORyPx 0
      return
   }

   if {"$compassPoint" == "ne"} {
      set canvasANCHORxPx $MAXIMGwidthPx
      set canvasANCHORyPx 0
      return
   }

   if {"$compassPoint" == "e"} {
      set canvasANCHORxPx $MAXIMGwidthPx
      set canvasANCHORyPx $MIDCANVASyPx
      return
   }

   if {"$compassPoint" == "se"} {
      set canvasANCHORxPx $MAXIMGwidthPx
      set canvasANCHORyPx $MAXIMGheightPx
      return
   }

   if {"$compassPoint" == "s"} {
      set canvasANCHORxPx $MIDCANVASxPx
      set canvasANCHORyPx $MAXIMGheightPx
      return
   }

   if {"$compassPoint" == "sw"} {
      set canvasANCHORxPx 0
      set canvasANCHORyPx $MAXIMGheightPx
      return
   }

   if {"$compassPoint" == "w"} {
      set canvasANCHORxPx 0
      set canvasANCHORyPx $MIDCANVASyPx
      return
   }

   if {"$compassPoint" == "center"} {
      set canvasANCHORxPx $MIDCANVASxPx
      set canvasANCHORyPx $MIDCANVASyPx
      return
   }

}
## END OF PROC 'set_anchorPointOnCanvas'


##+####################################################################
## PROC:  'place_2images_onCanvas'
##+####################################################################
## PURPOSE:  Place the 2 images on the canvas --- without merging ---
##           image2 over image1.
##
##           Even if only one image has changed, we (re)place both of
##           them to make sure they are 'registered' properly ---
##           according to the current, user-selected 'compassPoint'
##           and the current size of the canvas.
##
## CALLED BY: the 'prep_imgsANDcanvas_forMerge' proc
##+####################################################################

proc place_2images_onCanvas {} {

   global IDimg1 IDimg2 compassPoint canvasANCHORxPx canvasANCHORyPx

   #################################################
   ## Place image1 on the canvas according to the
   ## user-selected 'compassPoint'.
   #################################################

   .fRcanvas.can create image $canvasANCHORxPx $canvasANCHORyPx \
       -anchor $compassPoint -image $IDimg1 -tag TAGimg1
   update

   ## FOR TESTING:
   if {0} {
      puts "PROC 'place_2images_onCanvas' had done 'create image' at"
      puts "canvasANCHORxPx: $canvasANCHORxPx"
      puts "canvasANCHORyPx: $canvasANCHORyPx"
      puts "with '-anchor' set to"
      puts "compassPoint: $compassPoint"
      puts "for IDimg1: $IDimg1"
   }

   ###################################################
   ## Allow at least a half-second for img1 to be seen,
   ## in case img2 completely covers img1.
   ###################################################

   after 1000

   #################################################
   ## Place image2 on the canvas according to the
   ## user-selected 'compassPoint'.
   #################################################

   .fRcanvas.can create image $canvasANCHORxPx $canvasANCHORyPx \
      -anchor $compassPoint -image $IDimg2 -tag TAGimg2
   update

   ## FOR TESTING:
   if {0} {
      puts "PROC 'place_2images_onCanvas' had done 'create image' at"
      puts "canvasANCHORxPx: $canvasANCHORxPx"
      puts "canvasANCHORyPx: $canvasANCHORyPx"
      puts "with '-anchor' set to"
      puts "compassPoint: $compassPoint"
      puts "for IDimg2: $IDimg2"
   }

}
## END OF PROC 'place_2images_onCanvas'


##+#########################################################################
## PROC:  'set_merge_area_limits'
##+#########################################################################
## PURPOSE: Set merge-area-limits --- based on (1) the sizes of the 2 images
##          and (2) the currently chosen compass-position of the 2 images.
##
##          That is, get the locations of the corners of the parts of img1
##          and img2 (the 'merge rectangles' in img1 & img2) that are to be
##          merged.
##              These locations are to be in units of pixels ---
##              relative to the upper-left corner of each image.
##
##          These limits (coordinates of upper-left & lower-right rectangle
##          corners 'in' each image) are to be used in the x,y loop of
##          the 'merge2images' proc that performs the weighted-average
##          of the pixel colors.
##
## NOTE: I thought I would need the lower-right coordinates (X2,Y2 below),
##       BUT, in writing the 'merge2images' proc, i found that I only
##       needed the upper-left coordinates (X1,Y1 below, on img1,img2).
##       Rather than deleting the 'set' statements for the X2,Y2 coords,
##       I just comment them out --- in case they are handy for future
##       enhancements to this script.
##
## CALLED BY: the 'prep_imgsANDcanvas_forMerge' proc
##+########################################################################

proc set_merge_area_limits {} {

   ## Input variables (from radiobutton widgets or 'set_canvas_size' proc):
   global compassPoint \
          IMG1widthPx   IMG1heightPx   IMG2widthPx   IMG2heightPx \
          MAXIMGwidthPx MAXIMGheightPx

   ## For output (to be shared with the 'merge2images' proc):
   global MIDIMG1xPx MIDIMG1yPx MIDIMG2xPx MIDIMG2yPx \
          MINIMGwidthPx MINIMGheightPx halfMINIMGwidthPx halfMINIMGheightPx \
          IMG1mergeX1px IMG1mergeY1px IMG2mergeX1px IMG2mergeY1px

   ##    IMG1mergeX2px IMG1mergeY2px IMG2mergeX2px IMG2mergeY2px


   ##########################################################
   ## Get coordinates of the 2 centers of img1 and img2,
   ## relative to the upper-left corner of the 2 images.
   ##########################################################

   set MIDIMG1xPx  [expr {int( $IMG1widthPx  / 2.0 )}]
   set MIDIMG1yPx  [expr {int( $IMG1heightPx / 2.0 )}]

   set MIDIMG2xPx  [expr {int( $IMG2widthPx  / 2.0 )}]
   set MIDIMG2yPx  [expr {int( $IMG2heightPx / 2.0 )}]


   ##########################################################
   ## Get MINIMUM dimensions of img1 and img2,
   ## MINIMGwidthPx and MINIMGheightPx.
   ##########################################################

   set MINIMGwidthPx  $IMG1widthPx
   if { $IMG2widthPx < $MINIMGwidthPx } {set MINIMGwidthPx  $IMG2widthPx}

   set MINIMGheightPx $IMG1heightPx
   if { $IMG2heightPx < $MINIMGheightPx } {set MINIMGheightPx  $IMG2heightPx}


   ###############################################################
   ## Get the midpoint of the MINIMUM dimensions of img1 and img2.
   ###############################################################

   set halfMINIMGwidthPx  [expr {int($MINIMGwidthPx/2.0)}]
   set halfMINIMGheightPx [expr {int($MINIMGheightPx/2.0)}]

   ## FOR TESTING:
   if {0} {
      puts "PROC 'set_merge_area_limits' has calculated MID-IMAGE LOCATIONS :"
      puts "MIDIMG1xPx: $MIDIMG1xPx"
      puts "MIDIMG1yPx: $MIDIMG1yPx"
      puts "MIDIMG2xPx: $MIDIMG2xPx"
      puts "MIDIMG2yPx: $MIDIMG2yPx"
      puts "and MINIMUM dimensions (i.e. size of the 'merge rectangle') :"
      puts "MINIMGwidthPx : $MINIMGwidthPx"
      puts "MINIMGheightPx: $MINIMGheightPx"
      puts "and the CENTER-POINT-location in the 'merge rectangle' :"
      puts "halfMINIMGwidthPx : $halfMINIMGwidthPx"
      puts "halfMINIMGheightPx: $halfMINIMGheightPx"
   }


   ################################################################
   ## Set X1-to-X2 and Y1-to-Y2 limits for the 'merge rectangles'
   ## on both img1 & img2 --- for the current, user-selected
   ## 'compassPoint'. I.e. set the coordinates of the corners
   ## of those rectangles.
   ################################################################

   if {"$compassPoint" == "nw"} {
      ## Set the limits on img1:
      set IMG1mergeX1px 0
      set IMG1mergeY1px 0
      # set IMG1mergeX2px $MINIMGwidthPx
      # set IMG1mergeY2px $MINIMGheightPx
      ## Set the limits on img2:
      set IMG2mergeX1px 0
      set IMG2mergeY1px 0
      # set IMG2mergeX2px $MINIMGwidthPx
      # set IMG2mergeY2px $MINIMGheightPx
      return
   }

   if {"$compassPoint" == "n"} {
      ## Set the limits on img1:
      set IMG1mergeX1px [expr {round( ($IMG1widthPx - $MINIMGwidthPx)/2.0 )}]
      set IMG1mergeY1px 0
      # set IMG1mergeX2px [expr {round( ($IMG1widthPx + $MINIMGwidthPx)/2.0 )}]
      # set IMG1mergeY2px $MINIMGheightPx
      ## Set the limits on img2:
      set IMG2mergeX1px [expr {round( ($IMG2widthPx - $MINIMGwidthPx)/2.0 )}]
      set IMG2mergeY1px 0
      # set IMG2mergeX2px [expr {round( ($IMG2widthPx + $MINIMGwidthPx)/2.0 )}]
      # set IMG2mergeY2px $MINIMGheightPx
      return
   }

   if {"$compassPoint" == "ne"} {
      ## Set the limits on img1:
      set IMG1mergeX1px [expr {$IMG1widthPx - $MINIMGwidthPx}]
      set IMG1mergeY1px 0
      # set IMG1mergeX2px $IMG1widthPx
      # set IMG1mergeY2px $MINIMGheightPx
      ## Set the limits on img2:
      set IMG2mergeX1px [expr {$IMG2widthPx - $MINIMGwidthPx}]
      set IMG2mergeY1px 0
      # set IMG2mergeX2px $IMG2widthPx
      # set IMG2mergeY2px $MINIMGheightPx
      return
   }

   if {"$compassPoint" == "e"} {
      ## Set the limits on img1:
      set IMG1mergeX1px [expr {$IMG1widthPx - $MINIMGwidthPx}]
      set IMG1mergeY1px [expr {round( ($IMG1heightPx - $MINIMGheightPx)/2.0 )}]
      # set IMG1mergeX2px $IMG1widthPx
      # set IMG1mergeY2px [expr {round( ($IMG1heightPx + $MINIMGheightPx)/2.0 )}]
      ## Set the limits on img2:
      set IMG2mergeX1px [expr {$IMG2widthPx - $MINIMGwidthPx}]
      set IMG2mergeY1px [expr {round( ($IMG2heightPx - $MINIMGheightPx)/2.0 )}]
      # set IMG2mergeX2px $IMG2widthPx
      # set IMG2mergeY2px [expr {round( ($IMG2heightPx + $MINIMGheightPx)/2.0 )}]
      return
   }

   if {"$compassPoint" == "se"} {
      ## Set the limits on img1:
      set IMG1mergeX1px [expr {$IMG1widthPx - $MINIMGwidthPx}]
      set IMG1mergeY1px [expr {$IMG1heightPx - $MINIMGheightPx}]
      # set IMG1mergeX2px $IMG1widthPx
      # set IMG1mergeY2px $IMG1heightPx
      ## Set the limits on img2:
      set IMG2mergeX1px [expr {$IMG2widthPx - $MINIMGwidthPx}]
      set IMG2mergeY1px [expr {$IMG2heightPx - $MINIMGheightPx}]
      # set IMG2mergeX2px $IMG2widthPx
      # set IMG2mergeY2px $IMG2heightPx
      return
   }

   if {"$compassPoint" == "s"} {
      ## Set the limits on img1:
      set IMG1mergeX1px [expr {round( ($IMG1widthPx - $MINIMGwidthPx)/2.0 )}]
      set IMG1mergeY1px [expr {$IMG1heightPx - $MINIMGheightPx}]
      # set IMG1mergeX2px [expr {round( ($IMG1widthPx + $MINIMGwidthPx)/2.0 )}]
      # set IMG1mergeY2px $IMG1heightPx
      ## Set the limits on img2:
      set IMG2mergeX1px [expr {round( ($IMG2widthPx - $MINIMGwidthPx)/2.0 )}]
      set IMG2mergeY1px [expr {$IMG2heightPx - $MINIMGheightPx}]
      # set IMG2mergeX2px [expr {round( ($IMG2widthPx + $MINIMGwidthPx)/2.0 )}]
      # set IMG2mergeY2px $IMG2heightPx
      return
   }

   if {"$compassPoint" == "sw"} {
      ## Set the limits on img1:
      set IMG1mergeX1px 0
      set IMG1mergeY1px [expr {$IMG1heightPx - $MINIMGheightPx}]
      # set IMG1mergeX2px $MINIMGwidthPx
      # set IMG1mergeY2px $IMG1heightPx
      ## Set the limits on img2:
      set IMG2mergeX1px 0
      set IMG2mergeY1px [expr {$IMG2heightPx - $MINIMGheightPx}]
      # set IMG2mergeX2px $MINIMGwidthPx
      # set IMG2mergeY2px $IMG2heightPx
      return
   }

   if {"$compassPoint" == "w"} {
      ## Set the limits on img1:
      set IMG1mergeX1px 0
      set IMG1mergeY1px [expr {round( ($IMG1heightPx - $MINIMGheightPx)/2.0 )}]
      # set IMG1mergeX2px $MINIMGwidthPx
      # set IMG1mergeY2px [expr {round( ($IMG1heightPx + $MINIMGheightPx)/2.0 )}]
      ## Set the limits on img2:
      set IMG2mergeX1px 0
      set IMG2mergeY1px [expr {round( ($IMG2heightPx - $MINIMGheightPx)/2.0 )}]
      # set IMG2mergeX2px $MINIMGwidthPx
      # set IMG2mergeY2px [expr {round( ($IMG2heightPx + $MINIMGheightPx)/2.0 )}]
      return
   }

   if {"$compassPoint" == "center"} {
      ## Set the limits on img1:
      set IMG1mergeX1px [expr {round( ($IMG1widthPx  - $MINIMGwidthPx )/2.0 )}]
      set IMG1mergeY1px [expr {round( ($IMG1heightPx - $MINIMGheightPx)/2.0 )}]
      # set IMG1mergeX2px [expr {round( ($IMG1widthPx  + $MINIMGwidthPx )/2.0 )}]
      # set IMG1mergeY2px [expr {round( ($IMG1heightPx + $MINIMGheightPx)/2.0 )}]
      ## Set the limits on img2:
      set IMG2mergeX1px [expr {round( ($IMG2widthPx  - $MINIMGwidthPx )/2.0 )}]
      set IMG2mergeY1px [expr {round( ($IMG2heightPx - $MINIMGheightPx)/2.0 )}]
      # set IMG2mergeX2px [expr {round( ($IMG2widthPx  + $MINIMGwidthPx )/2.0 )}]
      # set IMG2mergeY2px [expr {round( ($IMG2heightPx + $MINIMGheightPx)/2.0 )}]
      return
   }

}
## END OF PROC 'set_merge_area_limits'


##+#########################################################
## PROC:  'merge2images'
##+#########################################################
## PURPOSE: Performs the weighted-average of the pixels of
##          'IDimg1' and 'IDimg2' that are in the 'merge area'
##          of the 2 images --- and, for each pair of pixels
##          being processed, assigns the average of the two
##          pixel colors to an appropriate point on the
##          (scrollable) 'max-canvas'.
##
##          Uses the 'WEIGHT_img1' variable of the 'scale'
##          widget to do the color averaging.
##
##          Uses the 'canvasANCHORxPx' , 'canvasANCHORyPx'
##          variables to locate the 'averaged-pixel' on
##          the 'max-canvas'.
##
##          Note: We allow for passing a variable 'x' into
##                this proc in case we ever want to try
##                connecting (passing) the 'WEIGHT_img1' value
##                of the 'scale' widget to this proc ---
##                to try merging the two images as the
##                sliderbar is moved.
##
##                But that would require a really powerful
##                computer (perhaps multi-threading).
##                The 'merge' would probably proceed in a
##                'jerky', 'partially-merged' fashion.
##
## CALLED BY: the '(re)Merge' button
##
##       NOTE: We could also put a button1-release binding
##             on the WEIGHT 'scale' widget, to call on this
##             proc. But that might do merges when the user
##             is not ready --- for example, he/she might
##             want to change the alignment too, before doing
##             the merge. So, for now, we implement 'merge'
##             only via the 'Merge' button.  This has the
##             advantage of keeping 'things' relatively simple.
##+#########################################################

proc merge2images {x} {

   global WEIGHT_img1 IDimg1 IDimg2 compassPoint \
      canvasANCHORxPx canvasANCHORyPx \
      IMG1widthPx IMG1heightPx IMG2widthPx IMG2heightPx \
      MIDIMG1xPx MIDIMG1yPx MIDIMG2xPx MIDIMG2yPx \
      MINIMGwidthPx MINIMGheightPx \
      IMG1mergeX1px  IMG1mergeY1px IMG2mergeX1px IMG2mergeY1px

   ## IMG1mergeX2px IMG1mergeY2px IMG2mergeX2px IMG2mergeY2px


   ## FOR TESTING: (dummy out this proc)
   # return


   ############################################################
   ## Define an (empty) 'IDimg3' 'photo' structure for the 'new'
   ## image and locate it on the canvas at the 'anchor point'.
   ##
   ## NOTE: We can manipulate img1, img2, and img3 (if necessary)
   ##       via the tags --- TAGimg1, TAGimg2, TAGimg3.
   ##       For example, we can do hide/show.
   ############################################################

   set IDimg3 [image create photo]

   ## Alternative:  (so that we do not keep generating new ID's)
   # image create photo IDimg3

   .fRcanvas.can create image $canvasANCHORxPx $canvasANCHORyPx \
       -anchor $compassPoint -image $IDimg3 -tag TAGimg3


   ############################################################
   ## Set the complementary weighting factor, for use in the
   ## loop below.
   ###########################################################

   set WEIGHT_img2 [expr {1.0 - $WEIGHT_img1}]


   #########################################################
   ## Start the loop over the merge region (with dimensions
   ## MINIMGwidthPx and MINIMGheightPx) --- to do the
   ## weighted-averaging of the pixel pairs.
   #########################################################

   for {set j 0} {$j < $MINIMGheightPx} {incr j} {

      for {set i 0} {$i < $MINIMGwidthPx} {incr i} {

         ## Get the pixel coordinates in img1 and img2
         ## for the averaging.

         set x1 [expr {$IMG1mergeX1px + $i}]
         set y1 [expr {$IMG1mergeY1px + $j}]
         set x2 [expr {$IMG2mergeX1px + $i}]
         set y2 [expr {$IMG2mergeY1px + $j}]

         ## Get the RGB-255 colors of those 2 pixels of img1 & img2.

         foreach {R1 G1 B1} [$IDimg1 get $x1 $y1] break
         foreach {R2 G2 B2} [$IDimg2 get $x2 $y2] break

         ## Get the RGB-255 values for the averaged pixel.

         set R3 [expr {round( ($R1 * $WEIGHT_img1) + ($R2 * $WEIGHT_img2) )}]
         set G3 [expr {round( ($G1 * $WEIGHT_img1) + ($G2 * $WEIGHT_img2) )}]
         set B3 [expr {round( ($B1 * $WEIGHT_img1) + ($B2 * $WEIGHT_img2) )}]

         ## Convert the 3 RGB-255 values to a hex-color specification.

         set HEXcolor3 [format "#%02x%02x%02x" $R3 $G3 $B3]

         ## Put the RGB3 color at the appropriate location in IDimg3.

         $IDimg3 put $HEXcolor3 -to $i $j

      }
      ## END OF LOOP  for {set i 0} ...

   }
   ## END OF LOOP  for {set j 0} ...

}
## END OF PROC 'merge2images'



##+#####################################################################
## PROC:  'raise-lower_images'
##+#####################################################################
## PURPOSE: Raises/Lowers img1 and img2, successively, several times
##          --- to help the user see where the common 'merge rectangle'
##          will be on the 2 images, given the current, user-selected
##          value of the 'compassPoint' variable.
##
##          Uses the variable 'Nflashes', which was set above among
##          the 'set' statements for array 'aRtext'.
##
## CALLED BY: a button1-release binding on the canvas.
##+#####################################################################

proc raise-lower_images {} {

   global IDimg1 IDimg2 Nflashes
   # global IDimg3

   ## FOR TESTING:
   #  puts "PROC 'raise-lower_images' just started."

   for {set k 1} {$k <= $Nflashes} {incr k} {
      .fRcanvas.can raise TAGimg1
      update
      after 500
      .fRcanvas.can raise TAGimg2
      update
      after 500
   }

}
## END OF PROC 'raise-lower_images'


##+#####################################################################
## PROC:  'new_compass_point'
##+#####################################################################
## PURPOSE: When the compass-point (image-alignment) has been changed,
##          reposition the 2 images on the canvas and compute new
##          parameters for subsquent 'merge' requests.
##
## CALLED BY: button1-release bindings on the compass-point radiobuttons
##+#####################################################################

proc new_compass_point {} {

   global ENTRYfilename1 ENTRYfilename2 IDimg2 aRtext

   ##########################################################
   ## We first check whether the file entry fields are empty.
   ##########################################################

   if {"$ENTRYfilename1" == ""} {
      set ERRmsg "$aRtext(MSGentry1forAlign)"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   if {"$ENTRYfilename2" == ""} {
      set ERRmsg "$aRtext(MSGentry2forAlign)"
      popup_msgVarWithScroll .topErr "$ERRmsg"
      return
   }

   ###############################################
   ## We clear the images off the canvas, because
   ## we are going to position them according to
   ## the new 'compassPoint' value.
   ###############################################

   .fRcanvas.can delete all

   #############################################################
   ## The following sequence of 4 procs is essentially the bottom
   ## of the 'prep_imgsANDcanvas_forMerge' proc.
   ##
   ## At this point, we are working under the assumption that
   ## img1 and img2 exist and the appropriate canvas dimensions
   ## for the 2 images have been determined.
   ##
   ## The following IDimg2 check is intended to
   ## assure that that assumption is valid.
   ############################################################

   if { ![info exists IDimg2]} {
      ## Put make the 2 'Tk image structures' and load them.
      create_img1_photoID
      create_img2_photoID
      ## Set a canvas size large enough to hold both images.
      set_canvas_size
   }

   set_anchorPointOnCanvas
   place_2images_onCanvas
   set_merge_area_limits
   place_2images_onCanvas

}
## END OF PROC 'new_compass_point'


##+#####################################################################
## 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 2 images will lie.
##
## ARGUMENTS: none
##
## CALLED BY: the 'BackgroundColor' button
##+#####################################################################

proc set_background_color {} {

   global COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex
   # global feDIR_tkguis


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

   #   $feDIR_tkguis/sho_colorvals_via_sliders3rgb.tk \

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

   if { "$TEMPrgb" == "" } { return }

   scan $TEMPrgb "%s %s %s %s" r255 g255 b255 hexRGB

   set COLORBKGDhex "#$hexRGB"
   set COLORBKGDr $r255
   set COLORBKGDg $g255
   set COLORBKGDb $b255

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

   ## Set the color of the canvas.

   .fRcanvas.can config -bg $COLORBKGDhex

   ## Update the background and foreground colors on the
   ## background-color button.

   update_color_button

}
## END OF proc 'set_background_color'


##+#####################################################################
## proc 'update_color_button'
##+#####################################################################
## PURPOSE:
##   This procedure is invoked to update the color and text on the
##   background-color button ---
##   to show current color (and hex value of the color) on
##   the background-color button.
##
##   This proc sets the background color of the button
##   to its current color as set in the 'set_background_color' proc
##   --- and sets foreground color to a
##   suitable black or white color, so that the label text is readable.
##
## Arguments: global color vars
##
## CALLED BY:  proc 'set_background_color'
##             and the additional-GUI-initialization section at
##             the bottom of this script.
##+#####################################################################

proc update_color_button {} {

   global aRtext COLORBKGDr COLORBKGDg COLORBKGDb COLORBKGDhex

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

   .fRbuttons.buttCOLORBKGD configure -bg $COLORBKGDhex

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

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

}
## END OF proc 'update_color_button'


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

proc popup_msgVarWithScroll { toplevName VARtext } {

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

   # bell
   # bell

   #################################################
   ## Set VARwidth & VARheight from $VARtext.
   #################################################
   ## To get VARheight,
   ##    split at '\n' (newlines) and count 'lines'.
   #################################################

   set VARlist [ split $VARtext "\n" ]

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

   set VARheight [ llength $VARlist ]

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


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

   set VARwidth 0

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

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

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

   }
   ## END OF foreach line $VARlist

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


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


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

   catch {destroy $toplevName}
   toplevel  $toplevName

   # wm geometry $toplevName 600x400+100+50

   wm geometry $toplevName +100+50

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

   wm iconname  $toplevName "Note"


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

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

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

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

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

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

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

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


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

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

      ## DO NOT USE '-expand 1' HERE on the Y-scrollbar.
      ## THAT ALLOWS Y-SCROLLBAR TO EXPAND AND PUTS
      ## BLANK SPACE BETWEEN Y-SCROLLBAR & THE TEXT AREA.

      pack $toplevName.scrollx \
         -side bottom \
         -anchor center \
         -fill x  \
         -expand 0

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

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


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

   ##  $toplevName.text delete 1.0 end

   $toplevName.text insert end $VARtext

   $toplevName.text configure -state disabled

}
## END OF PROC 'popup_msgVarWithScroll'


set HELPtext "\
**********  HELP for the Merge-Two-Images Utility *****************

This GUI utility allows the user to select 2 image files, which may
be in GIF or PNG or JPEG format.

The GUI also provides the user with 2 options for merging the images:
    1) a weighting factor for image 1 (a number between 0.0 and 1.0)
and
   2) an image-alignment specification (by 'compass points' ---
      n, ne, e, se, s, sw, w, nw, center).

Note that this utility allows the 2 images selected to be of
different sizes. The 'alignment' radiobuttons on the GUI allow
the user to specify the positioning of the 2 images relative
to each other.

When 2 image filenames are ready, the user can click on either
filename 'entry' field to cause the 2 images to be loaded to the
'canvas' at the bottom of the GUI.

When the 'weight' and 'alignment' options are ready, the user can
click on a 'Merge' button.

Then the two image files are read and colors of corresponding pixels
in a 'merge rectangle' on the 2 images are averaged to create a new
image that is shown on top of the original 2 images.

****************************
TYPICAL OPERATIONAL SEQUENCE:
****************************

STEP 1:

Select the 2 image files to be merged. This is most conveniently done
with the 'Browse...' buttons on the GUI.

STEP 2:

With the 'alignment' radiobuttons on the GUI, select a 'compass point'
which will be used to align the 2 images with respect to each other.

(If the 2 images are the same size, any of the choices should work.)

STEP 3:

As indicated in a brief 'guide' on the GUI, the user can 'right-click'
(with mouse-button-3) on EITHER filename entry field to cause the
2 image files to be read and their images shown on the 'canvas'.

The images will be located on the canvas according to the
user-selected 'alignment' option.

(The 2nd image may completely cover the first, so to be able to check the
alignment of the 2 images, the user can CLICK ANYWHERE ON THE CANVAS
to cause the 2 images to 'flash' over-under each other several times.
Thus the user can see the 'common merge rectangle' of each image that
will be averaged together to get the merged image.)

STEP 4:

Adjust the setting of the 'weight' slider and click on the 'Merge'
button to cause the merge (which occurs in about one second).

Experiment with the 'weight' slider and the 'Merge' button to see
the effect of weighting image1 more or less relative to image2.


**********************
USING THE MERGED IMAGE:
**********************

To keep the GUI relatively simple, there is no 'SaveAs-GIF/PNG/JPEG'
button on the GUI.

A SCREEN/WINDOW CAPTURE UTILITY (like 'gnome-screenshot' on Linux)
can be used to capture the GUI image in a 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 smaller image suitable for a web page or an email.
And the image could be converted from PNG to GIF or JPEG --- for example,
by using the ImageMagick 'convert' command.

The image file could be used with a utility (like the ImageMagick
'convert' command or the 'mtpaint' image editor) to change a color
of the image to TRANSPARENT. Thus one could make a (partially) transparent
GIF or PNG file.


*********************
MAKING ANIMATED GIF's:
*********************

Note that given 2 images, one could make a sequence of images which
could be used to make an animated GIF.  For example:

In addition to the 2 original images, one could make 3 more images
--- using weighting factors 0.25, 0.50, and 0.75. Then --- after
appropriate image capture, image editing, and image conversion ---
the 5 images could be combined to make an animated GIF --- using
a program like ImageMagick 'convert'. Example command:

 convert -delay 250 -loop 0 file1 file2 file3 file4 file5 output_ani.gif

where the delay time of 250 is in 100ths of seconds, giving an
inter-image wait time of 2.5 seconds. The parameter '-loop 0'
indicates that the animated GIF file should be played indefinitely,
rather than stopping after a finite number of cycles.


******************************
How JPEG and PNG are supported (as well as GIF) :
******************************

Running this utility requires the Tcl-Tk 'wish' interpreter to be
available on the user's computer. Versions of the 'wish' interpreter
up through 2014-March did not support reading JPEG-JFIF files ---
and it is not likely that there will be a 'wish' interpreter that
reads JPEG-JFIF files for another 3 or 4 years after that (if ever).

Furthermore, the 8.6 version of the 'wish' interpreter, which was the
first version to support PNG-read, became available in mid-2013.
Many computers with a 'wish' interpreter already installed are
version 8.5 or before --- and hence do not read PNG files (without
having to install a '3rd-party' extension).

Version 8.5 (and before) of the 'wish' interpreter DO support reading
GIF files 'natively'.

So to handle JPEG and PNG files, this utility assumes that the
ImageMagick 'convert' command is available. This utility
uses the 'convert' program to convert JPEG and PNG files to GIF files.
The filenames of the new GIF file(s) that are created appear in the
entry fields of the GUI with a '.gif' suffix. Any new GIF file is
put in the directory with the JPEG/PNG file from which it was made. 

It is the 'new' GIF file(s) that are placed on the canvas and used
when the 'Merge' button is clicked.


***************************************
Quality of converted JPEG and PNG files:
***************************************

Converting a JPEG or PNG file to a GIF file can result in a loss of
image quality --- especially when there are (many) more than 256
color shades in the JPEG or PNG file.

A common effect in these cases is 'color banding' in the converted
image.

For example, 'computer desktop wallpaper' images, which often consist
of gradual gradiations of colors across the large image, are subject
to 'color banding' when converted to GIF files.

And landscape and other nature photographs (usually in JPEG format)
typically consist of many more than 256 colors and result in rather
'grainy'/'aliased' images when they are converted to GIF files.

If/When a version of the Tk 'wish' interpreter becomes available that
'natively' supports both JPEG-JFIF-read and PNG-read, then this utility
could easily be changed to eliminate the use of the 'convert' program.

When version 8.6.x of the 'wish' interpreter becomes more common on
computers than the 8.5 and older versions, then it would become
desirable to change the code in the 'checkFile_convertToGIF' proc of
this utility so that the 'convert' program is not used on PNG files.

The Linux/Unix/BSD/Mac 'file' command is used by the
'checkFile_convertToGIF' proc to check for file-type of the
user-selected image files.

The Linux/Unix 'file' command returns text strings like the following
--- on these 3 types of image files:

   - JPEG image data, JFIF standard 1.01
   - GIF image data, version 89a, 256 x 352
   - PNG image, 1024 x 768, 8-bit/color RGB, non-interlaced


***********************************************
SETTING UP THIS UTILITY FOR EASY ICON-CLICK USE:
***********************************************

The set of files for this utility consists of TWO Tk scripts ---
one that makes the main GUI and one RGB-color-selector Tk script,
for use by a 'BackgroundColor' button on the GUI, to allow for
setting the background color of the 'canvas'.

That pair of Tk scripts could be put in a sub-directory of the
user's home directory --- such as \$HOME/apps/tkMerge2Images.

Then the user can use his/her desktop system (such as Gnome or KDE)
to set up the main Tk script as an icon on the desktop (or in a
desktop 'panel').

Then, whenever the user wants to merge 2 image files, the user can
click on the icon to startup the Tk script.


*****************
STARTUP DIRECTORY for image1 and image2:
*****************

If you want the 'browse' for image filenames to start at a
different directory from the user's home directory,
in the Tk script, you can look for the lines
    
   set curDIR1 \"\$env(HOME)\"
   set curDIR2 \"\$env(HOME)\"

and change them according to nearby examples.
"

##+#####################################################
## ADDITIONAL GUI INITIALIZATION section.
##+#####################################################

##+######################################################
## Set the canvas background color and the color of the
## 'BackgroundColor' button, from the color initialized
## near the top of the script in the COLOR-SCHEME section.
##+######################################################

.fRcanvas.can configure -bg $COLORBKGDhex
update_color_button


##+#################################################################
## Set an initial 'curDIR1' & 'curDIR2'for the 2 get-filename procs.
##+#################################################################

set curDIR1 "$env(HOME)"
set curDIR2 "$env(HOME)"

## FOR TESTING:
   set curDIR1 "[pwd]"
   set curDIR2 "[pwd]"


##+####################################################
## Start with the 'Merge' button disabled. It will be
## enabled by the 'prep_imgsANDcanvas_forMerge' proc.
##+####################################################

.fRbuttons.buttMERGE configure -state disabled

##+#################################################################
## The initialization of a few widget variables is done near the
## top of this script, where the widgets are defined.
## Examples: the scale & radiobuttons variables ---
##           'WEIGHT_img1' and 'compassPoint'.
## Those initializations could be moved here --- to be together,
## rather than scattered through the code.
##+################################################################


INSTALLING THE SCRIPT(s):

This utility consists of two Tk scripts --- the 'merge2images.tk' Tk script above --- and the 'sho_colorvals_via_sliders3rgb.tk' Tk script called in the 'set_background_color' proc.

This 'set of TWO scripts' could be put in a sub-directory of the user's home directory, such as $HOME/apps/tkMerge2Images.

Then the user can use his/her desktop system (such as Gnome or KDE) to set up the main Tk script as an icon on the desktop (or in a desktop 'panel').

Then, whenever the user wants to merge 2 images, he/she can click on the icon to startup the 'merge2images' GUI.


SOME POSSIBLE ENHANCEMENTS

In using this utility over the next year, I may find that I would like to add a few capabilities, such as

1) Add a 'MakeAniGIF' button to make an animated GIF from the 2 images very quickly (in about a second) --- to 'transition' repeatedly, back-and-forth between the two images.

The user would be given several options to specify --- such as number-of-frames and wait-time between frames. Another option could be the utility to use to make the animated GIF. For example, either ImageMagick 'convert' or 'gifsicle' could be used to make the animated GIF file.

After selecting the 2 image files, if the user chose to go with the defaults, making the animated GIF would be as easy as clicking on the 'MakeAniGIF' button.

2) Allow for handling transparency in either image (for GIF and PNG files).

3) Add another, more-general alignment option --- for example, an option similar to the 'center' option, but allowing the user to specify a 'general' point on each image to match up.


IN CONCLUSION

As I have said on several other code-donation pages on this wiki ...

There's a lot to like about a utility that is 'free freedom' --- that is, no-cost and open-source so that you can modify/enhance/fix it without having to wait for someone else to do it for you (which may be never).

A BIG THANK YOU to Ousterhout for starting Tcl-Tk, and a BIG THANK YOU to the Tcl-Tk developers and maintainers who have kept the simply MAH-velous 'wish' interpreter going.