AA_precision_bugdog_sxc_730329.jpg

© Bugdog, sxc.hu

Fix und fertig

Bilder verarbeiten mit der Magick Scripting Language

06.05.2011
,
Die Magic Srcipting Language hilft Ihnen, Bilder mit einem fest definierten Satz an Parametern zu bearbeiten. In Verbindung mit geschickter Shell-Programmierung nutzen Sie so die Ressourcen eines Systems optimal aus.

Als erstes Ergebnis beim Vereinfachen der Arbeit entstand im Teil 1 [1] ein Skript, das alle Parameter zum Verändern als Variablen enthielt. Das funktioniert sehr effizient, und es gelingt sehr leicht, die Modifikationen eindeutig einem ausgewählten Bildformat und Dateimuster zuzuordnen.

Möchten Sie hingegen mehrere Bilder individuell anzupassen, stoßen Sie so recht schnell an Grenzen und müssen sich eine brauchbare Alternative überlegen. In manchen Fällen hilft die Magick Scripting Language (MSL) weiter, und daher verknüpft dieser Workshop die Technik mit den Erkenntnissen aus dem Shell-Skripting aus dem Teil 1.

Serie Automatische Bildverarbeitung

Teil 1: Imagemagick, Graphicsmagick LU 03/2011, S. 84 http://www.linux-community.de/22947
Teil 2: Magick Scripting Language LU 06/2011, S. 84 http://www.linux-community.de/22948

Magische Sprache

MSL gehört zu den Imagemagick- und Graphicsmagick-Paketen. Es handelt sich dabei um einen XML-Dialekt [2] und als solcher folgt er daher den entsprechenden Konventionen. Bei MSL-Dateien handelt es sich um reine Textdateien mit einem Markup. Jeder Texteditor eignet sich zum Bearbeiten, und es existieren zudem Werkzeuge, die die Syntax auf Korrektheit prüfen (siehe Kasten "Validieren einer XML-Datei").

Validieren einer XML-Datei

Für komplexere XML-Dateien empfiehlt sich in jedem Fall das Überprüfen auf syntaktische Korrektheit. Dazu zählt beispielsweise, ob Sie jedes geöffnete Element in der richtigen Knotenebene wieder geschlossen, alle Attribute korrekt in Anführungszeichen gesetzt haben und ob Kommentare in sich abgeschlossen sind.

Das Standardisierungsgremium für Internetprotokolle W3C bietet dazu auf seiner Webseite [5] die Möglichkeit an, eigene XML-Dateien hochzuladen und sofort auf Korrektheit zu überprüfen. Das Ergebnis sieht dann analog zu Abbildung 1 aus.

Abbildung 1: Ergebnis der W3C-Validierung.

Auf der Kommandozeile hilft das Werkzeug Xmllint [6]. Es gehört zur Libxml, der XML-Bibliothek aus dem Gnome-Projekt. Für Debian und Ubuntu heißt das dazugehörige Paket Libxml2-utils. Sie verwenden Xmllint folgendermaßen:

$ xmllint --noout --valid bild.xml

Die Option --noout unterdrückt die Ausgabe der XML-Datei auf der Standardausgabe, --valid sorgt für das Überprüfen der angegebenen Datei auf Korrektheit ("XML-Konformität"). Gefundene Fehler gibt die Software am Ende auf der Standardausgabe aus.

In einer MSL-Datei legen Sie die Modifikationen fest, die Sie an einer Bilddatei vornehmen möchten. Diese einzelnen Anweisungen führt der Interpreter nacheinander aus. Für jedes Kommando gibt es in MSL ein eigenes Element, oft mit zusätzlichen Attributen. XML-Parser legen die Werte der Elemente und Attribute in einer Baumstruktur ab und haben so die Möglichkeit, die Daten effizient zu verarbeiten. Listing 1 zeigt eine einfache MSL-Datei.

Listing 1

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="bild.png" />
 <resize geometry="640x480" />
 <write filename="bild-vorschau.png" />
</image>

In Zeile 1 steht der Kopf ("Header") der XML-Datei mit der verwendeten XML-Version (hier: 1.0) und der Bezeichnung für das Codierungsschema, in dem die einzelnen Zeichen in der XML-Datei abgespeichert sind (hier: UTF-8). Die Zeilen 2 und 6 markieren mit den Knoten <image> und </image> den Beginn und das Ende der Informationen zu einer Bilddatei.

Die Zeilen 3 bis 5 enthalten die Anweisungen, die ein Interpreter auf die Bilddatei anwendet. Zunächst erfährt der Konverter in Zeile 3 aus dem Element read mit dem Attribut filename den Name der angegebenen Bilddatei (bild.png) und liest diese ein. Anschließend skaliert er die Bilddaten auf die aus dem Attribut geometry des Elements resize gewonnene Breite (640 Pixel) und eine Höhe (480 Pixel). Das Element write gibt über das Attribut filename Aufschluss darüber, wie die Ausgabedatei heißt (bild-vorschau.png). Falls die angegebene Datei bereits existiert, überschreibt der Konverter diese ohne Vorwarnung mit dem neuen Bildinhalt.

Die MSL-Datei verarbeiten Sie mit dem Werkzeug Conjure (Teil von Imagemagick [3]) oder über die Option conjure von Graphicsmagick [4]. Die Aufrufe sehen für die beiden Werkzeuge wie folgt aus:

$ conjure bild-einfach.xml
$ gm conjure bild-einfach.xml

Beide interpretieren das übergebene MSL-Skript und arbeiten die Anweisungen wunschgemäß ab. Die beiden Programme verhalten sich konform zu traditionellen Unix-Tools – nur bei Fehlern erfolgt eine Nachricht an den Benutzer, ansonsten schweigen beide beharrlich.

Haben Sie mehr als ein einziges Bild zu bearbeiten, legen Sie für jede Bilddatei eine passende MSL-Datei an und erledigen danach mit Conjure das Umwandeln. Von Hand ist das aufwendig. Mit einem Shellskript wie in Listing 2 verkürzen Sie den Aufwand erheblich.

Listing 2

#!/bin/bash
for bilddatei in $(find . -type f -printf '%P\n');
do
        # Name und Dateityp ermitteln und prüfen
        fname=$(basename ${bilddatei})
        dateiname=${fname%.*}
        dateityp=${fname#*.}
        case "$dateityp" in
        png|PNG)
                msl_datei="${bilddatei%.*}.xml"
                echo "Lege an: ${msl_datei} für ${bilddatei}"
                cat > ${msl_datei} << EOF
<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="${fname}" />
<resize geometry="200x200" />
<write filename="${dateiname}-vorschau.png" />
</image>
EOF
                # Bild transformieren
                gm conjure "$msl_datei"
        esac
done

Das Skript analysiert zuerst den Inhalt des aktuellen Verzeichnisses und merkt sich alle Dateien in einer Liste (Zeile 2). In der For-Schleife überprüft es jeden Eintrag mittels File, ob es sich dabei um eine Bilddatei mit der passenden Dateiendung handelt (Zeile 9). Für jede PNG-Datei aus der Liste generiert es eine passende MSL-Datei und speichert diese ab (Zeilen 11 bis 20). In Zeile 11 setzt es den vollständigen Name der MSL-Datei aus dem Namen der Bilddatei und dem Suffix .xml zusammen.

Die Zeilen 14 bis 19 repräsentieren die Zeilen der MSL-Datei, wobei an den passenden Stellen die Inhalte der beiden Variablen fname und dateiname eingesetzt werden. Danach ruft das Skript in Zeile 22 Graphicsmagick mit der soeben erzeugten MSL-Datei als Parameter aufgerufen und nimmt die Bildtransformationen vor.

Um das Skript zu starten, rufen Sie es als ./msl.sh in einem Terminal auf. Einmal durchgelaufen liefert das Skript für jede PNG-Datei ein passendes Vorschaubild mit der maximalen Höhe beziehungsweise maximalen Breite von 200 Pixel.

Performance

Ein Kriterium für die Qualität dieses Ansatzes liegt in der Laufzeit des Skriptes. Wir erhalten diese, in dem wir die Zeit zwischen dem Start und dem Ende messen. Dabei kommt das Unix-Kommando Time zum Einsatz. Der Aufruf time ./msl.sh liefert als Ergebnis auf dem Testsystem eine Laufzeit von rund einer Minute für rund 100 Bilddateien. Jede Bilddatei hat eine Auflösung von 2048x1536 Pixel und ist etwa 1 MByte groß.

Um die Laufzeit zu verringern, hilft es, sich das Skript etwas genauer anzuschauen und Optimierungspunkte zu finden. Großes Potential zum Verbessern beinhaltet Zeile 22. In jedem Schleifendurchlauf startet ein GM-Prozess, und erst nach dessen Ende setzt das Skript seine Arbeit mit der nächsten Iteration der For-Schleife fort. Jeder GM-Aufruf ist unabhängig von einem anderen. Ergänzen Sie den Aufruf in Zeile 22 um ein Ampersand (&) am Ende der Zeile, läuft jeder Aufruf ab jetzt im Hintergrund ab und blockiert das Abarbeiten der Schleife nicht weiter.

Der Laufzeittest auf einem Rechner mit Doppelkernprozessor ergibt eine Zeitersparnis von 50 Prozent auf eine halbe Minute (siehe Tabelle "Laufzeittests"). Existieren weitere Prozessorkerne, verringert sich die Laufzeit insgesamt stärker, aber nicht mehr genau im gleichen Verhältnis wie bisher. Der Aufwand zum Verwalten der Prozesse fällt deutlich mit ins Gewicht. Trotzdem haben Sie damit eine Lösung, mit der Sie zunächst zufrieden sein können, da es die heutigen Mehrkernprozessoren deutlich besser ausnutzt.

Laufzeittests

Skript Laufzeit  
    35 Bilder 100 Bilder
Listing 2 (ohne Parallelisierung) 0:23 0:58
Listing 2 (mit Parallelisierung) 0:10 0:30
Listing 4 (ohne Parallelisierung) 0:19 1:00
Listing 4 (mit Parallelisierung) 0:07 0:29

Das Tool Htop [8] zeigt sehr schön die Last auf der Hardware (Abbildung 2). Beide Prozessoren kommen in etwa gleichmäßig zum Zuge und sorgen für unser erfreuliches Ergebnis.

Abbildung 2: Htop visualiert die Systemlast.

Die verwendete Methoden beim Programmieren hat aber Nachteile: Das Skript startet unabhängig von der Lastsituation des Gesamtsystems sehr schnell genauso viele Prozesse, wie es Bilder findet. 100 Bilddateien liegen noch im Rahmen, nimmt die Zahl der Dateien jedoch deutlich zu, entbrennt auf der Maschine ein Kampf um die Ressourcen. Der Festplattenzugriff und die RAM-Anforderungen treiben den Kernel zum Auslagern von Daten in die Swap-Partition.

Eigentlich sollte das Skript genau so viele Prozesse starten, wie CPU-Kerne vorhanden sind. Auf jedem Kern arbeitet der Rechner einen Prozess ab, ohne sich Rechenkapazität mit einem anderen Prozess zu teilen. Die Information zur Anzahl der CPU-Kerne steht im Proc-Filesystem [9]. Sie ermitteln sie zur Laufzeit mit dem folgenden Shell-Befehl:

$ cat /proc/cpuinfo | grep "cpu cores"

Abbildung 3 zeigt die Ausgabe des Kommandos auf einem Quadcore-System mit zwei Kernen pro CPU.

Abbildung 3: Die Ausgabe von cpuinfo liefert Informationen über die Anzahl der CPU-Kerne.

Solche Informationen im Skript auf die Anzahl der gestarteten Prozesse abzubilden, wäre Thema eines Artikels über optimierten Ressourceneinsatz und sprengt den Rahmen dieses Beitrags. Das Grundprinzip lautet: Nur soviel Arbeit starten, wie auch Ressourcen bereit stehen.

Über diese Maßnahmen hinaus gerät ein weiterer Flaschenhals in den Blick – den genutzten Datenträger. Mit dem bisherigen Skript steigt nicht nur die Menge der Lese- und Schreibzugriffe auf dem Datenträger, es erzeugt auch etliche MSL-Dateien mit einer Größe von etwa 300 Byte. Diese belegen vergleichsweise viel Plattenplatz – meist einen ganzen Block pro Datei. Ein Block hat je nach Dateisystem eine Größe ab 512 Byte aufwärts. Da die MSL-Dateien recht klein sind, verschwendet das Skript mit dieser Methode eigentlich enorm viel Platz.

Weiterhin ist die Anzahl der I-Nodes pro Dateisystem begrenzt. Was bedeutet, sie dürfen nicht beliebig viele Dateien anlegen. Es besteht die Gefahr, dass das Skript die Eintragsliste auf dem Datenträger mit vielen kleinen Dateien füllt und Sie den Speicherplatz nicht vollständig nutzen können.

Um den beschriebenen Nachteil aufzuheben, besteht der nächste Schritt konsequenterweise darin, möglichst nur eine einzige MSL-Datei für alle Bilddateien zu benutzen. Dazu legen Sie ein MSL-Skript mit Variablen an, welches für alle Bilddateien passt. Listing 3 zeigt ein Beispiel für so ein Skript.

Listing 3

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="%[basename].png" />
 <resize geometry="%[dimensions]" />
 <write filename="%[basename]-vorschau.png" />
</image>

Darin kommen zwei Variablen zum Einsatz – basename und dimensions (Zeile 3, 4 und 5). Ins MSL-Skript fügen Sie die Variablen in eckige Klammern ein und stellen diesen ein Prozentzeichen voran. Die erste Variable (basename) beinhaltet den Namen der Bilddatei ohne Erweiterung, die zweite die Bildgröße, auf die Sie das Bild skalieren wollen. Für beiden Variablen übergeben Sie die Werte an Graphicsmagick als Parameter.

Der folgende Aufruf skaliert die Datei bild.png auf eine maximale Breite von 200 Pixel und speichert das Ergebnis in der Datei bild-vorschau.png ab:

$ gm conjure -basename bild -dimensions 200x200 png.xml

Um nun auch alle PNG-Dateien zu bearbeiten, modifizieren wir noch unser Shellskript entsprechend. Wir passen die Variablennamen im Aufruf von Graphicsmagick an und übergeben das MSL-Skript aus Listing 3 als Parameter.

Listing 4

#!/bin/bash
for bilddatei in $(find . -type f -printf '%P\n');
do
        # Name und Dateityp ermitteln und prüfen
        fname=$(basename ${bilddatei})
        dateiname=${fname%.*}
        dateityp=${fname#*.}
        case "$dateityp" in
        png|PNG)
                msl_datei="${bilddatei%.*}.xml"
                echo "Bearbeite: ${bilddatei}"
                msl_datei="png.xml"
                dimension="200x200"
                # Bild transformieren
                gm conjure -basename "$dateiname" -dimensions "$dimension" "$msl_datei"
        esac
done

Bei Laufzeittests analog zu Listing 2 ergibt sich ein ähnliches Bild: Für rund 100 Bilddateien benötigt das Skript rund 1 Minute, um Sie zu verkleinern. Ergänzen Sie den Aufruf von Graphicsmagic in Zeile 17 am Ende um ein "&", laufen die Prozesse wiederum parallel ab und die Laufzeit verringert sich um 50 Prozent auf etwa eine halbe Minute.

Im Vergleich zu Listing 2 erreichen Sie damit ein ähnliches Zeitverhalten, aber mit deutlich geringeren Anforderungen an die erforderlichen Ressourcen. Sie benötigen jedoch nur noch eine einzige MSL-Datei für alle Bilddateien, anstatt diese dynamisch zur Laufzeit zu erzeugen.

Zusammenfassung

Viele Werkzeuge verfügen über Erweiterungen, die sich erst in einem bestimmten Kontext als sehr nützlich erweisen. Der Workshop hat gezeigt, wie einfach Sie Transformationen mit Hilfe der Magick Scripting Language (MSL) und den Magick-Bibliotheken auf eine größere Datenmenge anwenden. Der nächste Teil 3 dieser Serie stellt die beiden Programmiersprachen Perl und Python und dazu passende Bibliotheken in den Mittelpunkt. 

Infos

[1] Bilder automatisiert verarbeiten (Teil 1): Frank Hofmann, "Am laufenden Band", LU 03/2011, S. 84, http://www.linux-community.de/22947

[2] XML: http://de.wikipedia.org/wiki/Xml

[3] ImageMagick: http://www.imagemagick.org

[4] Graphicsmagick: http://www.graphicsmagick.org

[5] W3C Markup Validation Service: http://validator.w3.org/check

[6] Xmllint (Libxml2): http://xmlsoft.org/

[7] Awk-Grundlagen: Stefan Lagotzki, "Ein Tool für alle Fälle", LU 10/2005, S. 88, http://www.linux-community.de/9036

[8] Htop: http://htop.sourceforge.net/

[9] Proc-Dateisystem: http://www.linux-praxis.de/lpic1/lpi101/proc.html

Der Autor

Wolfram Eifler studierte an der TU Berlin Informatik und arbeitet seit über zwei Jahrzehnten als Systemadministrator und Software-Entwickler im Unix-Bereich. Seine Schwerpunkte liegen bei robusten Systemen und im Bereich der Leistungsanalyse.

Frank Hofmann hat Informatik an der TU Chemnitz studiert. Derzeit arbeitet er in Berlin im Open-Source-Expertennetzwerk Büro 2.0 als Dienstleister mit Spezialgebiet auf Druck und Satz. Er gehört zum Vorstand der Linux User Group Potsdam (upLUG).

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 

Ähnliche Artikel

Kommentare