Richard Suchenwirth -- Tk images (bitmap, photo, and whatever the future may bring) are handy and indispensable for fancy displays. This page shows how to enjoy them with even fewer problems (as pop up every now and then in news:comp.lang.tcl ).
DO DELETE UNWANTED IMAGES:
It is easy to create an image, but in more complex applications large numbers of images may hang around cramming the memory (so-called "leaks" in Tcl would mostly come from such images). So, make it a rule to delete those images when you no longer need them. In an extreme case, you can sanitize an interp of all images with
foreach name [image names] { image delete $name }
Such extreme hygiene can be handy in restoring the state of long-running processes.
Also, images are memory objects. You can display the same image at various places of your GUI. No need to create it more than once, especially not in a loop.
Zarutian 2005-07-13: Here is a way to delete all unused images from thie interpreter.
foreach name [image names] { if {![image inuse $name]} { image delete $name } }
DON'T FILL photo IMAGES PIXEL BY PIXEL! It looks intuitive, but is extremely slow to write
for {set x 0} {$x<$xmax} {incr x} { for {set y 0} {$y<$ymax} {incr y} { $photo put $color -to $x $y } }
Rather, build up the data as a list of rows, each being a list of colors, and feed them to $photo put in one go: set data {{red yellow green blue} {white black orange purple} ...}
$photo put $data -to 0 0
SH: If you still want to draw pixel by pixel and you know how large your image becomes, first draw the left bottom pixel to allocate the memory for the whole image. If you don't do that, each new pixel to draw results in a newly allocation of memory, that's slow. Another way is to specify the size of the image when creating it.
You can also speed up drawing, by first deleting the image from the canvas, then drawing the pixels and then recreating it in the canvas.
DON'T GIVE AN IMAGE NAME -- TAKE ONE!
You may give an image a name at creation time, but maybe you shouldn't -- because a command with that name is created and might silently overwrite the original command or proc, be that "open", "close", or "menu" (DKF: you get a particularly interesting crash if you name the image "." as this completely blows Tk away within the current interpreter or crashes the whole app, depending on the version you are using) If you don't give an image name, image will give you a constructed name which will very probably not interfere with a proc. Example: don't say
image create photo menu -arg ...
but say
set img(menu) [image create photo -arg ...]
and use $img(menu) (which will look like image47) ever after.
DKF: I have edited the above to use an array; I find that more visually pleasing and less likely to interfere with anything else that is lying around...
WHD: For transient images, I agree--let Tk give you an image name and save it in a variable. For images like button icons that remain for the life of the program, and can likely be used multiple times, giving them an explicit name can be extremely useful. I usually put all of them in a single namespace, e.g., ::icon at the application level, or ::mylib::icon at the library level. But--remember to use the fully qualified name in the image create command.
DON'T USE -data !
Jan Nijtmans wrote in comp.lang.tcl: Even faster should be not to use the "-data" option, but the "<imageName> put" command. This is almost equivalent, but it prevents the storage of a copy of the binary data in memory. Just replace the line:
image create photo $pic -data $data
by the two lines:
image create photo $pic $pic put $data
ulis 2003-07-05: -data and put are NOT almost equivalent.
(CJL: put now treats its input as raw pixels only if it matches no known format)
See the header note at Serializing a photo.
Ro notes:
If you use the line:
image create photo $pic -data $data
then it will throw an error if you are reading from a file of length zero. But if you use the two lines:
image create photo $pic $pic put $data
then it will not throw an error if you are reading from a file of length zero. But it will prevent the storage of a copy of the binary data in memory, as noted above by Jan Nijtmans.
The moral of the story is: If you use the two latter lines, don't forget to check if the file is empty first, because otherwise, it will resize your picture to no size and not throw an error. This can be annoying if your picture is displaying in its own window and then all of a sudden you have a very small window to manipulate and you have to depend on your window manager to resize the window or close it.
This was tested on a GNU/Linux system with X11R6 4.0.1a
DKF notes:
Actually, image put can take any sort of image data that -data can. It uses the same extensible image parsing engine underneath.
BHE: A lot of these ideas are really helpful. I tried to do something like this:
namespace eval test { image create photo theImage } image inuse test::theImage; # returns 0 image inuse theImage; # returns 1
hmm. I wanted to create the image within the test namespace so it wouldn't destroy "::theImage". What about this?
namespace eval test { image create photo test::theImage } image inuse test::theImage; # returns 0! why? namespace eval test { image inuse theImage; # returns 0 image inuse test::theImage; # returns 1 }
What's going on here? I gave up and switched to using the image name generated as recommended above, storing the name as an array value instead:
namespace eval test { variable images set images(theImage) [image create photo] }
MG: Images are created with the name you give them, without respect to namespaces. To do what you meant, you need to do:
image create photo ::test::theImage
According to the docs for Tk 8.4, [image inus] returns 1 if the image is being used by a widget currently, and 0 otherwise (not whether it exists or not), which most wouldn't be anyway right after creation.
BHE: One other thing. I needed a way to "cut" an area out of an image and save it as a new image, leaving a transparent block. image doesnt have anything like that - the closest thing is $dst copy $src -from x1 y1 x2 y2, but the area remains in the source obviously. You could do this:
set src [image create photo] set bgcolor black # load the source ... let's say it's 32x32 set dst [image create photo] $dst copy $src -from 5 5 10 10 $src put $bgcolor -to 5 5 10 10
But what do you do if you want to leave a transparent block after "cutting"? For that matter, how can you simply erase an area instead of an entire picture? ([$src blank] will delete everything.)
You can use a blank photo and the "$image put" command with -compositingrule as "set" instead of "overlay"
set eraser [image create photo] $src copy $eraser -to 5 5 10 10 -compositingrule set image delete $eraser
yadav:
Hi i am new to TclTk..And now i am stuck at an important stage...i wanna optimize a ram read from Tk which displays that ram read as Picture...The Ram contains binary data.....So now i have a working code but very slow..............Would welcome any suggestions regarding this.. can mail at [email protected]
Thanks
MGH (sorry, didn't realize MH was taken, no idea how to "log-out" of the wiki...): While working on drawing and updating a grid-based image, I found the following: 800x600 Tk photo, attached to a canvas, fill generates a string of 800x600 pixel values with the given color string, put_data uses $photo put $data -to 0 0 to draw the string to the photo ("fill"ing it with that color)
% time {fill black} 2131 microseconds per iteration % time {put_data} 33850435 microseconds per iteration % time {fill \#000000}; # Note: \#000000 = black in RGB 1681 microseconds per iteration % time {put_data} 242814 microseconds per iteration
Yes, that's almost 140x FASTER by using the RGB notation. And changes my project from "UGH - have to write custom X code to do pixel drawing" to "Tk photo is fast enough - I can use it!"
Did not expect THAT much of a difference. Especially given that the advice above to use data arranged in scanlines uses literal color values, which will definitely offset any advantage of faster processing.
Linuxpeter - 2015-02-23 16:01:07
When I try to save the contents of a canvas to a picture, using either of the above-described methods (e.g. image create -data mycanvas), I just get a black picture with nothing on it. The canvas has actually a picture and some text on it.
AMG: I was not aware that it was possible to create an image from a canvas. First, you probably meant to say [image create photo] because you'll need a photo-type image to capture color data. But beyond that, photo's -data option doesn't allow window names. Here's what the manual says:
No mention of the canvas.
MG The Img package allows capturing windows by specifying a path as -data (with, I think, -type window, though it's been a while). IIRC, the main reason you end up with a totally black picture when doing that is if the window is obscured; try doing
set c [canvas .c -bg yellow] pack $c raise $c update set img [image create photo -data $c]
to make sure the canvas is mapped and properly displayed before you try and create the image.
ak - 2015-02-23 19:45:40
The tklib package canvas::snap (https://core.tcl.tk/tklib/doc/trunk/embedded/www/tklib/files/modules/canvas/canvas_snap.html ) demonstrates how to snapshot canvas contents into a photo. It requires the Tkimg package, specifically img::window.
linuxpeter - 2015-03-16 00:53:47
Thanks, ak, for your mention of canvas::snap which has solved my problem so far. But another inconsistency I've found is colour name management in Image/Photo when I try to copy certain pixels from one image to another:
set colourname [image1 get $x $y]
gives my the pixel colour in RGB (like 175 175 175), whereas
image2 put $colourname -to $x $y
doesn't work because image put requires a colour name in hex code (like #00000).
How can I solve this?
MG suggests
format #%02x%02x%02x 175 175 175
though you may also want to raise a ticket to suggest allowing a list of RGB values - it makes sense for Tk to accept as input anything it returns as output.
RS: here it is, nicely wrapped:
% proc rgb2 {r g b} {format #%02x%02x%02x $r $g $b} % rgb2 {*}[image1 get 10 10] #cc3300