Version 39 of Tk image Dos and Don'ts

Updated 2015-02-23 16:01:07 by Linuxpeter

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 ).

See Also

Images with transparency and plain images
ICONS
one source of images to use as Tk icons. Perhaps people will add other sources for this sort of thing.

Do's

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

Dont's

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.

  • -data is cooked and receives a structured image (as a gif image)
  • put is raw and receives lines of pixels

(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.