Mar 03

WxPerl: Converting images for use as embedded XPM in your Perl code

Category: Linux,Perl   — Published by tengo on March 3, 2010 at 5:51 am

Writing applications in Perl with WxPerl for GUI layout is nice. Decorating your apps with images is even nicer. Now when you go the extra mile to package your apps as compiled binaries, it can be regarded as messy to have the used images as external dependencies outside your code.

One solution around this is to embed your used images in the source code, so the images' pixel data is read in as any other line of code in your work. As straight-forward as this sounds, in reality it can give you quite a headache to make it work.

As you can read in this thread on Perlmonks related to image creation from embedded data, it can be done via the newFromXPM constructor. Duncan Elliott similarly added some thoughts on the topic. Further reading includes this bug report related to App::Wx::PodEditor, or Videobox's images2res.pl utility script (no direct link, sorry).

Each approach is a bit different. An elegant solution which I sadly couldn't get to work is embedding binary image data, for example a GIF, as base64 encoded strings into a Module, and then loading it via IO::File into Wx::Image or Wx::Bitmap. How this fails can be read on Perlmonks.

The solution I could get to work is emebedding XPM data-arrays into a module and reading these arrays in reference via Wx::Bitmap's newFromXPM() constructor. However, this approach generates quite large.pm libraries as all compression is removed from the resulting XPM, so comments on optimization welcome!

Below my very basic convert_images.pl utility script. You will need to adapt some things to work in your environment:
#!/usr/bin/perl

use strict;
use warnings;

use Wx ':everything';

opendir(my $dh, "images") || die "can't opendir: $!";
my @dir = readdir($dh);
closedir $dh;

my $vars;
for(@dir){
# we use GIF images as source, as PNGs, in some versions, seem to make trouble for Wx...
next unless $_ =~ /\.gif$/;

print "Converting $_ to XPM\n";

my @fname = split(/\./,$_);
pop(@fname);
my $fname = join("\.",@fname);

Wx::InitAllImageHandlers();
my $bmp = Wx::Bitmap->new("images/$_", wxBITMAP_TYPE_GIF);

my $ok = $bmp->SaveFile("images/$fname.xpm", wxBITMAP_TYPE_XPM);

if(!$ok){
print "Error saving $fname.xpm (check Wx::Bitmap creation success, or save problems) \n";
}else{
open(my $dat, "images/$fname.xpm") || die("Could not open file! images/$fname.xpm $!");
my @raw_data=<$dat>;
close($dat);

my @filtered;
for(@raw_data){
next if $_ =~ /^\/\*/;
next if $_ =~ /^static/;
next if $_ =~ /^\}/;

$_ =~ s/\@/\\@/g;
$_ =~ s/\$/\\\$/g;
$_ =~ s/\&/\\&/g;
$_ =~ s/\%/\\%/g;

push(@filtered,$_);
}

my $data = join('',@filtered);
$vars .= "\$images->{$fname}\t= [". $data ."];\n\n";
}
}

# cleanup
opendir(my $dh, "images") || die "can't opendir: $!";
@dir = readdir($dh);
closedir $dh;
for(@dir){
next unless $_ =~ /\.xpm$/;

print "Cleaning up $_\n";
unlink("buildres/images/$_");
}

## output
open(my $fh, '>', "Images.pm") or die $!;
print $fh "package Images;

#
# Warning: This lib was auto-generated by convert_images.pl, manual changes will be lost!
#

use strict;
use warnings;

sub InitImages {
my \$images = {};

$vars

return \$images;
}

1;";
close($fh);

exit;