Aus neu mach alt (Linux-Magazin, April 2009)

Das Fotomanipulationsprogramm Gimp hilft nicht nur dabei, Fotos optisch aufzubrezeln, sondern kann aus brandneuen Digitalbildern auch nostalgisch angehauchte Schwarz-Weiß-Bilder mit dem typischen Gelbstich produzieren.

Sieht man sich 100 Jahre alte Schwarz-Weiß-Bilder an, fällt eine gelbliche Tönung auf. Besonders an den ursprünglich schwarzen Stellen hat der Zahn der Zeit genagt und sie braun gefärbt, aber auch die helleren Töne weisen einen leichten Gelbstich auf. Als ich vor kurzem aus meinem Domizil, dem bunten Amerika, einen Abstecher in meine alte Heimat Deutschland unternam, kam mir die Idee, einige auf der Reise mit der Digitalkamera aufgenommene Farbbilder (Abbildung 1) spaßeshalber in Schwarzweiß umzuwandeln und künstlich zu altern (Abbildung 2).

Abbildung 1: Das mit einer modernen Digitalkamera aufgenommene Originalfoto.

Abbildung 2: Das mit Gimp künstlich gealterte Schwarz-Weiß-Foto mit Gelbstich.

Ein Fisch färbt gelb

Die sogenannte Sepia-Tönung alter Fotografien kommt laut [4] durch ein Pigment zustande, das Fotografien ab dem späten 19. Jahrhundert chemisch zugesetzt und aus einem im Ärmelkanal heimischen Fisch namens ``Sepia officinalis'' gewonnen wurde. Um diesen Effekt bei neuen Digitalaufnahmen zu erzielen, werden vor allem die dunkeln Teile eines Bildes mit einem hellen Gelb-Braunton (Abbildung 3) versetzt. Mittelhelle Bildteile sind weniger betroffen und helle Stellen fast überhaupt nicht.

Es reicht also nicht, einem Bild die Farbinformationen zu entziehen und es anschließend gelblich einzufärben -- der täuschend nachgemachte Effekt erfordert subilere Maßnahmen. Eric Jeschke hat auf [2] eine Reihe von Gimp-Operationen zusammengestellt, die täuschend echte Vorkriegsbilder produzieren. Mit dem Perl-Modul Gimp vom CPAN lassen sich die Einzelschritte in ein Perl-Skript verpacken, das der Nostaligiefreund ohne nennenswerten Arbeitsaufwand auf ganze Fotoreihen loslassen kann.

Abbildung 3: Die Farbe "Sepia", ein heller Gelb-Braunton mit dem RGB-Wert [162,138,101].

Durch die Brust ins Auge

Das Skript in Listing sepiafy nimmt auf der Kommandozeile eine Bilddatei entgegen, unterwirft sie einer Reihe von Transformationen und spuckt anschließend ein künstlich gealtertes Schwarz-Weiß-Bild mit Gelbstich aus. Der Aufruf sepiafy bild.jpg erzeugt die Ergebnisdatei bild-sepia.jpg, die man anschließend mit einem Image-Viewer wie zum Beispiel eog (Eye-of-Gnome) ansehen kann.

Wie schon einmal in [5] erläutert, verlangen Gimp-Skripts zunächst einige bürokratische Verwaltungsmaßnahmen, um Kontakt mit dem Gimp-Programm aufzunehmen. Die in Zeile 20 aufgerufene Funktion register() definiert einen Namen, einen Menüeintrag und noch weitere, in der Kommandozeilenversion eigentlich unnütze Angaben wie Autor und Hilfstext. Zeile 33 ruft schließlich mit main() das Gimp-Programm auf, das wiederum, wegen des Eintrags in Zeile 30, die ab Zeile 36 definerte Funktion sepiafy() aufruft. Von hinten durch die Brust direkt ins Auge!

Farben aussaugen

Die Funktion gimp_file_load() in Zeile 39 lädt Bilddateien in allen von Gimp unterstützten Formaten von der Festplatte und wandelt sie ins von Gimp intern genutzte Format um. Schlägt der Ladevorgang fehl, zum Beispiel weil die angegebene Datei nicht vorhanden oder unlesbar ist, liefert gimp_file_load() den Wert undef zurück und der Test in Zeile 42 bricht sofort das Programm ab.

Um Manipulationen am Bild auszuführen, ist zunächst das betroffene ``Drawable'' zu ermitteln, im vorliegenden Fall ist es der gerade aktive (und bisland einzige) Layer im Gimp-Image. Die Funktion image_get_active_layer() mit der von der Ladefunktion vorher zurückgelieferten Bildreferenz $img als Argument gibt ein Handle auf den zu bearbeitenden Bild-Layer zurück.

Dessen Methode desaturate_full() lässt aus den Bilddaten sämtliche Farbinformationen verschwinden. Der Parameter 2 legt dabei das Verfahren DESATURATE-AVERAGE fest. Weitere mögliche Werte wären die Methoden DESATURATE-LIGHTNESS (0) oder DESATURATE-LUMINOSITY (1), die Farben aufgrund ihrer Helligkeit oder ihrer Leuchtkraft in Grauwerte umwandeln und leicht unterschiedliche Ergebnisse liefern.

Abbildung 4: Nach dem Aufruf der Funktion desaturate_full() verschwinden alle Farbinformationen und es bleibt ein Schwarz-Weiß-Bild.

Nicht nur Grau in Grau

Übrig bleibt ein Graustufenbild (Abbildung 4), in das nun der helligkeitsbasierte Gelbstich hineinfahren muss. Hierzu legt sepiafy mit der Funktion layer_copy() einen weiteren Layer $sepia_layer an, der eine Kopie des Original-Layers des Bildes ist, um dem Skript Angaben von Bildhöhe und Breite und anderem Krimskrams zu ersparen. Der Parameter 1 stellt sicher, dass der neue Layer einen sogenannten Alpha-Channel anlegt, den er später zum Anlegen einer Layer-Maske brauchen wird. Dazu später mehr.

Den Modus, mit dem der neue Layer mit den alten interferiert, um ein Gesamtbild zu erzeugen, setzt Zeile 52 auf COLOR_MODE. Ein anschließender Aufruf von image_add_layer() fügt den neu erzeugten, aber herrenlosen Layer über dem aktuellen Layer im Layer-Dialog des gerade bearbeiteten Bildes ein. Der Parameter -1 gibt an, dass er im Layer-Dialog ganz oben zu liegen kommt.

Wozu der neue Layer $sepia_layer? Er wird mit der RGB-Farbe (162,138,101), dem gelb-braunen Sepia-Ton, gefüllt und anschließend mittels einer Layer-Maske helligkeitsabhängig auf den Original-Layer appliziert. Das Füllen mit der via gimp_context_set_foreground() gesetzten Vordergrund-Farbe erledigt die Methode drawable_fill() in Zeile 59. Der Parameter 0 steht für das Verfahren FOREGROUND-FILL.

Listing 1: sepiafy

    01 #!/usr/bin/perl
    02 use warnings;
    03 use strict;
    04 
    05 use Gimp qw(:auto);
    06 use Gimp::Fu;
    07 use Getopt::Std;
    08 use Log::Log4perl qw(:easy);
    09 
    10 Log::Log4perl->easy_init($DEBUG);
    11 DEBUG "Starting up";
    12 
    13 my $menu = 
    14     "<Toolbox>/Xtns/Perl-Fu/Sepiafy";
    15 
    16 my $file = $ARGV[0];
    17 die "No file" 
    18       unless defined $file;
    19 
    20 register(
    21   "perl_fu_sepiafy",   # Name
    22   "Sepia Toning",      # Explain
    23   "",                  # Help 
    24   "",                  # Author
    25   "",                  # Copyright
    26   "",                  # Date
    27   $menu,               # Menu
    28   "*",                 # Images accepted
    29   [ undef ],           # No parameters
    30   \&sepiafy            # Function
    31 );
    32 
    33 exit main();
    34 
    35 ###########################################
    36 sub sepiafy { 
    37 ###########################################
    38 
    39   my $img = gimp_file_load(
    40       RUN_NONINTERACTIVE, $file, $file);
    41 
    42   die "Can't load $file" unless $img;
    43 
    44   my $layer = image_get_active_layer($img);
    45 
    46   DEBUG "Desaturate";
    47   $layer->desaturate_full(2);
    48     # 2: Average
    49 
    50   my $sepia_mask = $layer->layer_copy(1);
    51     # 1: Add Alpha Channel
    52   $sepia_mask->layer_set_mode(COLOR_MODE);
    53 
    54     # Insert layer above active layer
    55   $img->image_add_layer($sepia_mask, -1);
    56 
    57   gimp_context_set_foreground( 
    58                        [162, 138, 101] );
    59   $sepia_mask->drawable_fill(0);
    60     # 0: FOREGROUND-FILL
    61 
    62   DEBUG "Adding layer mask";
    63   my $layer_mask = 
    64          $sepia_mask->layer_create_mask(0);
    65            # 0: White mask
    66   $sepia_mask->layer_add_mask( 
    67                              $layer_mask );
    68 
    69   $layer->edit_copy();
    70 
    71   my $float = $layer_mask->edit_paste(0);
    72     # 0: Clear selection 1: Paste behind it
    73   $float->invert();
    74   $float->floating_sel_anchor();
    75 
    76   DEBUG "Flattening image";
    77   $img->flatten();
    78   $layer = $img->get_active_layer;
    79 
    80   $layer->curves_spline(HISTOGRAM_VALUE, 
    81                   [0,0, 58, 36, 255, 255]);
    82 
    83   $file =~ s/\./-sepia./g;
    84   DEBUG "Saving $file";
    85   gimp_file_save(
    86     RUN_NONINTERACTIVE,
    87     $img,
    88     $layer,
    89     $file,
    90     $file);
    91 
    92   return $img;
    93 }

Wäre es das Ziel, den Sepia-Ton gleichmäßig auf den Original-Layer zu applizieren, könnte das Skript an dieser Stelle die verschiedenen Layer zusammenfalten und aufhören, denn der in Zeile 52 eingestellte COLOR_MODE würde die Farbinformationen der beiden Layer sanft vermischen (Abbildung 5).

Abbildung 5: Gleichmäßig applizierte Gelbtöne färben nicht nur die dunklen Bildstellen, sondern auch die hellen.

Da sepiafy aber dunkle Stellen stärker vergilben will als helle, kommt noch eine sogenannte Layermaske zum Einsatz. Sie gibt an, wie der Gelbstich-Layer mit dem Original-Layer des Bildes interagiert. Zeile 64 erzeugt die Maske. Der Parameter 0 steht für ADD-WHITE-MASK, also eine Maske, deren Pixel zunächst alle weiß sind. Der anschließende Aufruf von layer_add_mask() fügt die neu erzeugte, noch herrenlose Maske zum Layer $sepia_mask hinzu.

Maskenball

Wie genau funktionieren eigentlich Masken im Gimp? Masken selektieren Bildteile, ganz genau wie man mit dem Selektionstool beispielsweise ein Rechteck in einem Bild selektiert, das Gimp daraufhin mit den 'wandernden Ameisen' darstellt. Diese Selektionsinformation speichert Gimp als Schwarzweiß-Bild, dessen weiße Regionen selektierte Bildteile repräsentieren und dessen dunkle Pixel unselektierte. Selektiert der User also ein Rechteck in der Bildmitte, ist die zugehörige Maske ein schwarzes Bild mit einem weißen Rechteck in der Mitte. Im Gegensatz zu Selektionen mit der Maus können Masken außerdem noch Grauwerte definieren, die zugehörigen Bildteile sind dann nur so 'halb' selektiert.

Diese Grauwertbilder lassen sich nun auf sehr elegante Weise als Bildfilter verwenden. Statt mühsam Bildteile von Hand zu selektieren, definiert der User ein Maskenbild und Gimp selektiert automatisch Teile des Originalbildes, an denen das Maskenbild helle Pixel aufweist.

Um die Transparenz eines Layers zu definieren, erlaubt es Gimp, zu jedem Layer eine sogenannte Layer-Maske zu definieren. Dieses Grauwertbild appliziert an seinen weißen Stellen die Bildwerte des Layers zu 100%, während die schwarzen Maskenpixel den Layer 100% transparent machen, ihn also deaktivieren. An den grauen Pixeln der Layer-Maske appliziert Gimp den Wert des Layers zu einem entsprechenden Bruchteil.

Abbildung 6 zeigt den Layer-Dialog mit einem testweise eingetragenen dicken schwarzen Kreis auf weißem Hintergrund als Layer-Maske im Sepia-Layer, der Überlagerungsmodus des Layers ist auf ``Normal'' eingestellt. Wie in Abbildung 7 sichtbar, lässt Gimp das Originalbild so im Schwarzteil der Maske unangetastet, während er im Weißteil die Sepia-Farbe ohne Rücksicht auf die Bildinformation aufträgt. Dies ist zweifellos noch nicht die richtige Strategie, aber mit einem Graustufenbild kommen wir der Sache schon näher.

Abbildung 6: Eine Layer-Maske mit einem dicken schwarzen Kreis als ...

Abbildung 7: ... lässt das Bild im Schwarzteil der Maske unangetastet, appliziert aber den Sepia-Ton vollständig auf die Teile des Bildes, an denen die Maske weiß ist.

Original als Maske

Wie also muss nun die Maske aussehen, damit sie eine zum Verwechseln ähnliche Sepia-Färbung des Orignalbildes vornimmt, also an dessen dunklen Stellen die Schleusen öffnet und an dessen hellen Stellen dicht macht? Ist ein Pixel des Original-Layers schwarz, muss die Maske dort weiß sein, und sie appliziert den Sepia-Farbton zu 100%. Ist der Original-Layer hingegen weiß, ist die Maske schwarz und der Sepia-Layer kommt überhaupt nicht zum Original-Layer durch, lässt ihn also in Ruhe. Und an Grauwerten liegt die Maske entsprechend in der Mitte. Jetzt ist die Lösung hoffentlich offensichtlich: die gesuchte Maske ist genau das invertierte Graustufenbild des Original-Layers!

Um dies zu realisieren, kopiert Zeile 69 das Bild im Original-Layer in den Gimp-internen Cut-Paste-Puffer, und Zeile 71 streift dessen Inhalt wieder auf der Layer-Maske $layer_mask ab. Zurück kommt eine Referenz auf eine ``Floating Section'', die Zeile 73 farb-invertiert und Zeile 74 in der Layer-Maske verankert.

In Gimp würde der User hierzu den Original-Layer im Layer-Dialog anklicken, dann zum Bildfenster wechseln, ``Select-All'' drücken und mit ``CTRL-C'' den Bildinhalt in den Cut-Paste-Puffer kopieren. Anschließend ein Mausklick auf die Layer-Maske des Sepia-Layers (zweites Thumbnail in der Layer-Zeile), zurück zum Bildfenster und den Inhalt mit ``Paste'' dort eingespielt. Dies erzeugt eine ``Floating Section'' im Layers-Dialog, die ein Klick auf den Anker am unteren Rand des Layers-Dialogs in die Layer-Maske einbetoniert. Anschließend werden noch die Farben der Layer-Maske über ``Colors->Invert'' invertiert und der Layers-Dialog sieht aus wie in Abbildung 8.

Abbildung 8: Das Skript fügt in Gimp einen neuen Layer ein, dessen Bildinhalt eine Sepia-farbene Fläche und dessen Layer-Maske

das invertierte Originalbild ist.

Es bleibt nur noch, mittels der Methode flatten() die beiden Layer zu einem aktiven zusammenzufalten. Dies wirbelt allerdings die verfügbaren Layers durcheinander und das Skript muss anschließend mit der Methode get_active_layer() der Bildreferenz eine Referenz auf den einzig verbliebenen Layer hervorholen.

Das Resultat mit ordnungsgemäß aufgetragener Sepia-Tönung ist in Abbildung 9 zu sehen, und damit das Bild noch etwas dramatischer wirkt, wie in Abbildung 11 gezeigt, dunkelt die Funktion curves_spline dunkle Töne noch etwas ab, lässt aber hellere unbeschadet. Die sechs übergebenen Koordinaten definieren einen Graphen wie in Abbildung 10, die die gewünschte Manipulation am Bild vornimmt, ganz so, als hätte der User dies in Gimps Curves-Dialog verlangt. Eine lineare Kurve im Curves-Dialog lässt das Bild in Ruhe, eine Ausbuchtung nach unten hingegen führt zu einer Verdunkelung im jeweiligen Helligkeitsbereich.

Abbildung 9: Mit Sepia-Toning kommen Gelbtöne hauptsächlich in den dunklen Bildstellen zum Einsatz.

Abbildung 10: Durch leichtes Absenken der dunklen Töne bei Beibehaltung der helleren ergibt sich ein dramatischeres Bild.

Abbildung 11: Noch einmal zum Vergleich das Endergebnis, mit dramatisierter Abdunkelung der dunkleren Bildteile über den Curves-Dialog.

Die Funktion gimp_file_save() speichert das Ergebnis unter einem neuen Dateinamen ab, den Zeile 83 durch Anhängen der Zeichenkette ``-sepia'' an den Originalnamen erzeugt hat.

Installation

Zur Installation der notwendigen Perl-Module Gimp und Gimp::Fu genügt unter Ubuntu Hardy Heron ein einfaches

    sudo apt-get install libgimp-perl

Ältere Ubuntu-Versionen haben eine Macke, aber [5] zeigt, wie man es mit ein paar Tricks trotzdem installiert. Das Log4perl-Modul (erhältlich vom CPAN oder als Paket liblog-log4perl-perl der Linux-Distribution) zeigt den Fortschritt der Bildumwandlung auf der Kommandozeile an. Wer das Skript lieber schweigen lässt, kommentiert den Aufruf von easy_init() in Zeile 10 des Skripts einfach aus.

Abbildung 12: Gimps "Procedure Browser" hilft, die richtige API Funktion zu finden.

Die Dokumentation von Gimps Perl-Schnittstelle ist nicht sehr detailliert, aber Gimp verfügt über einen exzellenten Procedure- Browser, den der User über das Menü Xtns->Procedure Browser aufrufen kann. Gibt er anschließend ins Suchfeld einen erratenen Teil des gewünschten Funktionsnamens ein (z.B. ``load'' oder ``save'' oder ``layer''), zeigt der Procedure-Browser eine Liste von verfügbaren API-Funktionen mit minutiös dokumentierten Parametern und Rückgabewerten an. So ist es meist möglich, mit etwas Gespür jede im Gimp User-Interface per Mausklick verfügbare Aktion via API-Funktion auszuführen und alle erdenklichen Bildmanipulationen zu automatisieren.

Infos

[1]

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

[2]

``Sepia Toning'', Eric Jeschke, http://www.gimp.org/tutorials/Sepia_Toning/

[3]

``Retouch Pro: Luminosity Masks and Sepia Toning'', http://www.retouchpro.com/tutorials/lum-mask-sepia.html

[4]

http://en.wikipedia.org/wiki/Sepia_tone

[5]

``Farbenspiel'', Michael Schilli, Linux-Magazin 2008/07, http://www.linux-magazin.de/heft_abo/ausgaben/2008/07/farbenspiel

[6]

``Grokking the Gimp'', Carey Bunks, 2000, New Riders Publishing

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.