Ein Bericht soll als PDF verschickt werden, und darin fehlt eine Flächengrafik. Eine Diagrammbibliothek wäre für diese eine Abbildung zu schwer, und JavaScript hilft im PDF nicht weiter. Serverseitig gezeichnet ist die Grafik in wenigen Zeilen fertig.
Im Bild liegt der Nullpunkt oben links, die Eckpunkte sind durchnummeriert. Beide Details entscheiden darüber, ob die Form so aussieht wie geplant.
Was PHP imagepolygon() macht
PHP imagepolygon() zeichnet den Umriss eines Vielecks auf ein Bild. Die Eckpunkte kommen als flaches Array, in dem sich x- und y-Werte abwechseln. Mindestens drei Punkte sind nötig, nach oben gibt es keine Grenze.
<?php
$bild = imagecreatetruecolor(300, 200);
$hell = imagecolorallocate($bild, 255, 255, 255);
$dunkel = imagecolorallocate($bild, 43, 43, 43);
imagefilledrectangle($bild, 0, 0, 299, 199, $hell);
/* Dreieck: x1,y1, x2,y2, x3,y3 */
imagepolygon($bild, [150, 30, 270, 170, 30, 170], $dunkel);
header('Content-Type: image/png');
imagepng($bild);
Das Beispiel ist absichtlich vollständig. Ohne den Header liefert der Browser den Bildinhalt als Text aus, und eine einzige Leerzeile vor dem öffnenden PHP-Tag reicht, um die Ausgabe zu zerstören. Wer ein leeres Bild oder Zeichensalat sieht, prüft zuerst diese beiden Punkte.
Ein imagedestroy() am Ende fehlt hier bewusst: Seit PHP 8.0 ist das Bild ein Objekt und wird selbständig aufgeräumt. In einer Schleife über viele Bilder bleibt es trotzdem sinnvoll, die Variable früh freizugeben, damit der Speicher nicht bis zum Skriptende belegt bleibt.
Die Signatur im Original
Die Signatur hat sich mit PHP 8 geändert, und in älteren Beispielen steht deshalb ein Parameter, den es nicht mehr gibt:
imagepolygon(GdImage $image, array $points, int $color): bool
/* image - a GdImage object, the output target
points - an array of points: x0, y0, x1, y1 and so on. Each pair of
values describes one of the vertices of the polygon
color - a color identifier from imagecolorallocate()
return - true on success, false on failure
The function draws a polygon connecting all vertices in order and
closes the shape automatically. Before PHP 8.0 a num_points parameter
held the number of points; it became optional in 8.0 and was removed
in 8.1. See the manual for imageopenpolygon(), which leaves the
shape open. */
Wer älteren Code übernimmt, streicht also den Zählparameter. Bleibt er stehen, bricht der Aufruf unter PHP 8.1 ab.
Das Koordinatensystem von GD
Bevor PHP imagepolygon() sinnvolle Formen zeichnet, muss klar sein, wo der Nullpunkt liegt. Der Ursprung liegt oben links. Der x-Wert wächst nach rechts, der y-Wert nach unten. Wer aus der Mathematik kommt, erwartet die y-Achse andersherum und zeichnet seine erste Form spiegelverkehrt.
<?php
/* Punkt (0,0) ist die linke obere Ecke */
imagesetpixel($bild, 0, 0, $dunkel);
/* Punkt (299,199) ist die rechte untere Ecke eines 300x200-Bildes */
imagesetpixel($bild, 299, 199, $dunkel);
Daraus folgt eine Kleinigkeit mit großer Wirkung: Bei einem Diagramm mit einer Nulllinie unten wird der Messwert von der Bildhöhe abgezogen, nicht dazugezählt.
Die Punktliste ist flach
PHP imagepolygon() erwartet die Koordinaten nicht als Paare, sondern hintereinander in einer einzigen Liste. Wer seine Daten als Paare vorliegen hat, wandelt sie mit einer Zeile um.
<?php
$paare = [[150, 30], [270, 170], [30, 170]];
$punkte = array_merge(...$paare);
/* [150, 30, 270, 170, 30, 170] */
imagepolygon($bild, $punkte, $dunkel);
Die drei Punkte vor $paare packen das äußere Array aus, sodass array_merge() die einzelnen Paare als Argumente bekommt. Bei sehr vielen Punkten ist eine einfache Schleife genauso gut und besser lesbar.
Die Signatur hat sich mit PHP 8 geändert
Dieser Punkt kostet regelmäßig Zeit, weil ein Großteil der Beispiele im Netz die alte Form zeigt. Bis PHP 7 gab es einen Parameter, der die Anzahl der Punkte angab. Seit PHP 8.0 wird sie aus dem Array selbst bestimmt.
| PHP-Version | Aufruf | Status |
| bis 7.4 | imagepolygon($bild, $punkte, 3, $farbe) | einzige Form |
| ab 8.0 | imagepolygon($bild, $punkte, $farbe) | empfohlen |
| ab 8.1 | Aufruf mit Punktzahl | veraltet, meldet Deprecation |
Außerdem ist der erste Parameter seit PHP 8.0 ein GdImage-Objekt und keine Resource mehr. Prüfungen mit is_resource() schlagen seitdem fehl, obwohl das Bild einwandfrei erzeugt wurde.
Umriss und Füllung zusammen einsetzen
Für gefüllte Formen gibt es imagefilledpolygon(). In der Praxis werden beide Aufrufe kombiniert, denn die Füllung endet am Rand teilweise eine Pixelreihe früher als der Umriss. Bei Etiketten und Druckvorlagen fällt genau diese fehlende Reihe auf.
<?php
$punkte = [150, 30, 270, 170, 30, 170];
$fuellung = imagecolorallocate($bild, 238, 237, 237);
$rand = imagecolorallocate($bild, 43, 43, 43);
imagefilledpolygon($bild, $punkte, $fuellung);
imagepolygon($bild, $punkte, $rand);
Soll die Form randlos wirken, wird PHP imagepolygon() mit derselben Farbe wie die Füllung aufgerufen. Das Ergebnis ist eine saubere Kante ohne sichtbaren Rahmen.
Ein regelmäßiges n-Eck berechnen
Koordinaten von Hand einzutippen und an PHP imagepolygon() zu übergeben führt zu unregelmäßigen Formen. Für ein gleichmäßiges Vieleck genügt etwas Trigonometrie: Alle Ecken liegen auf einem Kreis um den Mittelpunkt.
<?php
function nEck(int $cx, int $cy, int $radius, int $ecken): array
{
$punkte = [];
$schritt = 2 * M_PI / $ecken;
for ($i = 0; $i < $ecken; $i++) {
$winkel = $i * $schritt - M_PI / 2; /* Spitze nach oben */
$punkte[] = (int) round($cx + $radius * cos($winkel));
$punkte[] = (int) round($cy + $radius * sin($winkel));
}
return $punkte;
}
imagepolygon($bild, nEck(150, 100, 70, 6), $dunkel); /* Sechseck */
imagepolygon($bild, nEck(150, 100, 40, 3), $dunkel); /* Dreieck */
Der Abzug eines Viertelkreises beim Winkel dreht die Form so, dass eine Spitze nach oben zeigt. Ohne diesen Versatz beginnt das Vieleck rechts, was bei Sechsecken und Dreiecken ungewohnt aussieht.
Der folgende Ablauf zeigt die Reihenfolge der Aufrufe von der leeren Bildfläche bis zur Ausgabe.
flowchart TD
A[imagecreatetruecolor] --> B[Farben anlegen]
B --> C[Punktliste bilden]
C --> D[imagefilledpolygon]
D --> E[imagepolygon fuer Rand]
E --> F{Ziel}
F -->|Browser| G[header und imagepng]
F -->|Datei| H[imagepng mit Pfad]
Die Farben müssen vor dem Zeichnen angelegt sein, weil die Zeichenfunktionen nur mit dem zurückgegebenen Farbindex arbeiten.
Eine Diagrammfläche aus Messwerten zeichnen
Das eigentliche Ziel aus der Einleitung ist eine Fläche unter einer Messreihe. Dazu werden die Werte auf die Bildhöhe umgerechnet und die Punktliste um den Rückweg zur Nulllinie ergänzt.
<?php
$werte = [12, 19, 15, 27, 24, 31, 28];
$breite = 420;
$hoehe = 200;
$rand = 20;
$bild = imagecreatetruecolor($breite, $hoehe);
imagefilledrectangle($bild, 0, 0, $breite - 1, $hoehe - 1,
imagecolorallocate($bild, 255, 255, 255));
$max = max($werte);
$schritt = ($breite - 2 * $rand) / (count($werte) - 1);
$punkte = [];
foreach ($werte as $i => $wert) {
$punkte[] = (int) round($rand + $i * $schritt);
$punkte[] = (int) round($hoehe - $rand - ($wert / $max) * ($hoehe - 2 * $rand));
}
/* Rueckweg entlang der Nulllinie schliesst die Flaeche */
$punkte[] = (int) ($breite - $rand);
$punkte[] = $hoehe - $rand;
$punkte[] = $rand;
$punkte[] = $hoehe - $rand;
imagefilledpolygon($bild, $punkte, imagecolorallocate($bild, 238, 237, 237));
imagepolygon($bild, $punkte, imagecolorallocate($bild, 43, 43, 43));
imagepng($bild, '/var/www/berichte/verlauf.png');
Im letzten Aufruf steht ein Pfad statt eines Headers, das Bild landet also in einer Datei. Genau diese Datei lässt sich anschließend in ein PDF einbetten oder per Mail verschicken. Die Maße eines vorhandenen Bildes ermittelt man übrigens mit den Funktionen aus dem Tutorial zu den Maßen eines Bildes.
Transparenz mit Alphakanal
Soll eine Fläche über einem Foto liegen und dieses durchscheinen lassen, wird die Farbe mit einem Alphawert angelegt. Der Wert reicht von 0 (deckend) bis 127 (unsichtbar).
<?php
$foto = imagecreatefromjpeg('/var/www/bilder/halle.jpg');
imagealphablending($foto, true); /* Farben mischen statt ersetzen */
$schleier = imagecolorallocatealpha($foto, 43, 43, 43, 80);
imagefilledpolygon($foto, [0, 300, 800, 300, 800, 400, 0, 400], $schleier);
imagesavealpha($foto, true);
imagepng($foto, '/var/www/bilder/halle-mit-band.png');
Ohne imagealphablending() würde die Farbe die darunterliegenden Pixel einfach ersetzen, der Alphawert bliebe wirkungslos. imagesavealpha() ist nur nötig, wenn die Transparenz in der Datei erhalten bleiben soll. Bilder aus einer Zeichenkette statt aus einer Datei behandelt das Tutorial zu PHP imagecreatefromstring().
Warum Antialiasing hier oft nicht greift
Schräge Kanten wirken bei GD ausgefranst, und imageantialias() scheint nichts zu bewirken. Das liegt nicht am Code: Die Funktion wirkt nur auf Linien und nur bei Truecolor-Bildern, nicht auf gefüllte Formen. Bei PHP imagepolygon() greift sie also für den Umriss, bei imagefilledpolygon() gar nicht.
<?php
/* Umweg fuer glatte Kanten: gross zeichnen, klein rechnen */
$gross = imagecreatetruecolor(1200, 800);
/* ... zeichnen wie gehabt, alle Werte mal 4 ... */
$klein = imagecreatetruecolor(300, 200);
imagecopyresampled($klein, $gross, 0, 0, 0, 0, 300, 200, 1200, 800);
imagepng($klein);
Dieser Umweg über ein vierfach großes Bild und imagecopyresampled() liefert deutlich sauberere Kanten als jede Antialiasing-Einstellung. Er kostet Rechenzeit und Speicher, was bei einer Grafik pro Bericht keine Rolle spielt. Zum Zuschneiden fertiger Bilder passt das Tutorial zu PHP imagecrop().
Fazit
PHP imagepolygon() ist eine kleine Funktion, die zusammen mit ein wenig Rechnerei erstaunlich weit trägt: vom Dreieck über das regelmäßige Sechseck bis zur Diagrammfläche aus echten Messwerten.
Vier Punkte entscheiden über den Erfolg. Die Punktliste ist flach, der Nullpunkt liegt oben links, seit PHP 8 entfällt der Parameter mit der Punktzahl, und Füllung und Umriss gehören zusammen. Wer diese vier kennt, braucht für einfache Grafiken keine Bibliothek.