Humor vom Fließband (Linux-Magazin, November 2013)

Generatoren für lustige Memes und animierte Gifs gibt es zuhauf, doch mit Perl lassen sie sich einfach selbst bauen und individuell gestalten.

Sommerzeit, Praktikantenzeit: Wie immer während der Sommermonate beschäftigt die Firma College-Studenten und wir altgediegenen Graubärte kratzen uns die kahler werdenden Schädel ob der heutigen Jugend. Besonders erstaunen die Humorgewohnheiten junger Leute heutzutage, keine Präsentation eines Jungspundes kommt mehr ohne sogenannte "Image Macros" ([2]) zur allgemeinen Erheiterung aus, entweder als statisches Foto oder als animiertes Gif in Endlosschleife.

Was als "I Can Has Cheezburger" ([3]) mit knuddeligen Kätzchen (sogenannten Lolcats) und kessen Sprüchen begann, ist heute als "Meme" fester Bestandteil der Humorkultur auf dem Internet. Man nehme ein ausdrucksstarkes Bild, füge eine Kopf- und eine Fußzeile im Font "Impact" hinzu, und fertig ist der Witz. "Meme" kommt aus dem Griechischen, "mimema" meint dort etwas imitiertes und in der evolutionären Biologie beschreibt man damit den Vorgang der sozialen Weitergabe kultureller Werte. Internetforscher beschreiben mit "Memes" sich virenartig verbreitende Massenphänomene.

Abbildung 1: Beispiel eines "Image Macro" Memes (Quelle: Wikipedia http://upload.wikimedia.org/wikipedia/commons/6/6f/Your_argument_is_invalid.jpg)

Manuell oder Maschinell

Mit einem Fotoeditor wie Gimp sind Image Macros schnell erstellt, aber mit einem Perlskript geht es sogar von der Kommandozeile aus. Listing 1 zeigt die einfachste Version, die einfach vorher ausgemessene Koordinaten für die Textstrings hart verdrahtet. Das CPAN-Modul Imager liest die Originaldatei turtle.jpg ein, ein von mir persönlich auf Hawaii geschossenes Urlaubsfoto von einer schwimmenden Riesenschildkröte. Den Font Impact fand ich in meiner Ubuntu-Distro als .ttf-Datei unter dem in Listing 1 angegebenen Pfad in Zeile 9. Zweimal die Methode string() aufgerufen, einmal mit der Fuß- und einmal mit der Kopfzeile, die Farbe mit "white" angegeben, die Fontgröße auf 60 eingestellt, Anti-Aliasing für schwachbrüstige Display eingeschaltet und die Ausgabedatei mit write() geschrieben, fertig ist der Spitzenwitz (Abbildung 2).

Abbildung 2: Auf der Kommandozeile erzeugter Spitzenwitz.

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 ruckzuck die Datei turtle-meme.jpg in Abbildung 2. Hierzu definiert das Skript einen vertikalen Abstand $margin_y von der Bildoberkante zur Kopfzeile bzw. von der Bildunterkante zur Fußzeile. Die ab Zeile 56 definierte Funktion dimensions errechnet die Breite und Höhe des mit dem Impact-Font der Größe 60 erzeugten Strings. Hierzu nutzt 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 rechtesten Rand des Strings ($pos_width) subtrahiert hat. Analog verfährt sie mit der Position am oberen ($asc) bzw. 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 prinzipiell mögliche Ausmaße als Maximalwerte für beliebige 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 X/Y-Wertepaar. X läuft dabei von links nach rechts im Bild, Y von oben nach unten.

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 }

Wie in Listing 1 fügt die Methode string() dann die beiden Textstrings im vorgegebenen Font in das Bild ein und die Methode write() schreibt eine um die Endung -meme erweiterte Datei mit dem Ergebnis auf die Festplatte.

Der gespielte Witz

Noch lustiger wird's mit animierten Kurzfilmchen. Ein animiertes Gif-Bild lädt der Browser in einem Rutsch vom Server und spielt dann die darin enthaltenen Frames bis zum St. Nimmerleinstag ab, falls im Bild das Endlos-Flag gesetzt ist. Dieses Verfahren stammt aus dem Jahr 1987 und erfreut sich auch heute noch trotz HTML5 großer Beliebtheit, weil es auch mit Uraltbrowsern funktioniert. Die tonlosen Videosequenzen lassen sich so problemlos in HTML einbetten und Seiten wie Wikipedia nutzen sie unter anderem zur Darstellung von Algorithmen oder beweglichen Motorenteilen. Komiker in der Softwareentwicklung betten sie in Powerpoint-Präsentationen und Kommentarfelder zu Pull-Requests auf Github ein. Die teilweise ruckartig abgespielten Frames erinnern an tolpatschiges "Verstehn Sie Spaß"-Material, und ähneln Slapstick-Szenen aus der Steinzeit des Kinos. Übrigens ist der seit Jahrzehnten herrschende Streit ob man gif "Giff" oder "Tschiff" ausspricht auch heutzutage noch nicht endgültig geklärt. Die Fronten der Rechthaber haben sich jedoch unvereinbar verhärtet [4].

Um einzelne Frames strategisch aus einer Videodatei zu extrahieren, eignet sich der mplayer, den man mit

    $ mplayer -vf screenshot video.avi

aufruft. Während das Video läuft, drückt dann der User jedes Mal die Taste "s", um den nächsten angezeigten Frame als .png-Datei auf die Platte zu sichern. Am Ende stehen im aktuellen Verzeichnis dann von shot0000.png bis shotXXXX.png durchnumerierte Dateien mit den geschossenen Frames (Abbildung 3). Dabei sollte man sich auf etwa 20 Frames insgesamt beschränken, damit die Gif-Datei nicht zu groß wird, und darauf achten, dass Szenen mit viel Bewegung eine schnellere Abfolge von Frames benötigen, sonst kommt der Betrachter nicht mit. Dazu hämmert man während der schnellen Szenen einfach doppelt so häufig auf die "s" Taste als sonst.

Abbildung 3: Kopierte Frames aus dem Video als PNG-Bilder in Eye of Gnome.

Schönste Schadenfreude

Als Video zu Demonstrationszwecken hält eine von mir selbst aus einem Hotelzimmer an der Strandpromenade von Venice Beach bei Los Angeles geschossene Szene her ([5]). Dort versuchte ein verzweifelter Tourist, einen störrischen Segway-Roller zum Vermieter zurückzuschleppen. Schadenfreude ist bekanntlich die schönste Freude!

Listing 3 baut die Einzel-Frames mittels 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 im Skript in Zeile 11 gewählten Animationsgröße von 300x200 Pixeln und 26 verwendeten Frames erreicht das animierte Gif-Bild anim.gif gerade mal ein Megabyte. Die Methode write_multi() schreibt die vorher mittels 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 eine schnellere Konvertierung. Wichtig ist es auch noch, 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 4: Das animierte Gif läuft im Browser in einer Endlosschleife.

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();

Titel für mehr Komik

Um den Spaß noch weiter zu treiben und alle Screenshots des Gif-Filmchens mit einer lustigen Kopfzeile zu versehen und den Footer leer zu lassen, dient das folgende Shell-Kommando

    $ for i in *.png
    do
      meme-simple $i "SEGWAY FAIL" ""
    done

Anschließend liest anigif alle Dateien mit der Endung -meme.png ein und erzeugt das animierte Gif.

    $ anigif shot*-meme.png

Das Ergebnis zeigt Abbildung 5. Nun prangt während des gesamten Filmchens 100 Pixel unterhalb des oberen Bildrandes eine bewegungslose Überschrift in weiß im witzen Impact-Font, da der String identisch in jeden einzelnen Frame eingebaut wurde. Wer möchte, kann sich unter [6] das animierte Bild ansehen.

Abbildung 5: Animiertes Gif mit Kopfzeile in allen Frames.

Weitere komische Entfaltungsmöglichkeiten springen ins Auge. Vielleicht lassen sich ja automatisch Spitzenwitze mit zufälligen Textstücken erzeugen? Zitate aus der Kinofilm-Datenbank IMDB böten sich an. Die Autorengilde der Witzindustrie bekäme ernstzunehmende Konkurrenz, falls sich herausstellen sollte, dass sich die für Spitzenwitze notwendige Inkongruenz maschinell produzieren ließe.

Infos

[1]

Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2013/11/Perl

[2]

"Image Macro", Wikipedia, http://en.wikipedia.org/wiki/Image_macro

[3]

"I can has Cheezburger", Wikipedia, http://en.wikipedia.org/wiki/I_Can_Has_Cheezburger%3F

[4]

"Battle Over ‘GIF’ Pronunciation Erupts", New York Times, http://bits.blogs.nytimes.com/2013/05/23/battle-over-gif-pronunciation-erupts/?_r=0

[5]

"Segway FAIL", Youtube-Video von Michael Schilli, http://www.youtube.com/watch?v=8_EbnF9xl-g

[6]

Animiertes GIF des "Segway FAIL" Videos: http://perlmeister.com/anim.gif

Michael Schilli

arbeitet als Software-Engineer bei Yahoo in Sunnyvale, Kalifornien. In seiner seit 1997 laufenden Kolumne forscht er jeden Monat nach praktischen Anwendungen der Skriptsprache Perl. Unter mschilli@perlmeister.com beantwortet er gerne Ihre Fragen.