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.
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.
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.
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.
---
USING THE MERGED IMAGE:
To keep the GUI relatively simple, there is no 'SaveAs-GIF/PNG/JPEG' button on the GUI --- as seen in the images above.
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.
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?)
#!/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 or other). 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.0 and 1.0 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/alignment' 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 ## ##+########################### ## SINGLE-MERGE OR MULTI-MERGE: ## ## In addition to making one merged image at a time by clicking ## on a 'Merge' button (OR by button1-release bindings on radiobuttons ## or other widgets of the GUI), there is a 'MakeAniGIF' button ## on the GUI. ## ## The 'MakeAniGIF' procedure automatically makes a sequence ## of merged images based on the 2 user-selected images and ## the value of an 'Nframes' entry-field parameter on the GUI. ## ## The 'MakeAniGIF' procedure makes (Nframes - 2) 'merged' GIF ## files by using (Nframes - 2) weighting factors between 0.00 ## and 1.00. Then together with the 2 'original images' the ## (Nframes - 2) 'merged images' are used to make (and display) ## an animated-GIF file consisting of 'Nframes' frames. ## ## A 'Delay' entry-field parameter on the GUI (in units of ## 100ths of a second) is used to set the inter-frame delay ## of the animated GIF. ## ##+######################### ## PLANNED LAYOUT OF THE GUI: ## ## --------------------------------------------------------------------------------------------------- ## Merge 2 Images - with color-weighting & image-alignment options ## [window title] ## --------------------------------------------------------------------------------------------------- ## Pixel-Color Weighting Factor (for image1 ## {Exit} {Help} {reMerge} pixels versus image2 pixels - 0.0 to 1.0): <--------------O-------------> ## [A background-color button may be put between the Help and Merge buttons.] ## ## Img1 Filename (GIF/PNG/JPEG): __________________________________________________ {Browse...} ## ## Img2 Filename (GIF/PNG/JPEG): __________________________________________________ {Browse...} ## ## Image alignment points: O n O ne O e O se O s O sw O w O nw O center ## ## {MakeAniGIF} Nframes(2-20): __ Delay(100ths of a second): ___ O ImageMagick 'convert' O 'gifsicle' ## ## 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 5 times, to confirm alignment. ## [This guide is in a label widget.] ## ## --------------------------------------------------------------------------------- ## | A ## | | ## | | ## | Canvas for displaying and | ## | merging the two images. | ## | | ## | | ## | | ## | | ## | V ## <-------------------------------------------------------------------------------> ## ## ## SKETCH CONVENTIONS for this GUI sketch: ## ## SQUARE-BRACKETS indicate a comment (not to be placed on the GUI) ## BRACES indicate a Tk 'button' widget. ## A COLON indicates that the text before the colon is on a 'label' widget. ## UNDERSCORES indicate a Tk 'entry' widget. ## CAPITAL-O indicates a Tk 'radiobutton' widget. ## CAPITAL-X indicates a Tk 'checkbutton' widget (if any). ## ## A LINE (HYPHENS) WITH AN 'ARROW-HEAD' AT EACH END indicates a Tk 'scale' widget. ## ## A combination of VERTICAL-BAR CHARACTERS AND HYPHEN (or UNDERSCORE) CHARACTERS, ## that outline a RECTANGULAR SHAPE, are used to indicate either a Tk 'listbox' or ## a Tk 'canvas' or a Tk 'text' widget. ## ## SCROLL-BAR 'ARROW-HEADS' (for a 'listbox', 'canvas', or 'text' Tk widget) ## are drawn as follows: ## ## UP ARROW-HEAD is drawn with a CAPITAL-A. ## DOWN ARROW-HEAD is drawn with a CAPITAL-V. ## LEFT ARROW-HEAD is drawn with a LESS-THAN sign. ## RIGHT ARROW-HEAD is drawn with a GREATER-THAN sign. ## ## ## UP-and-DOWN ARROW-HEADS at the right/left of the box shape indicate a VERTICAL SCROLL-BAR there. ## ## LEFT-and-RIGHT ARROW-HEADS at the bottom/top of the box shape indicate a HORIZONTAL SCROLL-BAR there. ## ## The arrow-heads are joined by hyphens, rather than underscores. ## ## This GUI will contain about: ## ## 7 'button' widgets ## 5 'label' widgets ## 1 'scale' widget ## 4 'entry' widgets ## 1 'canvas' widget (with x-y scrollbars) ## 11 'radiobutton' widgets ## 0 'checkbutton' widgets ## 0 'listbox' widgets ## 0 'text' widgets ## ##+########################################### ## METHOD USED to do a 'merge' of the 2 images: ## ## 0) The user selects two image filenames (fully-qualified) via two ## 'Browse...' buttons, to put 2 image-filenames in the two ## filename entry fields. ## ## IMPLEMENTATION IS VIA TWO PROCS: ## 'get_img1_filename' and 'get_img2_filename' ## ## 1) When the user 'right-clicks' on either filename entry field ## (or uses the Return key), the following sequence of operations ## is performed --- to put the 2 images on the canvas. ## ## 1.1) PROCS 'create_img1_photoID' and 'create_img2_photoID': ## ## 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. ## ## 1.2) PROC 'set_canvas_size': ## ## 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. (A background color for the canvas can be applied ## in this proc.) ## ## 1.3) PROCEDURE 'set_anchorPointOnCanvas': ## ## For the currently specified 'compass point', an 'anchor point' ## on the canvas is determined --- to be used to align the ## 2 images on the canvas. ## ## 1.4) PROC 'set_merge_area_limits': ## ## 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 locations on the 2 images are ## determined that can be used to merge the 'common area' on the two ## images. The currently specified 'compass point' is used in making ## those determinations. ## ## 1.5) PROC 'place_2images_onCanvas': ## ## 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. ## ## 2) ALTERING THE MERGE: ## At this point the 'merged image' is not created yet. 'img2' has ## been placed on top of 'img1'. ## ## If the user clicks on the '(re)Merge' button, the proc 'merge2images' ## is executed --- using the current settings of the 'compass point' ## and the 'weight factor'. ## ## PROCEDURE 'merge2images': ## ## The merge parameters determined in Step 1.4 are used to set ## 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 WHERE THE PROCESSING OCCURS ## THAT CREATES THE MERGED IMAGE ON THE CANVAS. ## ## However, rather than taking the defaults --- the user may wish ## to change the 'compass point' or the 'weight factor'. ## ## The user can change the image alignment (via the 'compass' radiobuttons). ## Button1-release on the radiobuttons calls the 'merge2images' proc, ## causing a (new) 'merged image' to be created, using the current ## 'weight factor' setting. ## ## The user can choose a different weight factor (0.0 to 1.0) for a merge --- ## via the slider-button on the 'scale' widget. Then the user can ## click on the '(re)Merge' button to cause a new 'merged image' to ## be created. ## ## (We use a button rather than a button1-release binding on the ## 'scale' widget, because the user might 'fudge around' with ## the slider-button before settling on a weight-factor to use.) ## ## So, after clicking on the '(re)Merge' button or on a 'compass' radiobutton, ## the two user-selected images have been put on the canvas --- one image ## over the other --- and a merged 'common area' image is on top of them. ## ## The user can do a screen-capture (described below) to use ## any part of the images (and background color) on the canvas. ## ## 3) THE FAST MULTI-IMAGE OPTION: ## ## Rather than 'manually' making multiple 'merged' images, using ## various weight-factors, the user can simply click on the ## 'MakeAniGIF' button to QUICKLY make an animated-GIF ## from the 2 user-selected images, based on the 'Nframes' ## and 'Delay' parameters currently set on the GUI. ## ## The animated-GIF file, along with the 'Nframes' individual ## GIF files that were used to make the aniGIF file, are in ## a temporary directory (default /tmp), that can be changed ## by the user. The user can move the aniGIF file (and/or ## the individual GIF files) into another directory. Typically, ## the user will rename the file(s). ## ##+############################################################# ## SOME NOTES ON THE 'IMAGE-PARTS' ON THE CANVAS AFTER A 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. ## ## It is the 'common area' image of the two-images that ## is placed in a 3rd Tk 'photo' image 'structure'. ## It is the 'common area' images that are used to ## make an animated-GIF file via the 'MakeAniGIF' button. ## ## In other words, the 'over-hanging, unmatched' parts of the 2 ## original images are not included in the aniGIF file made ## with the 'MakeAniGIF' button. However, the user could ## 'manually' make an animated-GIF with larger portions of ## the canvas, by using a series of screen captures. ## ##+################################## ## NOTE ON GIF versus PNG versus JPEG (and other image formats): ## (as things stand with Tk in 2014 March) ## ## 1) On JPEG: ## For the 'wish' interpreter of Tk 8.6, 8.5, and older: ## 'image create photo' does not support reading JPEG-JFIF image files. ## To read JPEG files with (what looks like) Tcl-Tk commands, one must ## resort to a Tk 'extension'. ## ## 2) On PNG: ## Tk 'image create photo' command did not support reading PNG files until ## late 2013 --- when version Tk 8.6 of the 'wish' interpreter was released. ## That is, version 8.5 and older of the 'wish' interpreter does not support ## the use of PNG files. ## ## Rather than require a user to install a Tk extension and/or upgrade ## their version of Tcl-Tk to 8.6, this utility assumes that ## the ImageMagick (IM) 'convert' command is available to the user. ## ## The 2 'get_img*_filename' procs use a proc 'checkFile_convertToGIF' ## that uses ImageMagick 'convert' to convert JPEG and PNG files ## (and about 100 other types of image files) to GIF files. ## ## For non-GIF files, it is these 'convert'-ed GIF files that are used by ## 'image create photo' commands to load two Tk 'photo' image 'structures' ## --- 'IDimg1' and 'IDimg1'. ## ## --- ## ## NOTE: The conversion to GIF 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. ## ## Furthermore, 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 --- ## in the 2 'get_img*_filename' procs --- which call a 'checkFile_convertToGIF' ## proc to make a '.gif' from non-GIF image files. ## ##+######## ## CREDITS: ## This Tk script was inspired by a Tk script at the web page ## 'Merging images with transparency' - https://wiki.tcl-lang.org/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 transparent GIF (or PNG) file --- OR ## to make a sequence of transparent GIF's for making a transparent ## ANIMATED GIF 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' ## '.fRalign' ## '.fRanigif' ## '.fRguide' ## '.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 '.fRalign': 9 RADIOBUTTONs for compass-points ## ## - In '.fRanigif': 1 BUTTON, 2 LABEL-and-ENTRY, 2 RADIOBUTTON widgets ## ## - In '.fRguide': 1 LABEL widget ## ## - In '.fRcanvas': 1 (scrollable) CANVAS widget ## ## 3) Define BINDINGS: ## ## - button1-release bindings on the 9 compass-point radiobuttons --- ## to call on two procs: 'set_merge_area_limits' and ## 'place_2images_onCanvas', listed below in the ## PROCS section. ## ## - button3-release and Return-key binding on the 2 filename entry fields ## ## - button1-release on the canvas --- to call on proc 'raise-lower_images' ## to 'flash' img1 & img2 over each other. ## 4) Define PROCS: ## ## Some of the main procs follow. See the comments at the top of the ## PROCS section --- and see the actual procs --- for a few more procs ## that may have been added during development and 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. ## ## 'get_chars_before_last' - called by the 'get_*_filename' procs and ## the 'checkFile_convertToGIF' proc ## ## 'checkFile_convertToGIF' - called by the 2 '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 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 weight-factor scale and ## image-alignment 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. ## ## 'make_aniGIF' - called by the 'MakeAniGIF' button. ## ## '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. ## ## 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. ## Changed by: Blaise Montandon 2014mar16 Add 'thisDIR' variable. ## Changed by: Blaise Montandon 2014mar27 Added 'MakeAniGIF' button in a ## new '.fRanigif' frame --- and ## added a new 'make_aniGIF' proc. ##+####################################################################### ##+####################################################################### ## Set WINDOW TITLES. ##+####################################################################### wm title . \ "Merge 2 Images (GIF/PNG/JPEG/other) - with img-weighting & img-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 '.fRalign' 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 '.fRanigif' frame: set aRtext(buttonANIGIF) "MakeAniGIF" set aRtext(labelNFRAMES) "Nframes(>1):" set aRtext(labelDELAY) "Delay(100ths of a second):" set aRtext(radbuttCONVERT) "ImageMagick 'convert'" set aRtext(radbuttGIFSICLE) "'gifsicle'" ## 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.)" ## For popup messages in proc 'make_aniGIF': set aRtext(ERRMSGconvert) \ "The ImageMagick 'convert' command 'threw an error' on trying to make an \ animated-GIF file from files" set aRtext(ERRMSGanimate) \ "The ImageMagick 'animate' command 'threw an error' on trying to show the \ animated-GIF file" set aRtext(ERRMSGgifsicle) \ "The 'gifsicle' command 'threw an error' on trying to make an \ animated-GIF file from files" set aRtext(ERRMSGgifview) \ "The 'gifview' command (that usually comes with 'gifsicle') \ 'threw an error' on trying to show the animated-GIF file" ## END OF if { "$VARlocale" == "en"} ##+################################################################### ## Set a MINSIZE of the window (roughly). ## ## For WIDTH, allow for a minwidth of the '.fRbuttons' frame: ## several buttons (Exit,Help,BackgroundColor,reMerge), 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 '.fRalign' frame ## 1 char high for the '.fRanigif' 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) Background $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: 4 buttons. ## 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 '.fRalign' frame ## 1 char high for the '.fRanigif' 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 + (8 * $CharHeightPx)}] ## Add about 28 pixels for top-bottom window decoration. Also add ## about 6 frames x 4 pixels/frame for each of the 6 stacked frames ## and their widgets (their borders/padding). set minWinHeightPx [expr {52 + $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' ## '.fRalign' '.fRanigif' '.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 .fRalign -relief $RELIEF_frame -bd $BDwidth_frame # frame .fRalign -relief raised -bd 2 frame .fRanigif -relief $RELIEF_frame -bd $BDwidth_frame # frame .fRanigif -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 \ .fRalign \ .fRanigif \ .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, BackgroundColor, 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 '.fRalign' FRAME - ## DEFINE 9 RADIOBUTTON widgets --- for the compass-points: ## n ne e se s sw s nw center. ## THEN PACK THEM. ##+################################################################### label .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 .fRalign.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 '.fRalign' frame. pack .fRalign.labelRADBUTTS \ .fRalign.radbuttN \ .fRalign.radbuttNE \ .fRalign.radbuttE \ .fRalign.radbuttSE \ .fRalign.radbuttS \ .fRalign.radbuttSW \ .fRalign.radbuttW \ .fRalign.radbuttNW \ .fRalign.radbuttCENTER \ -side left \ -anchor nw \ -fill none \ -expand 0 pack .fRalign.buttCLEAR \ -side right \ -anchor ne \ -fill none \ -expand 0 ##+######################################################### ## In the '.fRanigif' frame - ## DEFINE 1 BUTTON, 2 LABEL-and-ENTRY, 2 RADIOBUTTON widgets. ## THEN PACK THEM. ##+######################################################### ## We could add '+' and '-' buttons on each side of the ## Nframes and DELAY100ths entry fields --- like we did in ## the '.fRgrid' frame of script ## 'tkImageGridWarp_withFixedEdge.tk'. ## But this GUI is already quite 'busy'. ############################################################ button .fRanigif.buttANIGIF \ -text "$aRtext(buttonANIGIF)" \ -font fontTEMP_varwidth \ -padx $PADXpx_button \ -pady $PADYpx_button \ -relief $RELIEF_button \ -bd $BDwidthPx_button \ -command {make_aniGIF} ## FOR 'Nframes' ENTRY: label .fRanigif.labelNFRAMES \ -text "$aRtext(labelNFRAMES)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -padx $PADXpx_label \ -pady $PADYpx_label \ -relief $RELIEF_label_lo \ -bd $BDwidthPx_label set Nframes 3 entry .fRanigif.entNFRAMES \ -textvariable Nframes \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width 3 \ -relief sunken \ -bd $BDwidthPx_entry ## FOR 'Delay' ENTRY: label .fRanigif.labelDELAY \ -text "$aRtext(labelDELAY)" \ -font fontTEMP_varwidth \ -justify left \ -anchor w \ -padx $PADXpx_label \ -pady $PADYpx_label \ -relief $RELIEF_label_lo \ -bd $BDwidthPx_label set DELAY100ths 100 entry .fRanigif.entDELAY \ -textvariable DELAY100ths \ -bg $entryBKGD \ -font fontTEMP_fixedwidth \ -width 4 \ -relief sunken \ -bd $BDwidthPx_entry ## The 'anigifMAKER' variable is used for these radiobuttons. set anigifMAKER "convert" # set anigifMAKER "gifsicle" radiobutton .fRanigif.radbuttCONVERT \ -text "$aRtext(radbuttCONVERT)" \ -font fontTEMP_varwidth \ -anchor w \ -variable anigifMAKER \ -value "convert" \ -selectcolor "$radbuttBKGD" \ -padx $PADXpx_radbutt \ -pady $PADYpx_radbutt \ -relief $RELIEF_radbutt_hi \ -bd $BDwidthPx_radbutt radiobutton .fRanigif.radbuttGIFSICLE \ -text "$aRtext(radbuttGIFSICLE)" \ -font fontTEMP_varwidth \ -anchor w \ -variable anigifMAKER \ -value "gifsicle" \ -selectcolor "$radbuttBKGD" \ -padx $PADXpx_radbutt \ -pady $PADYpx_radbutt \ -relief $RELIEF_radbutt_hi \ -bd $BDwidthPx_radbutt ## Pack the widgets in frame '.fRanigif'. pack .fRanigif.buttANIGIF \ .fRanigif.labelNFRAMES \ -side left \ -anchor w \ -fill none \ -expand 0 ## We could allow 'entNFRAMES' to expand, ## but there seems to be no reason for that. pack .fRanigif.entNFRAMES \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRanigif.labelDELAY \ -side left \ -anchor w \ -fill none \ -expand 0 ## We could allow 'entDELAY' to expand, ## but there seems to be no reason for that. pack .fRanigif.entDELAY \ -side left \ -anchor w \ -fill none \ -expand 0 pack .fRanigif.radbuttCONVERT \ .fRanigif.radbuttGIFSICLE \ -side left \ -anchor w \ -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 .fRalign.radbuttN <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttNE <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttE <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttSE <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttS <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttSW <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttW <ButtonRelease-1> {new_compass_point} bind .fRalign.radbuttNW <ButtonRelease-1> {new_compass_point} bind .fRalign.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. ## ## 'new_compass_point' - called by button1-release bindings on the ## compass-point radiobuttons. If img1 & img2 are ## on the canvas, this proc runs 3 procs (above): ## - set_anchorPointOnCanvas ## - place_2images_onCanvas ## - set_merge_area_limits ## ## Some 'utility' procs: ## ## 'raise-lower_images' - called by a button1-release binding on the canvas. ## ## 'make_aniGIF' - called by the 'MakeAniGIF' button. ## ## '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 ## Clear the canvas (in case it was used previously ## in this session). .fRcanvas.can delete all ## 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: '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 } ## END OF PROC 'new_compass_point' ##+##################################################################### ## 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: 'make_aniGIF' ##+######################################################################### ## PURPOSE: Makes an animated-GIF file by making (Nframes - 2) 'intermediate' ## images from the images currently in 'IDimg1' and 'IDimg2' --- ## and according to the current alignment parameters. ## ## We basically want to calculate (Nframes - 2) weighting factors, ## and make a 'merged image' corresponding to each one --- similar ## to the logic in the proc 'merge2images'. But, unlike 'merge2images', ## this proc does not need to place the temporary images, being ## created in-memory, onto the canvas. ## ## We use weighting factors: ## k/(Nframes - 1), where k = 1,...,Nframes-2 ## ## Note that for k=0 and k=(Nframes-1), this formula gives ## weighting factor 0.0 and 1.0 --- corresponding to img1 and img2. ## ## As we create each image, we write a GIF file from each one ## into $DIRtemp --- numbering the files #2 to #(Nframes - 1). ## We use (k+1) to get the file numbers. That gives a total ## of (Nframes - 2) files. ## ## Also, we write 2 GIF files from 'IDimg1' and 'IDimg2' ---- ## files numbered #1 and #Nframes --- for a total of Nframes files. ## ## We use the ImageMagic 'convert' (or 'gifsicle') command ## to make an animated-GIF file --- in $DIRtemp. ## (That is, we use the GIF-maker program indicated by the ## 'anigifMAKER' radiobutton variable.) ## ## We use the inter-image 'delay' specified in the ## 'DELAY100ths' entry variable. ## ## The resulting animated-GIF file is displayed using an ## aniGIF-viewer program determined from the 'anigifMAKER' ## variable --- 'animate' if 'convert' was used ; ## 'gifview' if 'gifsicle' as used. ## ##+##################### ## HOW TO MAKE THE LOOP: ## ## NOTE that there are a couple of 'natural' ways the looping animated-GIF ## file could be created from the Nframes GIF files: ## 1) Have the animated-GIF go from img1 to img2 (Nframes), ## and suddenly start over at img1 again. ## 2) Have the animated-GIF go from img1 to img2 and back ## to img1 again (Nframes - 1) + (Nframes - 1). ## ## The 2nd method would give a smoother transition throughout ## the loop, so we may use the 2nd method --- and pass ## 2*(Nframes - 1) filenames to 'convert'/'gifsicle'. ## ## img1 and img2 are used once in a loop, at the beginning ## and end --- while the (Nframes - 2) are used twice-each ## during a loop --- for a total of 2 + 2*(Nframes - 2) = ## (2*Nframes) + 2 - 4 = (2*Nframes) - 2 = 2*(Nframes - 1) ## --- confirming the number we got above via different reasoning. ## ## CALLED BY: the 'MakeAniGIF' button ##+##################################################################### proc make_aniGIF {} { ## FOR TESTING: (to dummy out this proc) # return ## FOR INPUT (basic info to make the aniGIF file): global IDimg1 IDimg2 Nframes DELAY100ths anigifMAKER DIRtemp ## FOR INPUT (to locate the 'common merge area' on the 2 images): global IMG1mergeX1px IMG1mergeY1px IMG2mergeX1px IMG2mergeY1px \ MINIMGwidthPx MINIMGheightPx ## FOR INPUT (for error messages): global aRtext ENTRYfilename1 ENTRYfilename2 ## OUTPUT IS THE FILES IN THE 'DIRtemp' DIRECTORY. ## NOT NEEDED: ## IMG1widthPx IMG1heightPx IMG2widthPx IMG2heightPx ## MIDIMG1xPx MIDIMG1yPx MIDIMG2xPx MIDIMG2yPx ########################################################### ## If 'IDimg0' or 'IDimg1' does not exist, exit gracefully. ########################################################### if {![info exists IDimg1] || ![info exists IDimg2]} {return} ############################################################ ## Set a 'middle name' for the files to be put in $DIRtemp. ############################################################ set fileMIDNAME "merge2imgs" ########################################################## ## WRITE the 2 GIF files from IDimg1 and IDimg2. ########################################################## ## We use '-format' to assure that a GIF file is written. ## See pages 629-631 of the 4th edition of ## 'Practical Programming in Tcl & Tk'. ########################################################## ## IN TESTING, IT WAS FOUND: ## Cannot use these 'originals' in the animation, because ## they may not be the same size. We will make images from ## the 'common overlap area' of the 2 original images. ########################################################## # set TEMPfilename1 "$DIRtemp/${fileMIDNAME}_img1.gif" # $IDimg1 write "$TEMPfilename1" -format gif # set TEMPfilename2 "$DIRtemp/${fileMIDNAME}_img${Nframes}.gif" # $IDimg2 write "$TEMPfilename2" -format gif ############################################################ ## Define an (empty) 'IDimgTEMP' 'photo' structure for ## making the sequence of (Nframes - 2) images. ########################################################### image create photo IDimgTEMP ## Alternative: (Make IDimgTEMP a variable rather than a 'literal'.) # set IDimgTEMP [image create photo] ########################################################## ## In a loop from 0 to (Nframes - 1), make Nframes ## GIF files, by making a temporary Tk image 'structure' ## in-memory for each GIF and then writing the file ## like we did for the 2 files above. ########################################################## ## INITIALLY, a loop from 1 to (Nframes - 2) as used --- ## to make (Nframes -2) GIF files. But IN TESTING, it was ## found that the beginning and end images had to be made ## as well, from the 'common overlap area'. ########################################################## set Nframes_2 [expr {$Nframes - 2}] set Nframes_1 [expr {$Nframes - 1}] for {set k 0} {$k <= $Nframes_1} {incr k} { ## Take img1 from a large weight to a small weight. set tempWEIGHT_img1 [expr {1.0 - (double($k) / $Nframes_1)}] set tempWEIGHT_img2 [expr {1.0 - $tempWEIGHT_img1}] ## FOR TESTING: # puts "tempWEIGHT_img1: $tempWEIGHT_img1" # puts "tempWEIGHT_img2: $tempWEIGHT_img2" ######################################################### ## 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 * $tempWEIGHT_img1) + ($R2 * $tempWEIGHT_img2) )}] set G3 [expr {round( ($G1 * $tempWEIGHT_img1) + ($G2 * $tempWEIGHT_img2) )}] set B3 [expr {round( ($B1 * $tempWEIGHT_img1) + ($B2 * $tempWEIGHT_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. IDimgTEMP put $HEXcolor3 -to $i $j } ## END OF THE i-LOOP } ## END OF THE j-LOOP (to make intermediate image $k) ########################################################### ## Write the IDimgTEMP to a PPM file with appropriate name. ## Then IM 'convert' the PPM file to a GIF file with ## appropriate name. ########################################################### ## Since the image may contain more than 256 colors, ## we write a PPM rather than a GIF, to avoid an error from ## the 'IDimgTEMP write "filename" -format gif' command. ########################################################### set TEMPfilenamePPM "$DIRtemp/${fileMIDNAME}_img${k}.ppm" catch {exec rm "$TEMPfilenamePPM"} CatchMsg IDimgTEMP write "$TEMPfilenamePPM" -format ppm ## Empty or delete 'IDimgTEMP'??? NOT NEEDED. set TEMPfilenameGIF "$DIRtemp/${fileMIDNAME}_img${k}.gif" catch {exec rm "$TEMPfilenameGIF"} CatchMsg set RETcode [catch {exec convert "$TEMPfilenamePPM" -colors 256 \ "$TEMPfilenameGIF"} CatchMsg] ## FOR TESTING: if {0} { puts "" puts " PROC 'make_aniGIF' - message from 'convert' command:" puts "" puts "CatchMsg: $CatchMsg" } ############################################## ## Check for error from the 'convert' command. ############################################## if {$RETcode != 0} { set ERRmsg "$aRtext(MSGconvert) $TEMPfilenamePPM CatchMsg: $CatchMsg " popup_msgVarWithScroll .topErr "$ERRmsg" return } } ## END OF THE k-LOOP (to make 'Nframes - 2' GIF files) ################################################### ## The 0,...,Nframes-1 GIF files are now made. ## Let us build the string of filenames to pass to ## 'convert' or 'gifsicle'. ## ## We use the 1,...,Nframes-2 GIF files to make ## the 'middle' of the forward and backward passes. ## ## So that the original 2 images get 'equal time', ## we add a k=0 frame at the beginning and a ## k=Nframes-1 frame at the end of the first pass. ################################################### set GIFfilenames "$DIRtemp/${fileMIDNAME}_img0.gif" for {set k 0} {$k <= $Nframes_2} {incr k} { set GIFfilenames "$GIFfilenames $DIRtemp/${fileMIDNAME}_img${k}.gif" } set GIFfilenames "$GIFfilenames $DIRtemp/${fileMIDNAME}_img${Nframes_1}.gif" for {set k $Nframes_1} {$k >= 1} {incr k -1} { set GIFfilenames "$GIFfilenames $DIRtemp/${fileMIDNAME}_img${k}.gif" } ######################################################## ## Rather than fill the output directory with a lot of ## files with unique names, we keep reusing the same ## name for the animated GIF files created by this proc. ## The user can move it if he/she wants to keep it. ######################################################## set TEMPfilenameAniGIF "$DIRtemp/${fileMIDNAME}_ani.gif" ######################################################## ## If $anigifMAKER = 'convert', ## use ImageMagick 'convert' to make the animated-GIF file ## and use ImageMagick 'display' to show it. ######################################################## if {"$anigifMAKER" == "convert"} { ################################################## ## Run the 'convert' command. ## (The 'eval' is needed, so that $$GIFfilenames ## is not treated as ONE argument.) ################################################## catch {exec rm "$TEMPfilenameAniGIF"} CatchMsg set RETcode [catch {eval exec convert -delay $DELAY100ths -loop 0 \ $GIFfilenames "$TEMPfilenameAniGIF"} CatchMsg] ############################################## ## Check for error from the 'convert' command. ############################################## if {$RETcode != 0} { set ERRmsg "$aRtext(ERRMSGconvert) $ENTRYfilename1 $ENTRYfilename2 RETcode: $RETcode CatchMsg: $CatchMsg " popup_msgVarWithScroll .topErr "$ERRmsg" return } ################################################## ## The animated GIF file seems to be created. ## Show it with the ImageMagick 'animate' command. ################################################## set RETcode [catch {exec animate "$TEMPfilenameAniGIF" &} CatchMsg] ############################################## ## Check for error from the 'animate' command. ############################################## if {$RETcode != 0} { set ERRmsg "$aRtext(ERRMSGanimate) $TEMPfilenameAniGIF RETcode: $RETcode CatchMsg: $CatchMsg " popup_msgVarWithScroll .topErr "$ERRmsg" return } } ## END OF if {"$anigifMAKER" == "convert"} ######################################################## ## If $anigifMAKER = 'gifsicle', ## use 'gifsicle' to make the animated-GIF file --- ## and use 'gifview' (that usually comes with 'gifsicle') ## to show the aniGIF file. ######################################################## if {"$anigifMAKER" == "gifsicle"} { ############################################## ## Run the 'gifsicle' command. ############################################## catch {exec rm "$TEMPfilenameAniGIF"} CatchMsg set RETcode [catch {eval exec gifsicle --delay $DELAY100ths --loopcount 0 \ $GIFfilenames > "$TEMPfilenameAniGIF"} CatchMsg] ############################################## ## Check for error from the 'gifsicle' command. ############################################## if {$RETcode != 0} { set ERRmsg "$aRtext(ERRMSGgifsicle) $TEMPfilename1 $TEMPfilename2 RETcode: $RETcode CatchMsg: $CatchMsg " popup_msgVarWithScroll .topErr "$ERRmsg" return } ################################################## ## The animated GIF file seems to be created. ## Show it with the 'gifview' command. ################################################## set RETcode [catch {exec gifview "$TEMPfilenameAniGIF" &} CatchMsg] ############################################## ## Check for error from the 'gifview' command. ############################################## if {$RETcode != 0} { set ERRmsg "$aRtext(ERRMSGgifview) $TEMPfilenameAniGIF RETcode: $RETcode CatchMsg: $CatchMsg " popup_msgVarWithScroll .topErr "$ERRmsg" return } } ## END OF if {"$anigifMAKER" == "gifsicle"} } ## END OF PROC 'make_aniGIF' ##+##################################################################### ## 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 thisDIR # global feDIR_tkguis set TEMPrgb [ exec \ $thisDIR/sho_colorvals_via_sliders3rgb.tk \ $COLORBKGDr $COLORBKGDg $COLORBKGDb] ## 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 --- or about 100 other formats. 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 'right 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 images are processed: colors of corresponding pixels in a 'common 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 alignment 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 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: ********************** There is no 'SaveAs-GIF/PNG/JPEG' button on the GUI --- partly to keep an 'already busy' GUI from being any 'busier' (and to keep an 'already long' script from being any 'longer'). A SCREEN/WINDOW CAPTURE UTILITY (like 'gnome-screenshot' on Linux) can be used to capture the GUI image in a PNG file, say. 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 (such as the background color of the canvas, if the canvas background were not totally cropped out of the image) to TRANSPARENT. Thus one could make a (partially) transparent GIF or PNG file. ********************* MAKING ANIMATED GIF's ('manually'): ********************* 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. ********************* MAKING ANIMATED GIF's (the automated way): ********************* In addition to making one merged image at a time by clicking on a 'Merge' button, there is a 'MakeAniGIF' button on the GUI --- for making a sequence of merged images, and putting them together in an animated-GIF file. The 'MakeAniGIF' procedure automatically makes a sequence of merged images based on the 2 user-selected images and the value of an 'Nframes' entry-field parameter on the GUI. The 'MakeAniGIF' procedure makes (Nframes - 2) 'merged' GIF files by using (Nframes - 2) weighting factors between 0.00 and 1.00. Then together with the 2 'original images' the (Nframes - 2) 'merged images' are used to make (and display) an animated-GIF file consisting of 'Nframes' frames. A 'Delay' entry-field parameter on the GUI (in units of 100ths of a second) is used to set the inter-frame delay of the animated GIF. By default, this utility uses the ImageMagick 'convert' command to make an animated GIF from the two user-selected image files --- using the 'Nframes' and 'Delay' parameters on the GUI. Alternatively, the user can use the 'gifsicle' command by changing the 'convert'-or-'gifsicle' radiobuttons setting on the GUI. So that the user does not have to navigate to the temporary directory to view the GIF file(s), the animated GIF is IMMEDIATELY shown to the user in animated mode. If ImageMagick 'convert' was used, the animated-GIF is shown with the ImageMagick 'animate' command. Both commands usually come together in the ImageMagick package. If 'gifsicle' was used, the animated-GIF is shown with the 'gifview' command, which often comes with 'gifsicle'. If the animated-GIF file is usable, the user can navigate to the temporary directory (defaulted to /tmp) and find the '_ani.gif' file there. Move it and/or rename it. ****************************** 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. Essentially all versions of the Tcl-Tk 'wish' interpreter (going back to releases before the year 2000) support reading image data (pixel values) from GIF files. The situation for JPEG files (and for PNG files, and most other image file formats) is different. 1) On JPEG: For the 'wish' interpreter of Tk 8.6 (first 'production' release in 2013), of 8.5.x (releases around 2005 to 2012), and older releases: None of these releases support reading JPEG-JFIF image files. To read JPEG files with (what looks like) Tcl-Tk commands, one must install a Tk 'extension'. 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). 2) On PNG: The Tk 'wish' interpreter did not support reading PNG files until around 2013 --- when version Tk 8.6 of the 'wish' interpreter was officially released. That is, version 8.5 and older of the 'wish' interpreter does not support the use of PNG files. 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). Rather than require a user to install a Tk extension and/or upgrade their version of Tcl-Tk to 8.6, this utility assumes that the ImageMagick (IM) 'convert' command is available to the user. When the user 'right clicks' on either image-file entry field, the procedures that read the 2 image files use the program ImageMagick 'convert' to convert JPEG and PNG files (and about 100 other types of non-GIF image files) to GIF files. For non-GIF files, it is these 'convert'-ed GIF files that are used to put image data on the 'canvas' and to do the 'merge' processing. So to handle JPEG and PNG files (and other non-GIF image files), this utility assumes that the ImageMagick 'convert' command is available. When this utility uses the 'convert' program to convert non-GIF 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 --- and with the same 'middle name' as the original file(s). Any 'new' GIF file is put in the directory with the non-GIF file from which it was made. If the user has no other use for the new '.gif' file, he/she can delete it later. It is the data from the 'new' GIF file(s) that is 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 may 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. You can also change the set DIRtemp \"/tmp\" statement, nearby --- to change the storage directory for the GIF and animated-GIF files that are created via the 'MakeAniGIF' button. " ##+##################################################### ## ADDITIONAL GUI INITIALIZATION section. ##+##################################################### ##+##################################################### ## Set 'thisDIR' to the directory containing this script ## --- for use in the 'set_background_color' proc. ##+##################################################### ## FOR TESTING: # puts "argv0: $argv0" set thisDIR "[file dirname $argv0]" ##+###################################################### ## 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 initial values of the 'MakeAniGIF' parameters. ######################################################### # set Nframes 3 # set DELAY100ths 100 set Nframes 5 set DELAY100ths 50 ##+################################################################# ## Set DIRtemp', for use by the 'make_aniGIF' proc. ##+################################################################# set DIRtemp "/tmp" ##+################################################################# ## Set an initial 'curDIR1' & 'curDIR2'for the 2 get-filename procs. ##+################################################################# set curDIR1 "$env(HOME)" set curDIR2 "$env(HOME)" # set curDIR1 "$env(HOME)/IMAGE_CAPTURE" # set curDIR2 "$env(HOME)/IMAGE_CAPTURE" # set curDIR1 "/data/images" # set curDIR2 "$env(HOME)/images" ## 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.
uniquename 2014mar29 UPDATE
In the 'Possible Enhancements' section above, I mentioned that I might add a 'MakeAniGIF' button to the GUI. I have done that by adding a new '.fRanigif' frame to contain the button and associated parameter-prompting widgets --- and by adding a new 'make_aniGIF' proc.
I have replaced the original code above with the new code.
Here is an image of the GUI with the new 'MakeAniGIF' button.
Note the prompt for 'Nframes' and 'Delay' (in 100ths of a second) --- as well as 2 radiobuttons to allow a choice between using the ImageMagick 'convert' command or the 'gifsicle' program to make the animated GIF.
In testing, I continued with an image of the 'cute kid' above, and tried a merge into a 'Mickey' image, yielding the following animated GIF. The processing from the time of clicking on the 'MakeAniGIF' button to the appearance of the GIF running in an ImageMagick 'animate' program window was about 10 seconds on my medium-powered PC.
Some people might expect a kid to grow up to be a hero someday, so I tried merging the 'cute kid' with a 'super-hero' image.
Note that in these two cases, both the 'Mickey' image and the 'super-hero' image were somewhat wider than the 'cute kid' image. (I had shrunk the original 'Mickey' image and the original 'super-hero' image to have the same height as the 'cute kid' image --- to get images of approximately the same size.)
I defaulted to the 'center' alignment option --- and the 'make-animated-GIF' process extracted the 'common merge area' of the two images to make the series of 'weighted-merge' images for the animated GIF.
---
Of course, you can use same-sized images, but with the alignment options, you don't really have to go to a lot of extra work to make 2 same-sized images.
In some cases, however, you might be starting from 2 same-sized images --- such as the two 300x300 images that made the following animated-GIF.
Note that these two coin images were originally in PNG files, but, as described in a 'non-GIF image formats' section above, this utility automatically uses the ImageMagick 'convert' command to make a GIF file from a non-GIF file --- and then performs all processing with the data from the 2 GIF files, including:
* making Tk 'photo' image 'structures', in-memory, from the data
* making a merged image from the 2 'photo' images, via the '(re)Merge' button
* making an animated-GIF file via the 'MakeAniGIF' button.
---
Here is an animated GIF that gives an example of transitioning between two 'toon' images.
The transition from one image to another is, admittedly, not ideal. To make the transition smoother, we will need a Tcl-Tk 'morphing' utility. That is on my 'to do' list. :-)
---
Here is an animated GIF that gives an example of transitioning between a cartoon image and a photograph-like drawing.
Although it took only about a second or two to make a single merged image from the small-to-medium-sized image pairs used above, it took on the order of 10 to 20 seconds to make each of these animated GIF's.
---
Underneath the covers (GIF's to PPM's)
I was 'blindsided' in testing by the 'too many colors' error from the 'make_aniGIF' proc when it was executing a command like
IDimgTEMP write "$TEMPfilename" -format gif
to make one of the GIF files to be used in making the animated-GIF file. That is, I was trying to make a sequence of GIF files with which to make an animated-GIF using ImageMagic 'convert'.
I had forgotten that the process of averaging the colors from pixels in two different GIF files, with no more than 256 colors in each, will (generally) result in an image with more than 256 colors.
To write a file containing more than 256 colors, I resorted to writing PPM (Portable Pixel Map) files --- since that is a 'write-format' that is 'natively' supported by Tcl-Tk (including version 8.5) and the PPM format will support 256x256x256 colors for each pixel.
NOW the 'Nframes' PPM files that are created are used to make the animated-GIF file.
---
Some timing data for a single merge (of large images)
In the process of testing for the new 'MakeAniGIF' button, I accumulated timing data for the '(re)Merge' button, which makes a single, 'weighted' image from 2 given images.
When the 2 images were about 250x250, it took about 1 second to do the merge on my medium-powered PC. For 500x500, as expected, it took about 4 seconds. And for two 1000x1000 pixel images, it took about 16 seconds.
For making an animated-GIF from two images about 1000x1000 in size, one can expect the process to complete within about 40 seconds --- if you use only a few frames.
If you are running on a current (2014) high-end Intel 'i7' processor, you can probably halve these estimates.
---
Some other 'fun-with-images' scripts that I have donated to this wiki:
* ImageMagnets - a Tk GUI for image processing
* Pointillate Image --- using Tk 'photo' image files (GIF,PGM,PPM,PNG)
* tkImageGridWarp - GIF/PNG/JPEG/other - using a barymetric technique on triangles