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

I asked this question at Re^4: Tk show image from web (inspired app) but want to supply the current code and avoid junking up the original thread. This program works except it leaks memory because I can't figure out where to call the delete method on the image object. See the end of the code for my attempts and relevant perldoc:
#!/usr/bin/perl =head1 DESCRIPTION View random images from the supplied folder (supports . and ~). Every time you close an image a new random image will open, until the program is stopped. Inspired by "Re^2: Tk show image from web" by IB2017 https://www.perlmonks.org/index.pl?node_id=11108799 =cut use strict; use warnings; use autodie; use Encode; use File::Spec; use MIME::Base64; use Image::Info; use Imager; use Tk; use Tk::Photo; use Tk::JPEG; use Tk::PNG; my $dir = shift || die "Usage: $0 /path/to/images"; die 'Folder not found :-(' unless -d $dir; my $img = shift; # an initial image, for debugging opendir my $dh, glob $dir; my @img = grep /\.(jpe?g|gif|png|bmp)/i, readdir $dh; closedir $dh; die 'No images found, try again!' unless @img; while () { my $file = $img || $img[rand@img]; my $path = File::Spec->catfile($dir,$file); my $info = Image::Info::image_info($path); undef $img if $img; print "$path \n"; if ($info->{color_type} eq 'CMYK') { my $tmpdir = File::Spec->tmpdir; my $imager = Imager->new; $imager->read(file=>$path) or die $imager->errstr; $path = File::Spec->catfile($tmpdir,$file); # Converts CMYK to RGB $imager->write(file=>$path) or die $imager->errstr; } open my $fh, '<', $path; my $data = join '', <$fh>; close $fh; $data = MIME::Base64::encode_base64($data); my $mw = MainWindow->new; $mw->configure(-title => Encode::decode_utf8($file)); my $top = $mw->Frame()->pack(); my $image = $mw->Photo(-data => $data); my $label = $top->Label(-image => $image)->pack(); print $image->width, 'x', $image->height, "\n\n"; # From Tk::Image perldoc: # # CAVEATS # # It's necessary to use the "delete" method to delete an image # object 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 setting to undef is not sufficient. # # $image->delete # # Deletes the image $image and returns an empty string. If there # are instances of the image displayed in widgets, the image won't # actually be deleted until all of the instances are released. # However, the association between the instances and the image # manager will be dropped. Existing instances will retain their # sizes but redisplay as empty areas. If a deleted image is # recreated (with the same name) the existing instances will # use the new image. # $image->delete; # Image area blank MainLoop; # $image->delete; # Segmentation fault: 11 }
Also tried declaring my $image; outside the while loop... Thank you

Replies are listed 'Best First'.
Re: Tk::Image delete method
by bliako (Abbot) on Dec 17, 2019 at 21:17 UTC

    Without knowing too much about Tk, I would think that deleting the image before calling MainLoop is a no-go because you are deleting it while the app is running! And deleting it after the MainLoop (to be called when main window exits) it is a no-go too because by that time a lot of things would have been destroyed.

    I think it would be best if you created a destroy-window button which will callback a function to do all the cleanup for you including deleting memory. Alternatively specify a sub to be called whenever the main window is destroyed, a la Re: $mw->OnDestroy and Dialog Box

    You may have a tiny bit of a scoping problem with your callback not being able to see $image declared inside the while loop.

    bw, bliako

Re: Tk::Image delete method
by jcb (Parson) on Dec 18, 2019 at 00:35 UTC

    Perl/Tk associates images with their corresponding MainWindow objects. Since you are only handling a single window at a time and repeatedly entering Tk's MainLoop, Tk is destroying the Image handles when it cleans up the MainWindow and shuts down. Run the program for a while and see if it leaks memory — I suspect that it does not.

    This is not the normal way to use Tk in Perl — you actually do not have a single callback anywhere in the program and are not fully using Tk's MainLoop, which should normally be the last step of the main script's execution.

    #!/usr/bin/perl use strict; use warnings; use Tk; # ... setup code that prepares the widget hierarchy ... MainLoop; # ... cleanup code as the program is exiting, usually there is nothing + here ... __END__
      Thanks for the example. It made me realize that the while loop is somehow causing the memory of the process to grow by the size of each image. In this version the memory grows and shrinks according to the image size, with no need for delete:
      #!/usr/bin/perl =head1 DESCRIPTION View random images from the supplied folder (supports . and ~). Every time you close an image a new random image will open, until the program is stopped. Inspired by "Re^2: Tk show image from web" by IB2017 https://www.perlmonks.org/index.pl?node_id=11108799 =cut use strict; use warnings; use autodie; use Encode; use File::Spec; use MIME::Base64; use Image::Info; use Imager; use Tk; use Tk::Photo; use Tk::JPEG; use Tk::PNG; my $dir = shift || die "Usage: $0 /path/to/images"; die 'Folder not found :-(' unless -d $dir; my $img = shift; # an initial image, for debugging opendir my $dh, glob $dir; my @img = grep /\.(jpe?g|gif|png|bmp)/i, readdir $dh; closedir $dh; die 'No images found, try again!' unless @img; my $file = $img || $img[rand@img]; my $path = File::Spec->catfile($dir,$file); if (Image::Info::image_type($path)->{file_type} eq 'JPEG') { my $info = Image::Info::image_info($path); if ($info->{color_type} eq 'CMYK') { my $tmpdir = File::Spec->tmpdir; my $imager = Imager->new; $imager->read(file=>$path) or die $imager->errstr; $path = File::Spec->catfile($tmpdir,$file); # Converts CMYK to RGB $imager->write(file=>$path) or die $imager->errstr } } open my $fh, '<', $path; my $data = join '', <$fh>; close $fh; $data = MIME::Base64::encode_base64($data); my $mw = MainWindow->new; $mw->configure(-title => Encode::decode_utf8($file)); my $top = $mw->Frame()->pack(); my $image = $mw->Photo(-data => $data); my $label = $top->Label(-image => $image)->pack(); print $path, "\n", $image->width, 'x', $image->height, "\n\n"; MainLoop; exec 'perl', $0, $dir;

        Could it be because you *re-load* perl and your script every time via that exec? btw how does user exit such a cycle? Or is it meant to be a virus? (re: cookie cookie i need a cookie now)

Re: Tk::Image delete method
by Anonymous Monk on Dec 19, 2019 at 07:46 UTC

    Hi

    Path::Tiny is very convenient shortcut over opendir

    use Path::Tiny qw/ path /; my @img = path( $dir )->children( qr/\.(jpe?g|gif|png|bmp)/i );
      Thanks for the tip, Path::Tiny is a sweet module! I had switched to File::Find and see these are all equivalent:
      use File::Spec::Functions; opendir (my $dh, $dir); @img = map { catfile($dir,$_) } grep /\.(jpe?g|gif|png|bmp)$/i, readdi +r $dh; closedir $dh; use Path::Tiny 'path'; @img = path( $dir )->children( qr/\.(jpe?g|gif|png|bmp)$/i ); use File::Find; find(sub { push @img, $File::Find::name if /\.(jpe?g|gif|png|bmp)$/i && $dir eq $File::Find::dir }, $dir);
      I used File::Find because recursion is so easy, by removing the $dir constraint:
      find(sub { push @img, $File::Find::name if /\.(jpe?g|gif|png|bmp)$/i }, $dir);
      Does Path::Tiny support such recursion? I can't figure it out from the doc. Thanx again.
        > Does Path::Tiny support such recursion?

        Of course it does. The visit method is the most similar to File::Find's way:

        my @img; path($dir)->visit(sub { push @img, $_->stringify if /\.(jpe?g|gif|png|bmp)$/i; }, {recurse => 1});
        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]