Perl-Visualize
view release on metacpan or search on metacpan
Visualize.pm view on Meta::CPAN
Polyglot image L<http://search.cpan.org/src/JNAGRA/Perl-Visualize-1.02/examples/v-piet.gif>
Note that the program we embed is very simple. It simply calls the
Piet::Interpreter with itself (C<$0>) as the argument. This example
is interesting because it actually interprets the same GIF file twice
- and in two different ways. The first time the GIF file is
interpreted as a perl program by the perl interpreter. This causes
the embedded perl code to execute which in turn calls the Piet
interpreter. The Piet interpreter then reads the GIF file again - but
this time it considers the actual image in the GIF file and interprets
that according to the rules of Piet.
The examples that we have looked at so far do not require
Perl::Visualize to be installed in order for it to be run once the GIF
files have been generated - indeed the resulting GIF files are as
portable as perl. The next example we look at does require
Perl::Visualize to be installed because we generate executable images
on the fly.
=head2 Yet Another 99 Bottles of Beer and the Wall
Printing out the hundred stanzas of "Ninety-nine bottles of beer on
the wall" song is another common example of programming tradition.
Over 550 ways of printing out this song has been recorded in over 100
programming languages and archived at
L<http://99-bottles-of-beer.ls-la.net>. For our I<piece de
resistance>, we will write a self replicating version of the "99
bottles of beer" program. But because it is sometimes difficult to
understand just how much beer that is, we will write our program as a
executable self replicating image using Perl::Visualize.
Instead of producing the entire song at once we will design our
program so that every time it is run it will print out a verse of the
song. It will then modify itself so that it is primed for the next
verse. Further more, we would like its picture representation to
reflect the number of bottles of beer currently on the wall.
Let us start with the easiest problem - that of printing out each
stanza.
sub printMessage {
my($number) = @_;
my($prev) = $number+1;
$number = $number < 1 ? "No bottles"
: $number == 1 ? "1 bottle"
: "$number bottles";
$prev = $prev < 1 ? "No bottles"
: $prev == 1 ? "1 bottle"
: "$prev bottles";
print <<BOTTLES;
$prev of beer on the wall
$prev of beer on the wall
Take one down dash it to the ground
$number of beer on the wall
BOTTLES
}
This subroutine prints out one verse of the song given the number of
bottles that remain. Next we need some code to produce a picture of a
wall with beer bottles on it. For this we will use, Image::Magick.
Image::Magick is a toolkit for editing a large variety of image
formats programmatically. We will use it to generate our GIF images on
the fly.
sub drawWall {
my($image, $width,$height) = @_;
for my $y ( 0..3 ) {
for my $x ( 0..($width/10) ) {
my $warn = $image->Draw ( primitive=>'Rectangle',
points=>"@{[($x - ($y%2)*.5)*10]},
@{[$height - $y*5]}
@{[($x - ($y%2)*.5)*10 + 10]},
@{[ $height - $y*5 - 5]}",
fill=>'red' );
warn $warn if $warn;
}
}
}
sub drawBottle {
my($image, $x,$y) = @_;
my $warn = $image->Draw ( primitive=>'Rectangle',
points=>"$x,$y
@{[$x+5]},@{[$y-10]}",
fill=>'brown');
warn $warn if $warn;
my $warn = $image->Draw ( primitive=>'Polygon',
points=>"@{[$x+2]},@{[$y-13]}
$x,@{[$y-10]}
@{[$x+5]},@{[$y-10]}
@{[$x+3]},@{[$y-13]}
@{[$x+5]},@{[$y-10]}" );
warn $warn if $warn;
}
sub drawBottles {
my($bottles, $image, $width,$height) = @_;
for my $bottle_number ( 1..$bottles ) {
my $x = ($bottle_number + .5 ) * $width / ($bottles+2);
drawBottle $image, $x, $height - 20;
}
}
Now for the crucial part. We need to make the program have access to
its own source. Note however that the source will in fact be embedded
inside a GIF so we cannot necessarily simply open ourselves as a file
in an attempt to copy and edit our contents. Instead we will use
techniques for writing quines to embed the source code in the program
itself. We embed most of the code as a string in C<$_>, eval it then
we edit C<$_> so that it is initialized for one less beer bottle.
Finally, we embed the edited C<$_> in a GIF file.
#!/usr/bin/perl
$_=<<'CODE';
use strict;
use Perl::Visualize;
use Image::Magick;
sub printMessage {...}
sub drawWall {...}
sub drawBottle {...}
sub drawBottles {...}
my $width = 600;
my $height = 100;
my $bottles = 5;
my $image = Image::Magick->new(size=>"${width}x$height");
my $warn;
$image->ReadImage('xc:white');
printMessage $bottles;
drawWall($image, $width, $height);
drawBottles($bottles, $image, $width, $height);
$warn = $image->Write('99.gif');
warn $warn if $warn;
__END__
eval $_;
s/^(my \$bottles = )(\d+)/$1.($2?$2-1:$2)/em;
m/^__END__(.*)/ms;
Perl::Visualize::paint ( '99.gif', '99.gif', "\$_=<<'CODE';\n${_}CODE".$1);
CODE
eval $_;
s/^(my \$bottles = )(\d+)/$1.($2?$2-1:$2)/em;
m/^__END__(.*)/ms;
Perl::Visualize::paint ( '99.gif', '99.gif', "\$_=<<'CODE';\n${_}CODE".$1);
Polyglot image L<http://search.cpan.org/src/JNAGRA/Perl-Visualize-1.02/examples/99.gif>
=head1 HOW IT ALL WORKS
The choice of GIF as the image format to use was mostly in response to
a challenge that it could not be done. Certainly, several other image
formats appear to be lend themselves more easily to being made into
polyglots. In particular, it is worth noting that PBM and XPM image
formats - because of their ASCII like headers, end of line conventions
and use of # to introduce comments - are almost trivial to use to
embed polyglots, not merely using Perl, but also in C, Python and a
handful of other languages.
In fact the first image polyglots the author created were perl
embedded in black and white XPM images. However, neither the XPM not
PBM formats are particularly prolific except on Unix platforms. They
are also generally quite large and used more as an intermediate
language than a target language. Much more importantly, making image
polyglots is almost entirely a recreational exercise and only worth
doing if there at least a few challenges along the way.
=head2 GIF file format
The GIF file format was originally proposed in 1989 by Unisys as a way
of reducing the amount of bandwidth taken up by image transfers.
Several aspects of the encoding was controlled by a patent - one which
with some jubilation expired on 20 June, 2003.
In order to successfully build a polyglot we have to be able to shift
gears mentally between programming in Perl and maintaining consistency
with the GIF specification. The GIF standard is very particular about
the order and interpretation of every byte in the header of a GIF
file. Since perl is comparatively more lenient, let us being with a
GIF file and try to alter it into also being a valid perl program. We
begin by taking a look at a few lines of a GIF image:
00000000: 4749 4638 3961 3000 3000 e300 0000 0000 GIF89a0.0.......
00000010: abab ab99 9999 4545 45de dede 2121 21cc ......EEE...!!!.
( run in 1.844 second using v1.01-cache-2.11-cpan-39bf76dae61 )