Discipulus has asked for the wisdom of the Perl Monks concerning the following question:

Hello monks!

I'm writing a Tk program to choose and copy photos choosing among hundreads of them.

It is almost complete and usable but when I've used it with a very big list of files i've noticed the program was eating my memory and finally become impossible to reproduce further images.

The original error generated was

      Tk::Error: not enough free memory for image buffer at [path]/lib/Tk/Image .pm line 21.

The original code that produced the above error is a bit complicated because i preload a number of photos to render them faster, it scale them to a customizable size (using modules), it handle rotation looking into EXIF tags..

So even if i was almost sure to had cleared all unneeded variables I decided to look at some classic author's example before claiming that Tk was eating my memory.

I've found Tk-thumbnail-viewer and i tried it loading seven pics of 3.5 Mb each one: the program occupy ~58Mb after had loaded thumbs and ~225Mb after i've clicked all 7 thumbs!

So I prepared a little test program reproducing the memory leak: on start it consumes ~7Mb but end at 7th photos with ~222Mb.

Looking at the process it seems sometimes it release some Mb of memory. Im working with Tk 804.032 on strawberry Perl 5.14.2

here the test program

#!/usr/bin/perl use warnings; use strict; use Tk; use Tk::JPEG; use Tk::Pane; use File::Spec; use Data::Dump; my $glob = $ARGV[0]||'./*.jpg'; my @files; &build_list($glob); ########################################################### my $mw = new MainWindow (); $mw->geometry("100x50+0+0"); $mw->Button(-text => "nex picture", -command => sub{ &next_pic; } )->pack(); my $phwin = $mw->Toplevel(); # default empty image # see http://www.perlmonks.org/?node_id=535837 my $image = $phwin->Photo(-file => '' ) or die $!; my $photo_label; #fill mainframe with default screen setup_pane(); $mw->MainLoop; ###################################################################### +########## sub next_pic { my $pic_file = shift @files; print "processing [$pic_file]\n"; $image->blank; $image = $phwin->Photo(-file => $pic_file ); $photo_label->configure(-image => $image ); } ############################################################# # see http://www.perlmonks.org/?node_id=535837 sub setup_pane{ my $pane = $phwin->Scrolled('Pane', Name => 'Main Display', -width => 1000, -height =>1000, -background => 'black', -scrollbars => 'osoe', -sticky => 'n', )->pack(-side => "left", -anchor => "n", -fill=>'both',-expand=>1); # the global! $photo_label = $pane->Label(-image => $image, -background =>'black' )->pack(-side => 'top', -anchor => 'n', -fill => 'both', -expand => 1, ); } ###################################################################### +########## sub build_list{ my $glob=shift; print "received [$glob]\n"; map { push @files,File::Spec->file_name_is_absolute($_) ? $_ : File::Spec->rel2abs($_); } glob($glob); print "$_\n" for @files; }

How can i inspect the memory used by the program?

How can i prevent memory usage increase while cycling my photos?

I'm missing some Tk related best practice?

L*

There are no rules, there are no thumbs..
Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

Replies are listed 'Best First'.
Re: Tk photo display: memory never released
by Anonymous Monk on Jul 04, 2016 at 09:46 UTC
      no permanent growth with this one
      #~ $image = $phwin->Photo(-file => $pic_file ); $image->read($pic_file, -shrink );
        thanks Anonymous Monk,

        so the only way to efficiently load into a Tk::Photo object it is giving to it a filename to read ?

        What I can do if i want to manipulate the image before displaying it? I've used in the original program GD and Image::Resize to handle rotation, for example, but this implies that i'll later pass the photo's data as MIME::Base64 encoded string into the -data => part of the Tk::Photo configure.

        Thanks for your help

        L*

        There are no rules, there are no thumbs..
        Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
      thanks for the links you provided

      L*

      There are no rules, there are no thumbs..
      Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
Re: Tk photo display: memory never released (not leaking solution feeding -data to Tk::Photo)
by Discipulus (Canon) on Jul 05, 2016 at 08:22 UTC
    Ok i've done some experiments last night and, even if I do not relly know how, it seems I've found a working, not leaking solution.

    The previous not leaking solution was $photo->blank; $photo->read($image); but when you read into a Tk::Photo object you cannot manipulate the image, because Tk::Photo it is not so powerfull.

    So i tried every trick i found on the net to free the Tk::Photo used memory; see CAVEATS about TK::Image (docs suggests that TK::Photo inherits from TK::Image).

    Basically the Tk::Photo object is created emtpy using the zentara's trick: $phwin->Photo(-file => "" )

    In the next_pic sub the file is loaded into a GD object, another GD object is created with scaled dimensions and the big one is copyresized into the little one (I tried to modify the big one directly but it seems this approach was more memory hungry)

    After freed the big original GD object, the Tk::Photo object is configured to use an undef file and a -data from the resized GD object (encoding the GD->jepg() data using MIME::Base64::encode )

    So even if -data is involved the following program at first pic (~3Mb average size) consumed ~23Mb of memory and at 100th pic the memory usage was just ~31Mb.

    I will be very happy to ear some insight from wise Tk users

    #no leak tk-memtest07.pl #!/usr/bin/perl use warnings; use strict; use Tk; use Tk::JPEG; use Tk::Pane; use File::Spec; use GD; use MIME::Base64; my $glob = $ARGV[0]||'./*.jpg'; my @files; &build_list($glob); ########################################################### my $mw = new MainWindow (); $mw->geometry("100x50+0+0"); $mw->Button(-text => "nex picture", -command => sub{ #for (1..100){ # uncomment this line to test 100 pi +cs &next_pic; #} # uncomment this line to test 100 pi +cs } )->pack(); my $phwin = $mw->Toplevel(); # default empty image # see http://www.perlmonks.org/?node_id=535837 my $tk_ph_image = $phwin->Photo(-file => '' ) or die $!; my $photo_label; # fill mainframe with default screen setup_pane(); next_pic(); $mw->MainLoop; ###################################################################### +########## sub next_pic { my $pic_file = shift @files; print "processing [$pic_file]\n"; # http://search.cpan.org/~srezic/Tk-804.033/pod/Image.pod # It's necessary to use the "delete" method to delete an image obje +ct and # free memory associated with it. Just using a lexical variable for + storing # the image object and letting the variable to go out of scope or s +etting to # undef is not sufficient. # # $tk_ph_image is a Tk::Photo object $tk_ph_image->delete if $tk_ph_image->blank; # load original pic file in GD my $gd_image = GD::Image->new($pic_file); # find out smaller dimensions used to create the resized image my $small_w = int($gd_image->width * 0.3); my $small_h = int($gd_image->height * 0.3); # create the resized but still empty GD image my $resized = GD::Image->new($small_w,$small_h); # copy from source into resized on $resized->copyResized($gd_image,0,0,0,0, $small_w, $small_h, $gd_image->width, $gd_image->height); # free the original GD image $gd_image = undef; # configure the existing (still empty) Tk::Photo object # i found somewhere the '-file => undef' part but I do not remember + where.. $tk_ph_image->configure( -file => undef, -data => MIME::Base64::encode($resized->jp +eg()) ); # configure the Tk::Label to use the Tk::Photo as image $photo_label->configure(-image => $tk_ph_image ); # system 'tasklist | grep perl'; # debug memory usage # see also: # http://www.perlmonks.org/?node_id=403597 # http://www.perlmonks.org/?node_id=537705 } ############################################################# # see http://www.perlmonks.org/?node_id=535837 sub setup_pane{ my $pane = $phwin->Scrolled('Pane', Name => 'Main Display', -width => 400, -height =>400, -background => 'black', -scrollbars => 'osoe', -sticky => 'n', )->pack(-side => "left", -anchor => "n", -fill=>'both',-expand=>1); # the global! $photo_label = $pane->Label(-image => $tk_ph_image, -background =>'black' )->pack(-side => 'top', -anchor => 'n', -fill => 'both', -expand => 1, ); } ###################################################################### +########## sub build_list{ my $glob=shift; print "received [$glob]\n"; map { push @files,File::Spec->file_name_is_absolute($_) ? $_ : File::Spec->rel2abs($_); } glob($glob); print "$_\n" for @files; }

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.