Gimp Daddy (Linux-Magazin, September 2003)

[Hinweis für Nicht-Amerikaner: Anspielung auf ``Pimp Daddy'', hier sind ein paar Ideen für den Grafiker: http://pimpdaddy.com/sightings/index.php :) ] Mit Gimp lassen sich selbst anspruchsvollste Verbesserungen an digitalen Bildern vornehmen. Statt aber immer wieder die gleichen Manöver per Maus auszuführen, automatisieren wir die Vorgänge heute mit Gimps Perl-Schnittstelle.

Kürzlich stolperte ich über die Webseite des Foto-Fachmanns Eric Jeschke, der auf [2] allerlei Tricks beschreibt, um digitale Fotos mit dem frei auf Linux erhältlichen Fotobearbeitungsprogramm Gimp zu verbessern. Da verschwinden ruckzuck rote Blitzaugen, Bildregionen werden 'nachbelichtet' oder 'abgewedelt', wie man im Fotolaborjargon sagt, exzessive Tiefenschärfe reduziert, und was es der Gimmicks mehr gibt.

In [3] fand ich dann eine zwar kurze, aber einigermaßen gelungene Einführung in Gimps Perl-Schnittstelle, eine von Marc Lehmann geschriebene Gimp-Erweiterung, die ein fester Bestandteil der Gimp-Source-Distribution ist. Damit kann man neue Menüs in den Gimp einfügen, sonst üblicherweise per Mausklick ausgelöste Aktionen im Skript ausführen und ungehemmt auf den Bilddaten herumorgeln.

Eines der Rezepte auf [2] beschrieb, wie man ein Bild dramatischer, ja traumartiger macht. Das geht so: Man lädt das Bild in den Gimp, lässt den Layers-Dialog erscheinen, kopiert den ersten Layer in einen zweiten und wählt dafür den Modus ``Overlay''. Dann hellt man die Farben mittels des ``Image->Colors->Levels''-Dialogs leicht auf und rundet die durch den ``Overlay'' entstandenen Ecken durch eine ``Gaussian Blur''-Unschärfe mit Radius 20 ab. Der Effekt ist erstaunlich, [4] zeigt Originalbild und Gimp-Fälschung. Wendet man das Verfahren allerdings ein paarmal von Hand auf verschiedene Bilder an, ermüden schnell die Finger und eine innere Stimme beginnt, lautstark nach einem Skript zu schreien.

Maus oder Hackbrett

Das heute vorgestellte Skript dreamify funktioniert sowohl als Gimp-Plugin, das den Gimp mit einem neuen Menüpunkt anreichert und seine Spielchen mit dem gerade geladenen Bild treibt, als auch von der Kommandozeile aus:

    dreamify test.jpg

lädt das angegebene Bild im JPG-Format, führt die gerade beschriebenen Manipulationen aus, lässt dann die zwei Layer durch 'Flatten' kollabieren und schreibt das Ergebnis im PNG-Format nach test.png.

In Perl geschriebene Gimp-Plugins ziehen einfach

    use Gimp qw(:auto);
    use Gimp::Fu;

herein. :auto ist optional, importiert aber alle verfügbaren Gimp-Bibliotheksfunktionen praktischerweise in den Namensraum des Skripts und definiert nützliche Konstanten. Die Plugin-Skripts müssen die register()-Funktion aufrufen, die den Plugin in Gimp registriert. Sie legt fest, unter welchem Menüpunkt der Plugin hängt, gibt eine kurze Beschreibung für die Online-Hilfe und definiert, was passiert, wenn der Anwender darauf drückt. Das Gimp-Plugin-Skript sollte am Ende mit

    exit main;

die Kontrolle an den Gimp zurückgeben, der das Skript während des Startvorgangs ausführt und sich die vorgenommenen Einstellungen merkt.

Menü oder Kommando

Wenn der Gimp den Plugin beim Hochfahren aufruft, gibt er ihm Parameter der Form

    -gimp 9 8 -query 0

mit. Das nutzt Zeile 19 in dreamify aus, um festzustellen, ob der Aufruf vom Gimp oder von der Kommandozeile kam. Im ersten Fall beginnt der Plugin, auf dem aktuellen Bild herumzuorgeln. Von der Kommandozeile aus aufgerufen, lädt das Skript zunächst die als Argument angegebene Datei als JPG-Bild und schreibt das ausgeflachte Ergebnis nach getaner Arbeit in ein PNG-Bild zurück auf die Festplatte.

Damit Gimp auf dem aktuellen Bild operiert, wenn der Benutzer den den ``Dreamify''-Eintrag im Menü auswählt, legt Zeile 21 den String

    "<Image>/Perl-Fu/Dreamify"

in der Variablen $menu ab, die später der register-Methode mitteilt, wo der Eintrag aufzuhängen ist. Im Falle eines Kommandozeilenaufrufs ist $menu auf

    "<Toolbox>/Xtns/Mike/Dreamify"

gesetzt, denn obwohl der Gimp in diesem Fall gar kein Menu anlegen muss, besteht er auf einem gültigen Eintrag und der <Image>-Eintrag oben verursacht eine wüste Fehlermeldung.

Wird das Skript von der Kommandozeile aus ohne Angabe der zu bearbeitenden Bilddatei aufgerufen, verzweigt Zeile 25 zur weiter unten stehenden POD-Dokumentation und bricht das Skript ab.

Registrierung

Außer dem Menüpunkt, unter dem die neue Funktion liegt, teilt die register()-Funktion dem Gimp auch noch mit, welche Funktion aufzurufen ist (dreamify()), welche Parameter ihr zu übergeben sind (keine), welche Rückgabeparameter der Gimp erwarten kann (keine) und welche Teile der POD-Dokumentation in die verschiedenen Sektionen der Gimp-Laufzeit-Hilfe einfließen sollen.

Die dreamify()-Funktion wird vom Gimp mit einem Handle zum aktuellen Image und Layer aufgerufen, wenn der Benutzer den neuen Menüpunkt anwählt (Abbildung 2). Und das nur, weil der Menüeintrag für die Funktion unter <Image> hängt -- hinge er hingegen unter <Toolbox>, wäre das nicht der Fall, da das Menü nicht Image-spezifisch ist.

Falls der Aufruf von der Kommandozeile aus erfolgte, sind $img und $layer nicht gesetzt und die in Zeile 52 aufgerufene Gimp-Bibliotheksfunktion file_jpeg_load lädt das Bild von der Platte, dessen Pfadangabe vom Hauptprogramm her noch in der Variablen $jpg_file liegt.

Die für interne Funktionen wie file_jpeg_load notwendigen Parameter zeigt der Gimp übrigens praktischerweise in seiner Online-Hilfe an. Man geht dazu ins Menü Xtns/DB_Browser, sucht per Stichwort in Hunderten von Funktionen und bekommt deren Signatur mit Kurzhilfe angezeigt - praktisch! Und auch der neue Plugin taucht gleich nach der Installation dort auf, wie Abbildung 1 zeigt. Abbildung 2 zeigt den neuen Menüpunkt in Aktion im rauhen Gimp-Alltag.

Abbildung 1: Manualseite der neuen Gimp-Erweiterung im DB-Browser

Abbildung 2: Der neue Perl-gesteuerte Menüpunkt erscheint im Gimp

Allerdings unterscheiden sich die Einträge dort manchmal von den im Gimp-Perl-Modul verwendeten Signaturen: Als Faustregel gilt, dass man run_mode immer weglassen darf, weil das Gimp-Modul selbst weiss, ob der Aufruf interaktiv oder im Hintergrund erfolgte. Und wenn die Dokumentation sowohl ein Image- als auch ein Layer-Handle fordert, genügt ein Layer-Handle, da das Gimp-Modul von diesem intern auf das Image schließt. Außerdem sind die Handles Objektreferenzen, so dass man statt

   gimp_layer_copy($layer, $add_alpha);

einfach

   $layer->layer_copy($add_alpha);

oder sogar

   $layer->copy($add_alpha);

sagen kann. Zurück zu dreamify(): In Zeile 54 gibt image_get_active_layer() den aktiven (und normalerweise einzigen) Layer des übergebenen Bildes zurück. Zeile 58 kopiert den Layer in einen neuen, das Flag, das einen Alpha-Channel hinzufügt, bleibt auf 0 gesetzt.

Die nachfolgende Methode layer_set_mode() setzt den Modus des neuen Layers auf die Konstante OVERLAY_MODE, die wegen des :auto-Tags nach use Gimp bequemerweise ins Skript importiert wurde. Diese Aktion entspricht der Auswahl des ``Overlay''-Modus im Layer-Dialog des Gimp.

image_add_layer() in Zeile 60 fügt den neu erzeugten Layer zum Bild hinzu, der auf -1 gesetzte Parameter setzt ihn auf den Kopf des schon bestehenden Basislayers.

Damit der Gimp-Benutzer bei langdauernden Aktionen nicht die Nerven verliert sondern bei Laune bleibt, initialisiert dreamify mit Gimp->progress_init("Titel") eine Fortschrittsanzeige und aktualisiert diese nach den einzelnen Schritten jeweils mit Gimp->progress_update($fraction). $fraction ist hierbei ein Fließkommawert zwischen 0 und 1 und gibt die Position des Fortschrittsbalkens an.

Zeile 63 skaliert das Bild auf 800x600 Pixel, Hierzu ruft es die ab Zeile 89 definierte Funktion scale_image_down auf, die ein Image-Handle und die gewünschte Maximalgroße des Bildes erwartet und die Veränderung direkt am Bild vornimmt. Hierzu findet sie zunächst heraus, ob das vorliegende Bild statt im Quer- im Hochformat vorliegt und deswegen die Maximalgröße auf 600x800 korrigiert werden muss. Sind Breite und Höhe des Bildes über den vorgegebenen Maximalwerten, wird das Bild mittels image_scale so verkleinert, dass die Bildproportionen erhalten bleiben.

plug_in_gauss_iir in Zeile 67 führt die Glättung des Bildes mit dem ``Gaussian Blur''-Algorithmus aus und setzt den Unschärferadius auf 20.0. Sie aktiviert sowohl horizontale also auch vertikale Unschärfe mit jeweils wahren Werten für die letzten beiden Parameter. Zeile 72 schließlich hellt die Farben im Bild mit einem Gamma-Faktor von 1.5 etwas auf. Ein- und Ausgabebereiche bleiben mit 0-255 konstant. Dies entspricht einem etwas nach links verschobenen ``Intensity''-Regler in Gimps Image/Colors/Level-Dialog. Anschließend patscht Zeile 75 die beiden Layer zu einem einzigen zusammen, und Zeile 76 holt dessen Handle.

Für den Fall, dass das Skript ohne Gimp läuft (erkennbar an der gesetzten $jpg_file-Variable), sichert die file_png_save-Methode das Bild in der angegebenen PNG-Datei mit einem Kompressionsfaktor von 6. Wie aus dem DB-Browser ersichtlich folgen noch 5 weitere Integer-Parameter, die wir alle auf 1 setzen.

Zeile 109 gibt die Kontrolle an den Gimp zurück -- fertig. Die angehängte POD-Dokumentation hilft sowohl dem Skript- als auch dem Gimpbenutzer.

Ohne Gimp läuft das Skript ohne Fortschritts- und sonstige Anzeigen. Da es letztendlich aber doch alle Funktionen des Gimp benötigt, bezahlt man mit einer langen Startphase. Um das zu umgehen, kann man eine Gimp-Instanz auf dem Rechner starten und den Menüpunkt Xtns/Perl/Server anwählen. Dies startet im Gimp einen Server, den sich das extern gestartete Skript beim Start automatisch sucht und anschließend den bereits laufenden Gimp nutzt. Aber Vorsicht: Dem Skript übergebene relative Pfadangaben für Bilddateien müssen sich dann auf das Startverzeichnis des Gimp beziehen, nicht auf das des Skripts.

Listing 1: dreamify

    001 #!/usr/bin/perl
    002 ###########################################
    003 # dreamify - Gimp Image Dreamifier
    004 # Mike Schilli, 2003 (m@perlmeister.com)
    005 ###########################################
    006 use warnings;
    007 use strict;
    008 
    009 use Gimp qw(:auto);
    010 use Gimp::Fu;
    011 use Pod::Usage;
    012 
    013 use Log::Log4perl qw(:easy);
    014 Log::Log4perl->easy_init($DEBUG);
    015 
    016 my $menu = "<Toolbox>/Xtns/Mike/Dreamify";
    017 my $jpg_file;
    018 
    019 if(grep /-gimp/, @ARGV) {
    020     # Call from within Gimp
    021   $menu = "<Image>/Perl-Fu/Dreamify";
    022 } else {
    023     # Call from the command line
    024   $jpg_file = $ARGV[0];
    025   pod2usage("No file") 
    026       unless defined $jpg_file;
    027 }
    028 
    029 register(
    030   "perl_fu_dreamify",    # Name
    031   "Dreamify a Picture",  # Blurb
    032   "=pod(HELP)",          # Help 
    033   "=pod(AUTHOR)",        # Author
    034   "=pod(COPYRIGHT)",     # Copy
    035   "=pod(DATE)",          # Date
    036   $menu,                 # Menu
    037     "*",                   # Images accepted
    038       [
    039       [PF_INT,   "size", "Img size", 100],
    040       [PF_COLOR, "color", "Img color", [255,127,0]]
    041         ],
    042 
    043 #"",                    # Images accepted
    044 #[],                    # Parameters
    045 #[],                    # Return values
    046   \&dreamify             # Function
    047 );
    048 
    049 ###########################################
    050 sub dreamify { 
    051 ###########################################
    052   my($img, $layer) = @_;
    053 
    054   Gimp->progress_init("Dreamifying ...");
    055      
    056   if(!$img) {
    057     DEBUG "Loading $jpg_file";
    058     eval { $img = file_jpeg_load($jpg_file, "") };
    059     $img = file_png_load($jpg_file, "") unless $img;
    060     die "Can't load $jpg_file" unless $img;
    061     $layer = image_get_active_layer($img);
    062   }
    063 
    064   DEBUG "Copying layer";
    065   my $new_layer = $layer->layer_copy(0);
    066   $new_layer->layer_set_mode(OVERLAY_MODE);
    067   $img->image_add_layer($new_layer, -1);
    068   Gimp->progress_update(.1);
    069 
    070   scale_image_down($img, 1000, 800);
    071   Gimp->progress_update(.5);
    072 
    073   DEBUG "Blurring";
    074   $img->plug_in_gauss_iir($new_layer, 
    075                           20.0, 1, 1);
    076   Gimp->progress_update(.8);
    077 
    078   DEBUG "Adjusting Colors";
    079   $new_layer->gimp_levels(0, 0, 255, 1.5, 
    080                           0, 255);
    081 
    082   $img->flatten();
    083   $layer = $img->get_active_layer;
    084   Gimp->progress_update(1);
    085 
    086   if($jpg_file) {
    087       (my $png_file = $jpg_file) =~ 
    088                            s/\.jpg$/.png/g;
    089       DEBUG "Saving to $png_file";
    090       $layer->file_png_save($png_file, "", 
    091                             0, 6, (1) x 5);
    092   }
    093 }
    094 
    095 ###########################################
    096 sub scale_image_down { 
    097 ###########################################
    098   my($img, $x, $y) = @_;
    099 
    100   my $w = $img->image_width();
    101   my $h = $img->image_height();
    102 
    103     # Switch x,y if portrait
    104   ($x, $y) = ($y, $x) if $w < $h;
    105   
    106   DEBUG "Limits $x x $y";
    107   DEBUG "Size   $w x $h";
    108 
    109   if($w > $x and $h > $y) {
    110       my $new_h = int($h*$x/$w);
    111       DEBUG "Resizing to $x x $new_h";
    112       $img->image_scale($x, $new_h);
    113   }
    114 }
    115 
    116 exit main;
    117 
    118 __END__
    119 
    120 =head1 NAME
    121 
    122 Dreamify - Gimp Plugin for dreamy pictures
    123 
    124 =head1 SYNOPSIS
    125 
    126     dreamify file.jpg
    127 
    128 =head1 HELP
    129 
    130 Adds a second layer to a picture in
    131 "Overlay" mode, lightens up the colors,
    132 applies a Gaussian blur and rescales the
    133 result to 800x600. Operates on the current
    134 picture if called from within Gimp via
    135 C<Rightclick/Perl-Fu/Dreamify>. If called
    136 from the command line, it modifies the JPG
    137 file specified, flattens the result and 
    138 writes it back as PNG.
    139 
    140 =head1 AUTHOR
    141 
    142 Copyright 2003 by Mike Schilli, all rights
    143 reserved. This program is free software,
    144 you can redistribute it and/or modify it
    145 under the same terms as Perl itself.

Installation

Für das Skript brauchen wir die Module XML::Parser, XML::Writer, PDL, Log::Log4perl und Gtk, die sich reibungslos mit einer CPAN-Shell installieren lassen. Falls es mit Gtk-Perl-0.7009 Probleme gibt, sollte man die manuelle Installation mit

    perl Makefile.PL --without-guessing
    make
    make install

versuchen, das sollte klappen. Auch nutzt das Skript Log::Log4perl, um anzuzeigen, was es gerade treibt. Wem das auf die Nerven geht, der kann's einfach abstellen, indem er den Log-Level in Zeile 14 auf $ERROR hochsetzt.

Die Module Gimp und Gimp::Fu erhält man zusammen mit der Gimp-Source über

    wget ftp://ftp.gimp.org/pub/gimp/v1.2/v1.2.5/gimp-1.2.5.tar.gz tar
    zxfv gimp-1.2.5.tar.gz

herein. Im Verzeichnis

    cd gimp-1.2.5/plug-ins/perl

ist dann endlich das Perl-Instrumentarium, das wie üblich mit

    perl Makefile.PL
    make
    make install

installiert wird. Allerdings musste ich die Distribution korrigieren, indem ich eine Datei po/Makefile einfügte, mit einer einzigen Zeile, die mit install: eine leere install-Target anlegte.

Um das Skript dreamify im Gimp zu verwenden, muss es ins Skriptverzeichnis, von wo es der Gimp beim Programmstart einliest und die Registrierung vornimmt. Das Kommando

    gimptool --install-bin dreamify

installiert den neuen Plugin in ~/.gimp-1.2/plug-ins/ und fertig ist der Lack: Gimp starten und auf Knopfdruck Bilder verträumter dreinschauen lassen!

Infos

[1]
Listings zu diesem Artikel: ftp://www.linux-magazin.de/pub/listings/magazin/2003/09/Perl

[2]
Eric Jeschke, ``Gimp Tutorials'', http://redskiesatnight.com

[3]
Shawn Wallace, ``Perl Graphics Programming'', O'Reilly, 2002

[4]
Ein verträumtes Beispielbild: http://www.perlmeister.com/rundbrief.archiv/20030526/images/pflaster.jpg ... und zum Vergleich das Original: http://www.perlmeister.com/rundbrief.archiv/20030526/images/porg.jpg

Michael Schilli

arbeitet als Software-Engineer bei Yahoo! in Sunnyvale, Kalifornien. Er hat "Goto Perl 5" (deutsch) und "Perl Power" (englisch) für Addison-Wesley geschrieben und ist unter mschilli@perlmeister.com zu erreichen. Seine Homepage: http://perlmeister.com.