Machine-generated memes in Perl

The Cat's Meow

Article from Issue 157/2013
Author(s):

It started off harmlessly enough with a few funny pictures of cats, but eventually it became the Internet phenomenon par excellence. It's no joke: Perl gives you some great tools for building and customizing memes yourself.

Summer time is intern time: As always during the summer months, my employer has taken on some college students, while we old guys scratch our balding pates and wonder how weird young academics can get. This year, the interns' sense of humor was on full display: Every presentation was adorned with image macros [1] for the purpose of amusement, either as static pictures or animated GIFs in infinite loops.

What started with "I Can Has Cheezburger?" [2] with cuddly kittens – the Lolcats – and cheeky sayings has morphed into an established part of culture known as the "meme." Take an expressive image and put an orthographically or a grammatically challenged saying (Lolspeak) in the header and footer using the Impact font – and you have a ready-made joke (Figure 1).

Figure 1: Modern classic – a Lolcat with orthographically questionable Lolspeak. © Niccolò Capanti.

The word "meme" originates from ancient Greek, where "mimema" means something imitated, and evolutionary biology uses the term to describe the process of social transmission of cultural values. On the Internet, memes are a mass phenomenon that spreads virally by email, chat, or on social networks (Figure 2).

Figure 2: A cartilaginous fish with a house – a typical example of an image macro meme.© Sarah Charlesworth.

Machine vs. Manual

Using a graphics program like GIMP, image macros are quickly made in the conventional manner, but a Perl script gives you a fast command-line-based approach. Listing 1 [3] shows the simplest version that hard codes the coordinates measured previously for the text strings. The CPAN Imager module reads the original file, turtle.jpg, which is a photo that I personally took on vacation in Hawaii of a giant turtle swimming.

Listing 1

meme-first

01 #!/usr/local/bin/perl -w
02 use strict;
03 use Imager;
04
05 my $img =
06   Imager->new(
07  file => "turtle.jpg")
08   or die Imager->errstr();
09
10 my $font =
11   Imager::Font->new(file =>
12    "/usr/share/fonts/truetype"
13    . "/msttcorefonts/Impact.ttf"
14   );
15
16 $img->string(
17  x      => 337,
18  y      => 102,
19  string => "ARRIVING FIRST",
20  font   => $font,
21  size   => 60,
22  aa     => 1,
23  color  => 'white'
24 );
25
26 $img->string(
27  x => 315,
28  y => 600,
29  string =>
30    "SO NOT WORTH IT.",
31  font  => $font,
32  size  => 60,
33  aa    => 1,
34  color => 'white'
35 );
36
37 $img->write(
38  file => "turtle-meme.jpg");

I found the Impact font in my Ubuntu distribution as a .ttf file in the path shown in Listing 1 (lines 12 and 13). I call the string() method twice, once with the footer and once with the header, set the color to white, and specify a font size of 60. Then, I turn on anti-aliasing for less powerful displays and write the output file with write() – the result is the fantastic joke that you can see in Figure 3.

Figure 3: At the command line, meme-first generates a zeitgeist joke.

Variable Comfort Levels

For variable text strings, the script needs to position the strings dynamically in the center. Listing 2 expects three parameters – the image to be modified, the header, and the footer – and turns them into an image macro. The following line

Listing 2

meme-simple

01 #!/usr/local/bin/perl -w
02 use strict;
03 use Imager;
04
05 my $margin_y   = 100;
06 my $font_size  = 60;
07 my $font_color = "white";
08
09 my ($file, $header, $footer)
10   = @ARGV;
11
12 die
13   "usage: file header footer"
14   if scalar @ARGV != 3;
15
16 my $img =
17   Imager->new(file => $file)
18   or die Imager->errstr();
19
20 my $font =
21   Imager::Font->new(
22  file =>
23 "/usr/share/fonts/truetype/"
24    . "msttcorefonts/Impact.ttf",
25  size  => $font_size,
26  color => $font_color,
27   );
28
29 my ($header_w, $header_h) =
30   dimensions($font, $header);
31
32 my ($footer_w, $footer_h) =
33   dimensions($font, $footer);
34
35 my $footer_x =
36   ($img->getwidth() -
37    $footer_w) / 2;
38
39 my $header_x =
40   ($img->getwidth() -
41    $header_w) / 2;
42
43 $img->string(
44  x      => $header_x,
45  y      => $margin_y,
46  string => $header,
47  font   => $font,
48  size   => $font_size,
49  aa     => 1,
50  color  => $font_color
51 );
52
53 $img->string(
54  x => $footer_x,
55  y => $img->getheight() -
56    $margin_y +
57    $footer_h,
58  string => $footer,
59  font   => $font,
60  size   => $font_size,
61  aa     => 1,
62  color  => $font_color
63 );
64
65 (my $outfile = $file) =~
66   s/\./-meme./;
67 $img->write(
68  file => $outfile);
69
70 #############################
71 sub dimensions {
72 #############################
73  my ($font, $string) = @_;
74
75  my (
76   $neg_width, $global_desc,
77   $pos_width, $global_asc,
78   $desc,      $asc,
79    )
80    = $font->bounding_box(
81   string => $string);
82
83  return $pos_width -
84    $neg_width,
85    $asc - $desc;
86 }
meme-simple turtle.jpg "ARRIVING FIRST" "SO NOT WORTH IT."

generates a file by the name of turtle-meme.jpg. To achieve this, the script defines a vertical distance $margin_y from the top edge of the image to the header and from the bottom edge of the image to the footer.

The function defined in lines 71-86 (dimensions) calculates the width and height of the string produced using Impact font size 60. To do this, it passes in the desired string to the bounding_box() method of an Imager::Font type object. The result is the width and height in pixels, after the function has subtracted the potentially negative coordinates of the left edge of the first glyph in the string ($neg_width) from the position at the right end of the string ($pos_width).

It uses the same approach for the position at the top ($asc) and bottom ($desc) edges of the string. The values $global_asc and $global_desc don't matter here, because they do not relate to the current string, but to the maximum possible extent of any glyphs.

Lines 35 and 39 center the footer and header, respectively, relative to the photo by dividing the overall width of the picture returned by getwidth() by two and subtracting half the string width. The results are the required starting coordinates of the centered string as an x-y pair of values, with x running from left to right and y from top to bottom.

As in Listing 1, the string() method in Listing 2 now draws the two text strings in the Impact font on the image, and the write() method writes the result to a file on disk, adding a -meme suffix to the file name.

Running Gag

A short movie is even funnier. The browser loads an animated GIF image in one fell swoop and plays back the frames it contains until the end of time, if the infinite flag is set. This method dates back to 1987 and is still very popular, despite HTML5, especially because it also works in very old browsers. Video sequences without sound can easily be embedded into HTML; Wikipedia pages, for example, use this technique to visualize algorithms or the interaction between the moving parts of mechanical equipment.

Comedians in the software development business add GIFs to PowerPoint presentations and comment fields for pull requests on GitHub. The often jerky frames are reminiscent of slapstick scenes from the early days of cinema or clumsy "Candid Camera" material. Incidentally, the argument that has been raging for decades about whether GIF is pronounced "giff" or "jiff" remains unresolved to the present day. Only the front between both righteous parties has hardened [4].

Strategically extracting individual frames from a video file sounds like a job for MPlayer:

mplayer -vf screenshot <video>.avi

While the movie is playing, you need to press the S key to save the next frame displayed as a PNG file. When you are done, the current directory will contain files numbered shot0000.png to shot<XXXX>.png with the frames you grabbed (Figure 4).

Figure 4: Frames can be copied from the video with MPlayer and displayed with the Eye of Gnome viewer tool.

To avoid bloating the .gif file, the total number of frames should be no more than about 20. Also, rapid-motion scenes need a faster sequence of frames for the viewer to keep up. To do this, simply press S twice as fast as usual during the rapid-motion scenes.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy Linux Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

  • ODF Compatibility

    What happens when you feed an ODT document created with OpenOffice to a word processor like AbiWord, KWord, or Writely? Read on to find out.

  • PDF Tools

    PDF is always a good choice say some people. As a test, we produced PDF files only to maltreat them with several open source programs. Some of the editors and extractors do a very good job, but others fail completely.

  • Cascading Style Sheets

    Cascading Style Sheets (CSS) help you polish up your websites without taking a crash course in programming.

  • ImageMagick

    GIMP isn’t the only option for photo manipulation. ImageMagick, a collection of command-line programs for image processing, can help you process multiple images in one go.

  • Perl: Skydiving Simulation

    Computer game programmers apply physical formulas and special tricks to create realistic animations. Simple DirectMedia Layer (SDL), which is available as a Perl wrapper, provides a powerful framework for creating simple 2D worlds with just a couple of lines of code.

comments powered by Disqus

Direct Download

Read full article as PDF:

Price $2.95

News