Was harmlos mit ein paar sonderbaren Katzenbildern begann, ist zu dem Internetphänomen schlechthin angeschwollen: Memes, mit Witztexten versehene Fotos, und animierte Gag-Gifs. Kein Witz: Mit Perl lassen die sich prima selbst bauen und individuell gestalten.
Sommerzeit, Praktikantenzeit: Wie immer über die Sommermonate beschäftigt mein Arbeitgeber Collegestudenten, und wir Altgediegenen kratzen uns die kahler werdenden Schädel ob des sonderbaren akademischen Nachwuchses. Besonders erstaunte in diesem Jahr der praktizierte Praktikantenhumor: Jede Präsentation zieren so genannte Image Macros [2] zum Zwecke der Erheiterung, entweder als statische Fotos oder als per Endlosschleife animierte Gifs.
Was als “I Can Has Cheezburger” [3] mit knuddeligen Kätzchen – den so genannten Lolcats – und kessen Sprüchen begann, ist heute als “Meme” fester Bestandteil der netzweiten Humorkultur. Man nehme ein ausdrucksstarkes Bild und lege einen orthografisch oder grammatikalisch jenseitigen Spruch (Lolspeak) als Kopf- und Fußzeile im Font Impact darüber – und fertig ist der Witz (Abbildung 2).
Meme oder Mem kommt aus dem Griechischen, “mimema” meint dort etwas Imitiertes, und die evolutionäre Biologie beschreibt damit den Vorgang der sozialen Weitergabe kultureller Werte. Im Internet schließlich sind Memes ein sich per Mail, Chat oder soziale Netze virenartig verbreitendes Massenphänomen (Abbildung 3).
Maschinell statt manuell
Mit einem Grafikprogramm wie Gimp sind Image Macros auf konventionelle Weise recht schnell angefertigt, aber mit einem Perl-Skript geht es sogar von der Kommandozeile aus. Listing 1 zeigt die einfachste Version, die nur vorher ausgemessene Koordinaten für die Textstrings hart verdrahtet. Das CPAN-Modul Imager liest die Originaldatei »turtle.jpg« ein, das von mir persönlich auf Hawaii geschossene Urlaubsfoto von einer schwimmenden Riesenschildkröte.
Den Font Impact fand ich in meiner Ubuntu-Distribution als ».ttf« -Datei unter dem in Listing 1 (Zeilen 10 und 11) angegebenen Pfad. Zweimal die Methode »string()« aufgerufen, einmal mit der Fuß- und einmal mit der Kopfzeile, die Farbe mit »white« und die Fontgröße auf 60 angegeben, Anti-Aliasing für schwachbrüstige Displays eingeschaltet und die Ausgabedatei mit »write()« geschrieben – fertig ist der Spitzenwitz (Abbildung 1).
Listing 1
meme-first
01 #!/usr/local/bin/perl -w 02 use strict; 03 use Imager; 04 05 my $img = Imager->new( 06 file => "turtle.jpg" ) or 07 die Imager->errstr(); 08 09 my $font = Imager::Font->new( file => 10 "/usr/share/fonts/truetype" . 11 "/msttcorefonts/Impact.ttf" ); 12 13 $img->string( x => 337, y => 102, 14 string => "ARRIVING FIRST", 15 font => $font, size => 60, 16 aa => 1, color => 'white' ); 17 18 $img->string( x => 315, y => 600, 19 string => "SO NOT WORTH IT.", 20 font => $font, size => 60, 21 aa => 1, color => 'white' ); 22 23 $img->write( file => "turtle-meme.jpg" );
Variabler Komfort
Für variable Textstrings muss das Skript diese dynamisch in der Mitte positionieren. Listing 2 nimmt drei Parameter entgegen – das zu modifizierende Bild, die Kopf- und die Fußzeile – und macht daraus ein Image Macro. Der Aufruf
meme-simple turtle.jpg "ARRIVING FIRST""SO NOT WORTH IT."
erzeugt die Datei »turtle-meme.jpg« . Hierzu definiert das Skript einen vertikalen Abstand »$margin_y« von der Bildoberkante zur Kopfzeile respektive von der Bildunterkante zur Fußzeile. Die ab Zeile 56 definierte Funktion »dimensions« errechnet Breite und Höhe des mit dem Impact-Font der Größe 60 erzeugten Strings. Hierzu benutzt es die Methode »bounding_box()« eines Objekts vom Typ Imager::Font und übergibt ihm den gewünschten String.
Zurück kommen Breite und Höhe in Pixeln, nachdem die Funktion die unter Umständen negative Koordinate des linken Rands des ersten Glyphen im String (»$neg_width« ) von der Position am rechten Ende des Strings (»$pos_width)« subtrahiert hat. Analog verfährt sie mit der Position am oberen (»$asc« ) beziehungsweise unteren (»desc« ) Rand des Strings. Die Werte »$global_asc« und »$global_desc« spielen keine Rolle, da sie sich nicht auf den aktuellen String, sondern auf maximal mögliche Ausmaße beliebiger Glyphen beziehen.
Die Zeilen 33 und 36 zentrieren dann Fuß- und Kopfzeile jeweils in der Mitte des Fotos, indem sie die mit »getwidth()« geholte Gesamtbreite des Fotos halbieren und die Hälfte der Stringbreite davon subtrahieren. Heraus kommt jeweils die erforderliche Anfangskoordinate des zentrierten Strings als xy-Wertepaar, x läuft dabei von links nach rechts im Bild, y von oben nach unten.
Wie in Listing 1 fügt in Listing 2 die Methode »string()« nun die beiden Textstrings im vorgegebenen Font in das Bild ein und die Methode »write()« schreibt das Ergebnis in eine um »-meme« am Ende erweiterte Datei auf die Festplatte.
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 ) = @ARGV;
10
11 die "usage: file header footer"
12 if scalar @ARGV != 3;
13
14 my $img = Imager->new(
15 file => $file ) or
16 die Imager->errstr();
17
18 my $font = Imager::Font->new(
19 file =>
20 "/usr/share/fonts/truetype/" .
21 "msttcorefonts/Impact.ttf",
22 size => $font_size,
23 color => $font_color,
24 );
25
26 my( $header_w, $header_h ) =
27 dimensions( $font, $header );
28
29 my( $footer_w, $footer_h ) =
30 dimensions( $font, $footer );
31
32 my $footer_x =
33 ( $img->getwidth() - $footer_w ) / 2;
34
35 my $header_x =
36 ( $img->getwidth() - $header_w ) / 2;
37
38 $img->string(
39 x => $header_x, y => $margin_y,
40 string => $header,
41 font => $font, size => $font_size,
42 aa => 1, color => $font_color );
43
44 $img->string(
45 x => $footer_x,
46 y => $img->getheight() -
47 $margin_y + $footer_h,
48 string => $footer,
49 font => $font, size => $font_size,
50 aa => 1, color => $font_color );
51
52 ( my $outfile = $file ) =~ s/\./-meme./;
53 $img->write( file => $outfile );
54
55 ###########################################
56 sub dimensions {
57 ###########################################
58 my( $font, $string ) = @_;
59
60 my( $neg_width, $global_desc,
61 $pos_width, $global_asc,
62 $desc, $asc,
63 ) = $font->bounding_box(
64 string => $string );
65
66 return $pos_width - $neg_width,
67 $asc - $desc;
68 }
Laufend witzig
Noch lustiger wird’s mit animierten Kurzfilmchen. Ein animiertes Gif-Bild lädt der Browser in einem Rutsch vom Server und spielt die darin enthaltenen Frames bis zum Sankt-Nimmerleins-Tag ab, falls im Bild das Endlos-Flag gesetzt ist. Dieses Verfahren stammt aus dem Jahr 1987 und erfreut sich HTML 5 zum Trotz bis heute großer Beliebtheit, zumal es auch in Uraltbrowsern funktioniert. Die tonlosen Videosequenzen lassen sich problemlos in HTML einbetten, Wikipedia-Seiten etwa visualisieren damit Algorithmen oder das Zusammenspiel der beweglichen Teile mechanischer Apparaturen.
Komiker unter den Software-Entwicklern fügen Gifs in Powerpoint-Präsentationen und Kommentarfelder zu Pull-Requests auf Github ein. Die oft ruckelnden Frames erinnern an Slapstick-Szenen aus der Frühzeit des Kinos oder an tollpatschiges “Verstehen Sie Spaß?”-Material. Übrigens ist der seit Jahrzehnten schwelende Streit, ob man Gif wie “Giff” oder “Tschiff” ausspricht, bis heute ungeklärt. Nur die Front zwischen den Rechthabern hat sich verhärtet [4].
Einzelne Frames strategisch aus einer Videodatei extrahieren, dafür eignet sich der Mplayer aufgerufen mit:
mplayer -vf screenshot video.avi
Während das Video läuft, drückt der User immer dann die Taste [S], wenn er den nächsten angezeigten Frame als PNG-Datei sichern will. Am Ende liegen im aktuellen Verzeichnis von »shot0000.png« bis »shotXXXX.png« durchnummerierte Dateien mit den geschossenen Frames (Abbildung 4).
Damit die Gif-Datei nicht zu groß wird, sollte bei etwa 20 Frames insgesamt Schluss sein. Außerdem erfordern Szenen mit viel Bewegung eine schnellere Abfolge der Frames, damit der Betrachter mitkommt. Dazu hämmert man während der schnellen Szenen einfach doppelt so häufig auf [S] als sonst.
Motiv: Schadenfreude
Als Demo dient mir ein aus meinem Hotelzimmer an der Strandpromenade von Venice Beach nahe Los Angeles heraus gefilmte Szene [5]. Darin versucht ein Tourist verzweifelt, einen störrischen Segway-Roller zum Vermieter zurückzuschleppen. Schadenfreude ist bekanntlich die schönste Freude.
Nach einem langen Drehtag zurück baut Listing 3 die Einzelframes mit Hilfe des Imager-Moduls zu einem animierten Gif-Filmchen zusammen:
anigif shot*.png
Die For-Schleife in Zeile 7 iteriert über alle auf der Kommandozeile hereingereichten Dateinamen. Mit der in Zeile 11 gewählten Animationsgröße von 300 mal 200 Pixeln und 26 verwendeten Frames erreicht das animierte Gif-Bild »anim.gif« rund 1 MByte.
Die Methode »write_multi()« schreibt die vorher mit »new()« eingelesenen Frames als Gif-Datei auf die Festplatte. Die notwendigen Konvertierungen der Bildformate erfolgen automatisch hinter den Kulissen. Die Option »make_colors« mittelt mit »mediancut« die Farbtabelle zwischen den Frames und sorgt damit für schnellere Konvertierung. Wichtig ist es zudem, die Option »gif_loop« auf den Wert 0 zu setzen, was den Browser dazu veranlasst, die Sequenz nach dem Laden des Bildes immer und immer wieder abzuspulen (Abbildung 5).
Listing 3
anigif
01 #!/usr/local/bin/perl -w
02 use strict;
03 use Imager;
04
05 my @imgs = ();
06
07 for my $file_name ( @ARGV ) {
08
09 my $img = Imager->new( file => $file_name );
10
11 $img = $img->scale( xpixels => 300,
12 ypixels => 200 );
13 push @imgs, $img;
14 }
15
16 Imager->write_multi( {
17 file => "anim.gif",
18 type => 'gif',
19 gif_loop => 0,
20 make_colors => "mediancut" }, @imgs) or
21 die Imager->errstr();
Zwecks Perfektionierung will ich alle Screenshots des Gif-Filmchens noch mit einer lustigen Kopfzeile versehen, den Footer lasse ich leer. Dabei kommt das Skript aus Listing 1 erneut zu Ehren:
for i in *.png do meme-simple $i "SEGWAY FAIL" "" done
Das abschließende Kommando »anigif shot*-meme.png« liest alle Dateien mit der Endung »-meme.png« ein und erzeugt das animierte Gif. Abbildung 6 zeigt ein Szenenbild des Meisterwerks. Über dem gesamten Filmchen thront 100 Pixel unterhalb des oberen Bildrands bewegungslos die Überschrift mit dem Titel, da der String identisch in jeden einzelnen Frame eingebaut ist. Sie ist weiß und im Impact-Witze-Font. Wer möchte, kann sich unter [6] das animierte Bild ansehen.
Laufend Kopfarbeit
Am Schneidetisch des Internethumors sitzend sinniere ich über weitere Gestaltungsmöglichkeiten: Sollte ich Witze automatisch aus Zufallstexten stanzen? Vielleicht mit Zitaten aus dem Kinofilm-Portal IMDB? Meine Praktikanten nächsten Sommer würden staunen. (jk)
Infos
- Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2013/11/Perl
- Begriff Image Macro: http://en.wikipedia.org/wiki/Image_macro
- “I Can Has Cheezburger”: http://en.wikipedia.org/wiki/I_Can_Has_Cheezburger
- “Battle Over ‘GIF’ Pronunciation Erupts”, New York Times: http://bits.blogs.nytimes.com/2013/05/23/battle-over-gif-pronunciation-erupts/
- Michael Schilli, “Segway FAIL” (Video): http://www.youtube.com/watch?v=8_EbnF9xl-g
- Animiertes Gif des “Segway FAIL”-Videos: http://perlmeister.com/anim.gif












