Perl und Grafik
Description: Einführung in die CGI-Programmierung mit Perl im Internet. Vorkenntnisse der Programmierung sind notwendig.
Keywords: Multimedia WWW Internet Webdesign CGI Perl Programmierung Grafik

| ### Grundlagen CGI-Programmierung mit Perl von Prof. Jürgen Plate

## 6 Grafiken erzeugen mit dem GD-Modul

> Vorbemerkung: Damit diese Programme funktionieren, muß das GD-Modul von Perl installiert sein. Weiterhin braucht man die GD-Bibliothek von Thomas Boutell (http://www.boutell.com/gd/), die ihrerseits die Bibliotheken Libpng und Zlib benötigt. Sie müssen also zunächst die drei Bibliotheken installieren (mit apt-get, yum etc.) und danach das GD-Modul von Perl.

Es gibt zahlreiche Anwendungen, bei denen Bilder zur Laufzeit des Programms manipuliert oder erzeugt werden sollen, z.B.:

- zur Generierung von Grafiken aus irgendwelchen Daten,
- zur Darstellung von Daten als Balken-, Torten- oder Liniendiagramm
- zur Erzeugung von Thumbnails aus Bildern
- etc.

Dabei generiert Perl das Bild und speichert es entweder ab oder schickt es an ein Programm, welches in der Lage ist, diese Bilder darzustellen. Bei CGI-Skripten erfolgt die Ausgabe auf STDOUT. Wahlweise können gif-und png-Bilder sowie Bilder im eigenen gd-Format erzeugt werden. Ein einfaches Bild wird von folgenden Skript erzeugt:

    use GD;
    use strict;

    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    $bild->rectangle(20,20,80,80,$rot);
    $bild->rectangle(30,30,70,70,$blau);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Im Programm wird zuerst das GD-Modul geladen. Dann wird ein (noch virtuelles Bild der Größe 100 x 100 Pixel erzeugt). Die erste der drei erzeugten Palettenfarben bildet die Hintergrundfarbe. Danach wird ein rotes und ein blaues Rechteck gemalt.

Nachdem mit "use GD" das GD-Modul eingebunden wurde, bildet das Programm eine Instanz der Klasse GD::Image. Diesem Objekt ordnen wir dann drei Farben zu. Die Methode `collorAllocate` hat drei Parameter, und zwar der jeweilige 8-Bit-Wert für rot, grün und blau. Jede Farbe wird als eine Mischung aus diesen drei Grundfarben definiert. Die erste Farbe, die zugewiesen wird, ist immer die Hintergrundfarbe des Bildes. Die Werte für die anderen Farben werden in Variablen abgespeichert und können dann zugewiesen werden. Weitere Methoden in diesem Zusammenhang sind:

- `colorDeallocate`: Farbzuweisungen wieder löschen.
- `colorsTotal()`: Anzahl der allozierten Farben.
- `colorClosest(r,g,b)`: Finde die am nächsten liegende Farbvariable in der Palette (oder -1, falls noch keine Farbe festgelegt wurde).
- `colorExact(r,g,b)`: Finde die zum RGB-Wert gehörige Farbvariable in der Palette (oder -1, falls die Farbe nicht vorhanden ist).
- `getPixel(x,y)`: Liefert die Farbvariable des angegebenen Pixels.
- `($r,$g,$b) = rgb($farbe)`: Liefert die drei Grundfarben einer Farbvariablen.

Bei der Farbzuordnung kann man eine Farbe, meist die Hintergrundfarbe auf "transparent" setzen:

    $bild->transparent($gray);

Soll das Bild sich Schritt für Schritt aufbauen, setzt man zusätzlich das Bild auf "interlaced":

    $bild->interlaced('true');

Die im Beispiel verwendete Methode `rectangle` hat insgesamt fünf Parameter. Die ersten zwei Parameter definieren die linke obere Ecke, die folgenden zwei die rechte untere Ecke und der letzte Parameter die Farbe.

Wie Linien gezeichnet werden, zeigt das folgende Beispiel. Die Methode `line` hat fünf Parameter: die Koordinaten von Anfangs- und Endpunkt sowie wieder die Farbe.

    use GD;
    use strict;
    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    my $gruen = $bild->colorAllocate(0,255,0);
    $bild->rectangle(20,20,80,80,$rot);
    $bild->line(20,20,80,80,$gruen);
    $bild->line(80,20,20,80,$gruen);
    $bild->line(50,20,50,80,$gruen);
    $bild->line(20,50,80,50,$gruen);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Die Linienbreite von Linie, Rechteck und Kreisbogen kann mittels `setThickness($thickness)` verändert werden.

Eine strichlierte Linke bekommt man mit `dashedLine` anstelle von `line`. Weitere Linienmuster lassen sich mit `setStyle` erzeugen. Diese Methode hat ein Array von Farbwerten als Parameter. Das so erzeugte Linienmuster kann bei Linie, Rechteck und Kreisbogen anstelle einer Farbe angegeben werden. Dazu ein Beispiel:

    use GD;
    use strict;
    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $green = $bild->colorAllocate(0,255,0);
    my $yellow = $bild->colorAllocate(255,255,0);

    my @linestyle = ($blue,$blue,     # 2 Pixel blau
                     $red,$red,       # 2 Pixel rot
                     $yellow,$yellow, # usw.
                     gdTransparent,gdTransparent);

    $bild->rectangle(20,20,80,80,$red);
    $bild->setStyle(@linestyle);
    $bild->rectangle(25,25,75,75,gdStyled);
    $bild->rectangle(24,24,74,74,gdStyled);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Das Rechteck kann auch ausgefüllt werden, wenn man statt der Methode `rectangle` nun `filledRectangle` verwendet. Die Parameter sind bei beiden Methoden gleich.

    use GD;
    use strict;
    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    my $gruen = $bild->colorAllocate(0,255,0);
    $bild->filledRectangle(20,20,50,50,$rot);
    $bild->filledRectangle(50,50,80,80,$rot);
    $bild->filledRectangle(20,50,50,80,$blau);
    $bild->filledRectangle(50,20,80,50,$blau);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Meist enthält ein Bild auch noch Text, der in der Regel ebenfalls dynamisch erzeugt werden soll. Wie Text in ein Bild eingefügt wird, zeigt folgendes Skript:

    use GD;
    use strict;

    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    $bild->rectangle(20,30,80,80,$rot);
    $bild->string(gdLargeFont,2,5,"Hello World",$blau);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Die Methode `string` hat vier Parameter, Font, Startposition (X,Y), den auszugebenden Text und die Textfarbe. Eine Feinsteuerung der Schrift ist nicht möglich, man hat lediglich die Auswahl zwischen gdTinyFont (5x8), gdSmallFont (6x12), gdMediumBoldFont (7x13) gdLargeFont (8x16) und gdGiantFont (9x15).

    use GD;
    use strict;

    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    $bild->string(gdTinyFont,10,10,"Hello World",$blau);
    $bild->string(gdSmallFont,10,25,"Hello World",$blau);
    $bild->string(gdMediumBoldFont,10,40,"Hello World",$blau);
    $bild->string(gdLargeFont,10,70,"Hello World",$blau);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Mittels `stringUp` kann auch ein vertikaler Text (Laufrichtung von unten nach oben) gezeichnet werden. Leider sind die Stringmethoden recht simpel, es gibt also keinen mehrzeiligen Text usw. Man kann sich aber die Zeichengröße ausgeben lassen und dann für jede Zeile entsprechend positionieren:

    ($breite,$hoehe) = (gdSmallFont->width,gdSmallFont->height);

Es gibt jedoch nicht nur Linien und Rechtecke, sondern es lassen sich auch Kreise und Ellipsen zeichnen. Dazu dient die Methode `arc`, die im folgenden Beispiel gleich dreimal aufgerufen wird, um einen gut sichtbaren Kreis zu zeichnen. Die Bedeutung der sieben Parameter ist Mitte-X, Mitte-Y, Breite-X, Breite-Y, Startwinkel, Winkel-Bogen und wieder die Farbe.

    use GD;
    use strict;
    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    my $gruen = $bild->colorAllocate(0,255,0);
    $bild->filledRectangle(20,20,50,50,$rot);
    $bild->filledRectangle(50,50,80,80,$rot);
    $bild->filledRectangle(20,50,50,80,$blau);
    $bild->filledRectangle(50,20,80,50,$blau);
    $bild->arc(50,50,50,50,0,360,$gruen);
    $bild->arc(50,50,51,51,0,360,$gruen);
    $bild->arc(50,50,52,52,0,360,$gruen);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Mitte-X und Mitte-Y bezeichen den Mittelpunkt des Bogens, Breite-X und Breite-Y jeweils die Ausdehnung in der X- und Y-Achse. Der Startwinkel legt fest, wo der Bogen beginnen soll (Koordinatensystem wie in der Mathematik üblich; 0 Grad = waagrecht rechts). Danach folgt die Angabe, wieviele Grad der Bogen durchlaufen soll (für einen Quadranten z.B. 90). Dazu noch ein Beispiel.

    use GD;
    use strict;

    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    my $gruen = $bild->colorAllocate(0,255,0);
    my $gelb = $bild->colorAllocate(255,255,0);
    $bild->filledRectangle(20,20,50,50,$rot);
    $bild->filledRectangle(50,50,80,80,$rot);
    $bild->filledRectangle(20,50,50,80,$blau);
    $bild->filledRectangle(50,20,80,50,$blau);
    # gruene Ellipse unten
    $bild->arc(50,50,50,30,0,180,$gruen);
    #gelber Kreis oben
    $bild->arc(50,50,50,50,181,360,$gelb);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Schliesslich kann man mit `setPixel(x,y,farbe)` auch einzelne Pixel setzen.

Für das Zeichen von Polygonzügen, beispielsweise für die Darstellung vom Messwerten, muss zuerst ein neues Objekt definiert werden:

    $poly = new GD::Polygon;

Mittels der Methode `addPt` kann ein Stützpunkt zum Polygon hinzigefügt werden (z.B. `$poly ->addPt(10,5);`). Ruft man `$bild->polygon($poly,$rot);` auf, werden alle Punkte miteinander verbunden. Es entsteht ein Polygon. Wer will, kann dieses Polygon auch ausfüllen (`filledPolygon`). Der Polygonzug wird immer geschlossen.

    use GD;
    use strict;

    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blau = $bild->colorAllocate(0,0,255);
    my $rot = $bild->colorAllocate(255,0,0);
    my $gruen = $bild->colorAllocate(0,255,0);
    my $gelb = $bild->colorAllocate(255,255,0);

    my $poly = new GD::Polygon;
    $poly ->addPt(80,80);
    $poly ->addPt(20,70);
    $poly ->addPt(30,50);
    $poly ->addPt(40,15);
    $poly ->addPt(50,60);
    $poly ->addPt(15,20);
    $bild->polygon($poly,$rot);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Links der einfache Polygonzug, rechts gefüllt:

Die einzelnen Stützpunkte werden beginnend von 0 an durchnumeriert. Man kann sich auch ein Rechteck geben lassen, welches das Polygon exakt umschliesst:

    my @boundingbox = $poly->bounds;
    $bild->rectangle(@boundingbox, $blau);

Weitere Methoden sind:

- `delete(index)`: Löschen eines Stützpunktes.
- `($x,$y) = getPt(index)`: Auslesen der Koordinaten eines Punktes.
- `length`: liefert die Anzahl der Stützpunkte.
- `offset(dx,dy)`: Verschiebt alle Punkte des Polygons um den angegeben Offset in X- und Y-Richtung (Translation).
- `scale(fx,fy)`: Skaliert alle Punkte des Polygons um den angegeben horizontalen oder vertikalen Faktor. Werte kleiner 1 führen zu einer Verkleinerung des Polygons, Werte größer 1 zu einer Vergößerung.
- `setPt(index,x,y)`: Ändern der Werte eines Punktes. Die Index-Zählung beginnt bei 0. Wird ein nicht belegter Punkt ausgewählt, erfolgt eine Warnung.
- `toPt(dx,dy)`: Angabe der Stützpunkte nicht absolut wie bei `addPt`, sondern relativ zum Anfangspunkt. Der erste Punkt wird immer mit`addPt` gesetzt, die folgenden Punkte könenn dann relativ dazu mit`toPt` gesetzt werden.
- `vertices`: Liefert eine Liste aller Stützpunkt als zweidimensionales Array.
- `transform(scaleX,rotX,scaleY,rotY,dX,dY)`: Koordinatentransformation aller Stützpunkte. `scaleX` und `scaleY` sind Skalierungsfaktoren wie bei `scale`, `rotX` und `rotY` bewirken eine Drehung (in Grad) und `dX` sowie `d>` bewirken eine Translation.

Beim Anlegen eines neuen Bildes ist man nicht auf ein leeres Bild beschränkt, es lassen sich vielmehr auch vorhandene Bilder (GD-, Gif, Jpeg-, Xbm-Format) laden. `newFromGif(FH)`: Erzeugt ein Image aus der GIF-Datei, die durch das Dateihandle FH angegeben wurde. Das Dateihandle muß vorher mit einer gültigen GIF-Datei oder Pipe geöffnet worden sein. Bei Erfolg gibt dieser Aufruf ein initialisiertes Image zurück. Schlägt der Aufruf fehl, wird undef zurückgegeben. Z.B.:

    open (GIF,"bildchen.gif") || die;
    $bild = newFromGif GD::Image(\*GIF) || die;
    close GIF;

Die Methoden `newFromXbm(FILEHANDLE)`, `newFromGd(FILEHANDLE)` und `newFromGd(FILEHANDLE)` funktionieren auf genau die gleiche Weise. GD ist Tom Boutells plattenbasiertes Speicherformat. Es ist für die seltenen Fälle gedacht, in denen Sie Images schnell von/auf Platte lesen/schreiben müssen. Es ist nicht zur regulären Nutzung gedacht, weil es im Gegensatz zu GIF oder JPEG keinerlei Komprimierung durchführt, d.h., die Dateien können sehr groß werden.

Man kann Linien und Formen (Shapes) mit einem sogenannten "Brush-Pattern" zeichnen. Brushes ("Pinsel") sind einfach Bilder, die auf die übliche Art und Weise erzeugt und bearbeitet werden. Wenn man mit ihnen zeichnet, wird ihr Inhalt für Farbe und Form der Linien verwendet. Um eine gemusterte Linie zu zeichnen, erzeugen oder laden Sie zuerst einen Brush und weisen ihn dann mit `setBrush()` einem Image zu. Das Zeichnen mit diesem Muster wird dann über die spezielle Farbe `gdBrushed` festgelegt. Es ist meist günstig, den Hintergrund des Brush transparent zu halten, damit die nicht-farbigen Teile andere Teile Ihres Bildes nicht abdecken.

    use GD;
    use strict;
    my $bild = new GD::Image(200,200);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $green = $bild->colorAllocate(0,255,0);
    my $yellow = $bild->colorAllocate(255,255,0);

    # Brush mit diagonalem Muster
    my $brush = new GD::Image(5,5);
    my $white = $brush->colorAllocate(255,255,255);
    my $black = $brush->colorAllocate(0,0,0);
    $brush->transparent($white);
    $brush->line(0,4,4,0,$black);

    $bild->setBrush($brush);
    $bild->arc(100,100,90,90,0,360,gdBrushed);

    binmode STDOUT;    # nur bei Windoof noetig
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Es lassen sich auch gefüllte Umrisse und Flood-Fills mit Hilfe eines Musters zeichnen. Das Muster ist, wie zuvor, einfach ein weiteres Bild. Das Muster wird wiederholt ausgegeben ("gekachelt"), um den benötigten Platz zu füllen. `setTile` wird aufgerufen, um das jeweilige Füllmuster zu definieren und das Muster wird über die Farbe `gdTiled` angesprochen.

    use GD;
    use strict;
    my $bild = new GD::Image(200,200);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $green = $bild->colorAllocate(0,255,0);
    my $yellow = $bild->colorAllocate(255,255,0);

    # Brush mit diagonalem Muster
    my $brush = new GD::Image(5,5);
    my $w = $brush->colorAllocate(255,255,255);
    my $b = $brush->colorAllocate(0,0,255);
    my $r = $brush->colorAllocate(255,0,0);
    $brush->transparent($w);
    $brush->line(0,4,4,0,$b);
    $brush->line(0,0,4,4,$r);

    $bild->setTile($brush);
    $bild->filledRectangle(20,20,180,180,gdTiled);

    binmode STDOUT;
    print "Content-type: image/gif\n\n";
    print $bild->gif;

Das Zeichen von Linien oder Füllen von Rechtecken macht ja noch keine sinnvolle Grafik. Versuchen wir mal den Verlauf einer Kurve zu zeichen, z.B. den Umsatz einer Firma über die Zeit. Es bietet sich an, so etwas mit der Polygonfunktion zu machen:

    use GD;
    use strict;
    my $bild = new GD::Image(300,200);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $black = $bild->colorAllocate(0,0,0);

    my $poly = new GD::Polygon;
    $poly ->addPt(21,170);
    $poly ->addPt(105,120);
    $poly->addPt(155,80);
    $poly->addPt(205,130);
    $poly->addPt(255,110);
    $poly->addPt(290,50);
    $bild->polygon($poly,$red);

    $bild->string(gdLargeFont,120,180,"Zeit",$blue);
    $bild->stringUp(gdLargeFont,2,120,"Umsatz",$blue);
    $bild->line(20,170,290,170,$black);
    $bild->line(20,170,20,20,$black);

    binmode STDOUT;
    print $bild->gif;

Das funktioniert so natürlich nicht! Polygone sind immer geschlossen, folglich wird das Polygon automatisch geschlossen, und das sieht nicht besonders toll aus. Der Trick ist, das Polygon um zwei Linien zu ergänzen. Die erste führt nach unten bis zu der Stelle, an der die horizontale Achse gezeichnet wird, die zweite zurück nach links. Diese beiden Polygonlinien ziehen wir nochmals mit der Hintergrundfarbe nach.

    use GD;
    use strict;
    my $bild = new GD::Image(300,200);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $black = $bild->colorAllocate(0,0,0);

    my $poly = new GD::Polygon;
    $poly ->addPt(21,170);
    $poly ->addPt(105,120);
    $poly->addPt(155,80);
    $poly->addPt(205,130);
    $poly->addPt(255,110);
    $poly->addPt(290,50);
    # die beiden zusaetlichen Punkte
    $poly->addPt(290,170);
    $poly->addPt(21,170);
    $bild->polygon($poly,$red);
    # abdecken
    $bild->line(290,50,290,170,$gray);
    $bild->line(290,170,21,170,$gray);

    # und die Legende
    $bild->string(gdLargeFont,120,180,"Zeit",$blue);
    $bild->stringUp(gdLargeFont,2,120,"Umsatz",$blue);

    $bild->line(20,170,290,170,$black);
    $bild->line(20,170,20,20,$black);

    binmode STDOUT;
    print $bild->gif;

Das linke Bild zeigt das Ergebnis des Programmlaufs, das recht eine Alternative mit gefülltem Polygon.

Man kann mit dem GD-Mudul auch einen Teil eines Bildes ausschneiden und in ein anderes Bild hineinkopieren. Dazu dient die Methode `copy`:

    copy(SrcImage, ZielX, ZielY, QuelleX, QuelleY, QBreite, QHoehe)

Die Funktion copy hat also sieben Parameter.

- Zuerst einen Zeiger auf das Quellbild,
- die nächsten beiden Parameter (ZielX, ZielY) definieren die Stelle im Zielbild, in die hineinkopiert werden soll (linke, obere Ecke des Zielbildes),
- die nächsten beiden Parameter (QuelleX, QuelleY) legen den linken oberen Eckpunkt des zu kopierenden Bereichs fest und
- die letzten beiden Parameter (QBreite, QHoehe) definieren die Ausdehnung des zu kopierenden Bereichs.

    use GD;
    use strict;
    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $black = $bild->colorAllocate(0,0,0);

    # Quellbild (blauer Hintergrund, lauter rote Punkte
    my $quelle = new GD::Image(100,100);
    my $b = $quelle->colorAllocate(0,0,255);
    my $r = $quelle->colorAllocate(255,0,0);
    for (my $i=0;$i<=100;$i=$i+2)
      {
      for (my $j=0;$j<=100;$j=$j+2)
        { $quelle->setPixel($i,$j,$r); }
      }

    # Qell-Ausschnitt (40x30 Pixel) an zwei verschiedene
    # Stellen kopieren
    $bild->copy($quelle,0,0,0,0,40,30);
    $bild->copy($quelle,50,50,0,0,40,30);

    binmode STDOUT;
    print $bild->gif;

Im folgenden Beispiel wird ein Ausschnitt aus dem Quellbild nicht einfach in das Zielbild eingefügt, sondern bei dieser Gelegenheit auch noch in seiner Größe verändert. Die Methode `copyResized` besitzt daher noch zwei zusätzliche Parameter für Breite und Hoehe beim Zielbild:

    copy(SrcImage, ZielX, ZielY, QuelleX, QuelleY, ZBreite, ZHoehe, QBreite, QHoehe)

Im folgenden Beispiel verdoppeln wir einmal die Größe des Quellbildes und einmal halbieren wir sie:

    use GD;
    use strict;
    my $bild = new GD::Image(100,100);
    my $gray = $bild->colorAllocate(200,200,200);
    my $blue = $bild->colorAllocate(0,0,255);
    my $red = $bild->colorAllocate(255,0,0);
    my $black = $bild->colorAllocate(0,0,0);

    # Quellbild (blauer Hintergrund, lauter rote Punkte
    my $quelle = new GD::Image(100,100);
    my $b = $quelle->colorAllocate(0,0,255);
    my $r = $quelle->colorAllocate(255,0,0);
    for (my $i=0;$i<=100;$i=$i+2)
      {
      for (my $j=0;$j<=100;$j=$j+2)
        { $quelle->setPixel($i,$j,$r); }
      }

    # Qell-Ausschnitt (40x30 Pixel) an zwei verschiedene
    # Stellen kopieren
    $bild->copyResized($quelle,0,0,0,0,80,60,40,30); # doppelt gross
    $bild->copyResized($quelle,50,50,0,0,20,15,40,30); # halbiert

    binmode STDOUT;
    print $bild->gif;

Wie man sieht, sind bei der Halbierung die roten Punkte verschwunden, weil jedes zweite Pixel ausgelassen wurde.

Das GD-Modul stellt die Grafikpromitive zur Verfügung, auf die sich zahlreiche andere Perl-Module stützen, die dann das Zeichnen von zwei- oder dreidimensionalen Charts, Barcodes, usw. ermöglichen. Dazu nur ein Beispiel:

Das folgende Programm ermöglicht es, schnell mal nebenbei Datenreihen zu visualisieren. So eröffnet Perl die Möglichkeit, ein Diagramm on-the-fly per CGI-Skript produzieren zu lassen. Basierend auf dem GD-Grafikpaket hat David Bonner ein Chart-Modul erstellt, das uns fast alle Arbeit abnimmt. Tatsächlich gibt es aber kein Chart-Modul. Die unterschiedlichen Chart-Typen (Points, Lines, Bars, LinesPoint, StackedBars, Pie etc.) sind selbst Klassen und erben eine Reihe von Methoden der Klasse `Chart::Base`. Ersetzen Sie einfach das Wort "Typ" durch den gewünschten Chart-Typ und fertig. Beispielsweise würde der Befehl `use Chart::Lines` das Lines-Modul aufrufen. Da die Methoden sich gleichen, kann man durch Wechsel des Moduls auch problemlos andere Grafiken erzeugen.

Mit der Kommandozeile `chart.pl < data` wird das Programm ausgeführt. Die Datei data hat folgenden Aufbau, wobei einzelne Items innerhalb einer Zeile durch Strichpunkte getrennt werden (Leerzeichen rechts und links vom Strichpunkt stören nicht):

1. Zeile: Dateiname (ohne Endung) der Ausgabedatei für die Grafik
2. Zeile: Überschrift der Chart, danach der Typ der Grafik ("L" für Linien, "B" für Balken oder "p" für Punkte), gegebenenfalls gefolgt von einem "g", falls Gitterlinien gewünscht werden. Werden diese Angaben weggelassen, erzeugt das Programm eine Liniengrafik ohne Gitterlinien.
3. Zeile: Überschrift der Y-Achse, bei Bedarf gefolgt von Minimum und Maximum der Y-Achsenskala (z. B. Bruttoumsatz;0;150)
4. Zeile: Überschrift der X-Achse
5. Zeile: X-Achsen-Tics, d. h. die Werteskala der X-Achse. Hier müssen genauso viele Werte angegeben werden, wie Zahlenwerte in den folgenden Zeilen (z. B. 2000; 2001; 2002; 2003; 2004)
6. und folgende Zeilen: Datenwerte für eine Kurve. der erste Wert dient der Legende, danach folgen die einzelnen Werte.

Eine Eingabedatei für das Chart-Programm könnte beispielsweise folgendermaßen aussehen:

    Umsatz
    Umsatzentwicklung;p;n
    Bruttoumsatz;0;150
    Jahr
    2000;2001;2002;2003;2004
    eins;10;20;30;40
    zwei;20;30;40;50
    drei;30;60;90;120
    vier;15;30;45;60

Solche Dateien lassen sich schnell von Hand erzeugen, aber auch recht leicht per Programm generieren, denn recht oft haben Programme, die mit Datenmanipulation zu tun haben, eine Export-Möglichkeit im CSV-Format.

    #!/usr/bin/perl -w
    use strict;
    use Chart::Lines;
    use Chart::Points;
    use Chart::Bars;

    my (@achse,@legende,@felder);
    my ($datei,$diagr,$titel,$typ,
        $xachse,$yachse,$grid,$maxy,$miny);

     # 1. Zeile: Name der Ausgabedatei
     chomp($datei = <>);

     # 2. Zeile: Titel;Typ [L,P,B];Gridlines
     chomp($titel = <>);
     ($titel,$typ,$grid) = split(/\s*;\s*/,$titel);
     $typ = uc($typ);
     $typ = 'L' unless(defined($typ));
     if ($typ eq 'P')
       { $diagr = Chart::Points -> new(600,350); }
     elsif ($typ eq 'B')
       { $diagr = Chart::Bars -> new(600,350); }
     else # 'L'
       { $diagr = Chart::Lines -> new(600,350); }

     # 3. Zeile: Y-Achsen-Legende, ggf. min. und max. Y-Wert
     chomp($yachse = <>);
     ($yachse,$miny,$maxy) = split(/\s*;\s*/,$yachse);

     # 4. Zeile: X-Achsen-Legende
     chomp($xachse = <>);

     # 5. Zeile: n X-Achsenticks
     chomp($_ = <>);
     @achse = split(/\s*;\s*/);
     shift(@achse); # 1. Feld = Dummy
     $diagr->add_dataset(@achse);

     # ab 6. Zeile: Datensaetze, jeweils n pro Zeile, erster
     # Datenwert = Bezeichnung
     while (<>)
       {
       chomp;
       @felder = split(/\s*;\s*/);
       push(@legende, shift(@felder)); # 1. Wert
       $diagr->add_dataset(@felder);
       }

     $diagr->set('title' => $titel);
     $diagr->set('x_label' => $xachse);
     $diagr->set('y_label' => $yachse);
     $diagr->set('transparent' => 'true');
     $diagr->set('legend_labels' => \@legende);
     $diagr->set('y_ticks' => '10');
     $diagr->set('grid_lines' => 'true') if (uc($grid) eq 'Y');
     $diagr->set('min_val' => $miny) if (defined($miny));
     $diagr->set('max_val' => $maxy) if (defined($maxy));
     $diagr->png($datei . '.png');

Die beiden folgenden Grafiken zeigen Beispielausgaben:

Zum vorhergehenden Abschnitt | Zum Inhaltsverzeichnis | Zum nächsten Abschnitt

###### Copyright © Hochschule München, FK 04, Prof. Jürgen Plate
Letzte Aktualisierung: