Include image data in your Perl script or module

@kobame asks “What is a recommended method of storing binary data in the __DATA__ section?

Following that principle, @DavidO posted an answer recommending Base64 encoding the binary data, and using MIME::Base64 to decode it at run time.

That is, of course, a correct way to do this.

But, there is another way using just Perl builtins.

For this example, I am going to use a 249 byte PNG image (QR code for νu42). First, take a hex dump of the image file, and append it to your Perl source file. For this, I used xxd:

C:\...> xxd -ps qrcode.png >> bundler.pl

My Perl module files tend to end with:

__PACKAGE__;
__END__

’cause, you know, any true value will do and it’s cute.

So, the hex dump goes right after the __END__ marker.

You can then decode this hex dump using Perl’s pack function:

sub load_data {
    my $bin;
    while (my $line = <DATA>) {
        $line =~ s/\s+\z//;
        $bin .= pack 'H*', $line;
    }
    $bin;
}

Of course, this being Perl, there is more than one way to do it. You could also use the oft-forgotten hex function:

sub load_data {
    my $hex = do { local $/; <DATA> };
    join '', map chr(hex), $hex =~ /([[:xdigit:]]{2})/g;
}

This is shorter, cuter, but I am assuming it has a much larger memory footprint. Of course, one could further shorten, or obfuscate this in a variety of ways, but that’s not the point of this post.

A much better way to handle any whitespace that might sneak into the script or module file, however, is to just read the data, and then remove all whitespace as it should consist solely of hexadecimal digits:

my $png = pack 'H*',
     map { s{\s+}{}g; $_ }
     do { local $/; scalar <DATA> }
;

And, of course, if your perl is version 5.14 or later, you can take advantage of the new s///r alternative which gives you a fresh string. That looks more elegant, but creates a copy of the string we just read in:

my $png = pack 'H*', map s{\s+}{}gr, do { local $/; scalar <DATA> };

The particular method you choose probably wouldn’t matter when you are decoding something less than 4Kb only once in your program, especially compared to loading MIME::Base64 (if your project didn’t already need it) but the slurpers ought not to be used with large inputs.

And, for reference here’s a script which you can use to save the aformentioned QR code on your computer. I am assuming you are not going tack the hex dump of a full length DVD onto your script, so slurping should be OK.

#!/usr/bin/env perl

use strict;
use warnings;

my $png = load_qr_from_data();
binmode STDOUT;
print $png;

sub load_qr_from_data {
    pack 'H*', map s{\s+}{}gr, do { local $/; scalar <DATA> };
}

__END__
89504e470d0a1a0a0000000d49484452000000d8000000d80103000000b3
eebfae00000006504c5445000000ffffffa5d99fdd000000ae4944415478
5eddd5410a80301403d1dcffd2153304bbf10213a4fce6fd8d2098f31f87
85bc13bb6b2cc67b525f8dc74203add1599f17ad462b34daf587c66384f9
6a2c76857e1789a51e0e6ea17758bba020a7c33810869d16dbb5d6797787
05df5a193118c59082bbc532caba89c30e6d4a7de023b1ec3de1645b1223
4cfb70d9765848cb64ddf1d8f70d23bb692cd7ef7f414cd630a22aaba6cc
9068accdca34740e23a70f2bac4aec370a7b0057273b7cbba8fcf7000000
0049454e44ae426082

PS: No comments for now, but if you wish, you can discuss this post on /r/perl.

PPS: When I was originally writing this post, I was confused, and assumed, without checking, that MIME::Base64 is not a core module. Module::Corelist tells me it’s been in the core since 5.007003. Thanks to @ikegami on Stackoverflow for pointing this out.

PPPS: It turns out, I was even more confused than I realized at the time I was writing this. There was a much better way to handle the slurpy case as further discussion on Stackoverflow showed. Updated the post to use that rather than using hex.