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) |
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. |
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" );
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.
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.
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. |
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. |
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();
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.
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2013/11/Perl
"Image Macro", Wikipedia, http://en.wikipedia.org/wiki/Image_macro
"I can has Cheezburger", Wikipedia, http://en.wikipedia.org/wiki/I_Can_Has_Cheezburger%3F
"Battle Over ‘GIF’ Pronunciation Erupts", New York Times, http://bits.blogs.nytimes.com/2013/05/23/battle-over-gif-pronunciation-erupts/?_r=0
"Segway FAIL", Youtube-Video von Michael Schilli, http://www.youtube.com/watch?v=8_EbnF9xl-g
Animiertes GIF des "Segway FAIL" Videos: http://perlmeister.com/anim.gif