XHTML SVG CSS PHP

Dr. O. Hoffmann

PHP-Labor

Ein Bild sagt mehr als tausend Wörter,
doch die richtigen Worte vermögen
mehr als tausend Bilder zu beschreiben.
Olaf Hoffmann

Das erste Bild mit PHP

Wenn bei PHP eine sogenannte GD-Bibliothek installiert ist, kann man damit auch ganz wunderbar Pixelgraphikbilder erstellen. Vektorgraphik im Format SVG kann ohne besondere Voraussetzungen erzeugt werden.

Zu beachten ist, daß man nur ein paar Sekunden Rechenzeit hat und nur begrenzten Arbeitsspeicher (einige Megabyte).
Aufwendige Rechnungen mit Zwischenspeicherung in arrays und sehr große Bildformate sind damit also nicht drin, aber mit den schnellen Rechnern von heute wundert man sich, daß selbst dreidimensionale Bilder damit noch recht schnell erstellt werden können.
Abwägen kann man, ob statt der Pixelgraphik nicht besser Vektorgraphik (SVG) verwendet werden sollte. SVG benötigt keine besondere Bibliothek und die konkrete Umsetzung der Graphik erfolgt durch das Darstellungsprogramm, spart also meist Arbeitsspeicher und Rechenleistung beim server, zudem hängt die Dateigröße nicht von Höhe und Breite des Bildes ab, sondern vor allem von der Anzahl (unterschiedlicher) Elemente. Pixelgraphik ist also vorteilhaft, wenn viele einzelne Pixel gesetzt werden oder allgemein sehr viele Elemente in einem relativ kleinen Bild dargestellt werden sollen (etwa bei bestimmten Fraktaltypen). Vektorgraphik ist sehr vorteilhaft bei klar strukturierten, wiederholten Inhalten, kann aber auch selbst Dateien referenzieren, die Pixelgraphik enthalten. Wenn also wiederum wenige Elemente zu einer Pixelgraphik hinzugefügt werden sollen, kann es wiederum vorteilhaft sein, dafür Vektorgraphik zu verwenden. Auf SVG wird in anderen Bereichen dieses Projektes näher eingegangen, daher wird es hier im ersten Beispiel nur um Pixelgraphik gehen.

Damit das Darstellungsprogramm weiß, welches Format dargestellt werden soll, wird dies bei einer mittels PHP erzeugten Graphik mit der Funktion header mitgeteilt, bevor eine Ausgabe von Inhalt erfolgt, gesendet wird der Inhaltstyp (englisch: content-type), ehemals auch MIME-Typ. Den header sende ich uebrigens extra so spät, damit man gegebenenfalls den Fehlerbericht sehen kann. Damit das Bild funktioniert, darf vor dem header allerdings keine Ausgabe erfolgen.

Pixelgraphik

Bei der Erzeugung von Pixelgraphik wird das Bild erst in einem String abgelegt/erzeugt und dann mit einer speziellen Funktion als Bildtyp JPEG oder PNG direkt nach dem dafür korrekten header rausgelassen.

Bei diesem ersten Bild lernen wir neben der prinzipiellen Bilderstellung vor allem, wie man die Kernelemente Punkt, Linie, Rechteck, Text malt.
Die Bildgröße kann übrigens durch Übergabe der GET-Parameter x und y festgelegt werden.

Hier ist das Beispiel (anklickern für ein weiteres):

Beispielbild mit PHP

Schauen wir uns einfach einmal an, wie es geht:

<?php
# php-Labor: erstes Bild 
# Fehlerbericht anschalten:
error_reporting(E_ALL);

   # gd-version ermitteln fuer jegliche php-version
   # ab php 4.3 geht es einfacher mit gd_info
   # dazu: puffer einschalten
   ob_start();
   # teil der phpinfo laden
   phpinfo(8);
   $info=ob_get_contents();
   # puffer loeschen und beenden
   ob_end_clean();
   # reststring hinter gd version finden
   $info=stristr($info, 'gd version');
   # suche nach dezimalzahl
   preg_match('/\d/', $info, $gd);
   # da steht sie jetzt, die gdversion:
   $gdversion=$gd[0];
   # info und gd loeschen
   unset ($info);
   unset ($gd);
    
# Zufallsgerator initialisieren:
# (ab PHP 4.2 kann man darauf verzichten)
mt_srand ((double)microtime()*1000000);
# Bildgroesse einlesen oder selbst festlegen:
$horizontal=400;
if(isset($_GET['x'])) {
  $h=$_GET['x'];
  # Groesse begrenzen:
  if (($h >= 100) AND ($h <=1000)) {
    $horizontal=$h;
  } 
}
$vertikal   = 300; 
if(isset($_GET['y'])) {
  $v=$_GET['y'];
  # Groesse begrenzen:
  if (($v >= 100) AND ($v <=1000)) {
    $vertikal=$v;
  } 
}

# Bild erzeugen: 
$im = imagecreate($horizontal, $vertikal);
# schon mal schwarz und weiss festlegen 
$white = imagecolorallocate($im,255,255,255); 
$black = imagecolorallocate($im,0,0,0); 
# jetzt zum Beispiel noch rot, gruen, blau:
$f[1] = imagecolorallocate($im,255,0,0); 
$f[2] = imagecolorallocate($im,0,255,0); 
$f[3] = imagecolorallocate($im,0,0,255); 

# Bild erst mal weiss fuellen:
imagefill ($im, 1, 1, $white);

# Punkte zufaellig malen:

for ($i = 0; $i <= 100; $i++) {
  $x1 = mt_rand(3,$horizontal-3);
  $y1 = mt_rand(3,$vertikal-3);
  $x2 = mt_rand(3,$horizontal-3);
  $y2 = mt_rand(3,$vertikal-3);
  $c  = $f[mt_rand(1,3)];
  imagesetpixel ($im, $x1, $y1, $c);
  imagesetpixel ($im, $x2, $y2, $c);
}

# Linien zufaellig malen:
# Liniendicke kann man auch aendern, wenn
# die PHP-Version und die GD-Version
# hinreichend frisch ist, sonst das zu Fuss erledigen
# (sieht so gerechnet sogar besser aus, als die GD
# es macht...)
# PHP ab 4.0.6., GD ab 2.0.1
for ($i = 0; $i <= 20; $i++) {
  $x1 = mt_rand(3,$horizontal-3);
  $y1 = mt_rand(3,$vertikal-3);
  $x2 = mt_rand(3,$horizontal-3);
  $y2 = mt_rand(3,$vertikal-3);
  $c  = $f[mt_rand(1,3)];
  $dick = mt_rand(1,10);
  
  if ($gdversion >=2) {
    imagesetthickness ($im, $dick);
    imageline ($im, $x1, $y1, $x2, $y2, $c);
  } else {
    $eex=$x1-$x2;
    $eey=$y1-$y2;
    $betrag=max(sqrt($eex*$eex+$eey*$eey),1.0);
    $ex=$eex/$betrag;
    $ey=-$eey/$betrag;
    $mess_p[0]=$x1+$dick/2.0*$ey;
    $mess_p[1]=$y1+$dick/2.0*$ex;
    $mess_p[2]=$x1-$dick/2.0*$ey;
    $mess_p[3]=$y1-$dick/2.0*$ex;    
    $mess_p[6]=$x2+$dick/2.0*$ey;
    $mess_p[7]=$y2+$dick/2.0*$ex;
    $mess_p[4]=$x2-$dick/2.0*$ey;
    $mess_p[5]=$y2-$dick/2.0*$ex;            
    
    imagefilledpolygon($im, $mess_p, 4 , $c);
   }
}

# Rechtecke zufaellig malen:
# bei der nicht GD 2 Version muss man
# bei nicht zufaelligen Bildern genau
# nachrechnen/denken, wie die Dicke
# konstruiert werden soll...

for ($i = 0; $i <= 5; $i++) {
  $x1 = mt_rand(3,$horizontal-3);
  $y1 = mt_rand(3,$vertikal-3);
  $x2 = mt_rand(3,$horizontal-3);
  $y2 = mt_rand(3,$vertikal-3);
  $c  = $f[mt_rand(1,3)];
  $dick = mt_rand(1,5);
  
 if ($gdversion >=2) {
    imagesetthickness ($im, $dick);
    imagerectangle ($im, $x1, $y1, $x2, $y2, $c);
  } else {
    for ($j = 0; $j <= $dick; $j++) {
      imagerectangle ($im, $x1-$j, $y1-$j, $x2+$j, $y2+$j, $c);
    } 
  }
}

# Text ins Bild schreiben:

imagestring($im, 4, 2, $vertikal-20, 'O', $black);
imagestring($im, 4, 5, $vertikal-15, 'H', $black);
imagestring($im, 1, 15, $vertikal-10, '2004', $black);

# header senden
Header("Content-type: image/png");

# Bild raushauen 
ImagePNG($im);

# es ginge auch alternativ:
# Header("Content-type: image/jpeg");
# ImageJPEG($im);

# vorsichtshalber Speicher leeren und Bearbeitung beenden:
imagedestroy ($im);
exit;
?>

Teiltransparentes Hintergrundbild

Obgleich das für ältere Prozessoren eine Herausforderung werden kann und alte Darstellungsprogramme wie netscape4 oder der microsoft internet explorer 6 teiltransparente Bilder nicht darstellen können, ist es oft gut, für ein CSS-Design oder -Layout, teiltransparente einfarbige Hintergrundbilder parat zu haben. Mittels PHP ist die Erzeugung solcher Bilder recht einfach, ab einer Version 4.3.0 und einer GD-Bibliothek ab Version 2.0.1.
Beispiel: Teiltransparentes PNG-Bild mit PHP
Als GET-Parameter können angegeben werden:
x,y: Breite, Höhe zwischen 10 und 1000
opacity: Teiltransparenz zwischen 0 und 127
rot,gruen,blau: Farbenangaben zwischen 0 und 255
Statt die Bilder bei jedem Nutzeraufruf neu zu erzeugen, spart es meist Ressourcen, wenn Bilder einmal erzeugt werden und dann abgespeichert, statt die jedes mal neu zu erzeugen, es sei denn natürlich, Farbe oder Transparenz sollen sich bei jedem Aufruf ändern können.

<?php
# php-Labor: teiltransparente Bilder erzeugen
# Fehlerbericht anschalten:
error_reporting(E_ALL);
  
# Bildgroesse einlesen oder selbst festlegen:
$horizontal=100;
if(isset($_GET['x'])) {
  $h=$_GET['x'];
  # Groesse begrenzen:
  if (($h >= 10) AND ($h <=1000)) {
    $horizontal=$h;
  } 
}
$vertikal   = 100; 
if(isset($_GET['y'])) {
  $v=$_GET['y'];
  # Groesse begrenzen:
  if (($v >= 10) AND ($v <=1000)) {
    $vertikal=$v;
  } 
}

# Farben einlesen oder selbst festlegen:
$rot=0 ; 
if(isset($_GET['rot'])) {
  $farbe=$_GET['rot'];
  if ($farbe > 255) {
    $farbe=255;
  }  else if ($farbe < 0) {
    $farbe=0;
  } 
  $rot=$farbe;  
}

$gruen=0 ; 
if(isset($_GET['gruen'])) {
  $farbe=$_GET['gruen'];
  if ($farbe > 255) {
    $farbe=255;
  }  else if ($farbe < 0) {
    $farbe=0;
  } 
  $gruen=$farbe;  
}

$blau=0 ; 
if(isset($_GET['blau'])) {
  $farbe=$_GET['blau'];
  if ($farbe > 255) {
    $farbe=255;
  }  else if ($farbe < 0) {
    $farbe=0;
  } 
  $blau=$farbe;  
}

# Teiltransparenz einlesen oder selbst festlegen:
$opacity   ="63" ; 
if(isset($_GET['opacity'])) {
  $opacity=$_GET['opacity'];
   if ($opacity > 127) {
    $opacity=127;
  }  else if ($opacity < 0) {
    $opacity=0;
  } 
}

# Bild erzeugen: 
$im = imagecreate($horizontal, $vertikal);
# Farbe festlegen 
$farbe = imagecolorallocatealpha($im,$rot,$gruen,$blau,$opacity); 
# Bild fuellen:
imagefill ($im, 1, 1, $farbe);


# header senden
Header("Content-type: image/png");
# Bild raushauen 
ImagePNG($im);
# vorsichtshalber Speicher leeren und Bearbeitung beenden:
imagedestroy ($im);
exit;
?>

Graphik direkt in XHTML-Datei einbetten

Meist wird man eines der Elemente image oder object nehmen und dort per Attribut src oder data eine externe Datei referenzieren, wenn man eine Graphik in eine XHTML-Datei integrieren will. Es ist aber auch möglich, über eine spezielle URI-Angabe die Bilddaten direkt in die XHTML-Datei zu integrieren, was praktisch nur sinnvoll bei kleineren Bildern ist, dazu sollte Pixelgraphik vor allem mit base64 kodiert sein, weil es sonst kaum wieder dekodiert werden kann. Diese Kodierung kann etwa mit PHP vorgenommen werden (wenn das Bild öfter so verwendet wird, sollte das Ergebnis natürlich statisch abgespeichert werden und nicht immer wieder neu berechnet werden. Eingebunden wird das dann mit einem PNG als Beispiel etwa so:


<img src="data:image/png;base64,...Bilddaten..." alt="Bsp." /> 

Wenn base64 nicht vorliegt, wird ASCII-Kodierung verwendet oder die Kodierung ist zuvor anzugeben, etwa so:


<object 
data="data:image/svg+xml;charset=iso-8859-1,...Bilddaten..." 
type="image/svg+xml" width="200" height="200"> 
<p>Alternativtext oder -bild</p> 
</object> 

Beispiel: in XHTML eingebettetes SVG.
Statt ...Bilddaten... kommt dann eben das kodierte Bild. Da wird dann nicht einmal PHP gebraucht, kann aber natürlich verwendet werden, um den Bildinhalt dynamisch zu erzeugen. Weil das SVG innerhalb von data auftaucht, sind Sonderzeichen natürlich wie in href zu maskieren.
Um Chaos zu vermeiden, muß man bei Klartextformaten wie SVG auf die Anführungszeichen aufpassen, um zum Ziel zu kommen, im Beispiel werden für den Wert des data-Attributes einfache Anführungszeichen verwendet, innerhalb der SVG-Daten nur große. All das kann durch geeignete Konvertierung umgangen werden.
Beispiel: in XHTML eingebettetes SVG mit base64.

<?php
# php-Labor: Bild einbetten 
# Fehlerbericht anschalten:
error_reporting(E_ALL);


# Bild in Speicher schreiben
ob_start();
echo '<?xml version="1.0" encoding="iso-8859-1"?>'."  \n";
?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
  "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" 
         xmlns:xlink="http://www.w3.org/1999/xlink" 
         width="100%" height="100%" viewBox="0 0 300 300">
<rect x="100" y="100" width="100" height="100" rx="10" ry="10" 
     fill="#aaf" stroke="#008" stroke-width="4" 
     fill-opacity="0.5" stroke-opacity="0.7" />
<rect x="20" y="20" width="100" height="100" rx="10" ry="10" 
     fill="#aaf" stroke="#008" stroke-width="4" 
     fill-opacity="0.5" stroke-opacity="0.7" />
<rect x="180" y="180" width="100" height="100" rx="10" ry="10" 
     fill="#aaf" stroke="#008" stroke-width="4" 
     fill-opacity="0.5" stroke-opacity="0.7" />
<rect x="20" y="180" width="100" height="100" rx="10" ry="10" 
     fill="#aaf" stroke="#008" stroke-width="4" 
     fill-opacity="0.5" stroke-opacity="0.7" />
<rect x="180" y="20" width="100" height="100" rx="10" ry="10" 
     fill="#aaf" stroke="#008" stroke-width="4" 
     fill-opacity="0.5" stroke-opacity="0.7" />    
</svg>
<?php
$im=ob_get_contents(); 
# Speicher leeren:
ob_clean();
# Bild base64-kodieren:
$im2=base64_encode ($im);
$im="";
$content="Content-type: application/xhtml+xml";
 header($content);
echo '<?xml version="1.0" encoding="iso-8859-1"?>'."  \n";
?>
<!DOCTYPE
 html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
<head>
<title>Eingebettetes SVG in XHTML</title>
</head>
<body> 

<h1>Eingebettetes SVG in XHTML</h1>

<div>
<object width="300" height="300" type="image/svg+xml" 
    data="data:image/svg+xml;charset=iso-8859-1;base64, 
    <?php echo $im2 ?>">
    Bei realem Beispiel Alternative nicht vergessen!
</object>
</div>

</body>
</html>

Bei XHTML kann nun auch SVG direkt eingebettet werden, ein Beispiel dafür ist etwa folgendes Dokument: Polar (deutsch), Polar (englisch).

Beispiel für eingebettete Pixelgraphik: in XHTML eingebettetes PNG mit base64. Prinzipiell geht das auch bei HTML, da habe ich mir das Beispiel gespart.
Das funktioniert so bei technisch aktuellen browsern, das Schema ist in RFC 2397 spezifiziert, getestet mit Konqueror (3.3), Opera 8 und Gecko/Mozilla 1.7.8. Angeblich soll der MSIE6 zu alt sein, um das zu verstehen, allerdings ist die RFC 2397 vom August 1998 und der MSIE6 ist erst später aufgetaucht, aber vermutlich wurde bei microsoft einfach vergessen, solche URIs zu interpretieren, getestet habe ich es selber nicht. Das PHP sieht dann wie folgt aus:

<?php
# php-Labor: Bild einbetten 
# Fehlerbericht anschalten:
error_reporting(E_ALL);

$im=file_get_contents ('./trans.png');
$im2=base64_encode ($im);

$content="Content-type: application/xhtml+xml";
header($content);
echo "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>  \n";
?>
<!DOCTYPE
 html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
<head>
<title>Beispiel data:image</title>
</head>
<body>
<h1>Pixelgraphik in XHTML-Datei integriert.</h1>
<p>
<?php
echo '<img src="data:image/png;base64,'.$im2.'" alt="Bsp." />';
?>
</p>
</body>
</html>

Dynamisch erstellte Pixelgraphik direkt in XHTML-Datei einbetten

Soll das Bild im gleichen Skript erst erzeugt werden, wie obige Bilder, so ergibt sich die Schwierigkeit, daß eigentlich nur eine Ausgabe direkt an das Darstellungsprogramm oder das Abspeichern in eine Datei vorgesehen ist. Wie ich nunmehr gelernt habe, kann man die Ausgabe an das Darstsellungsprogramm aber auch in eine Variable umbiegen und diese dann weiterverarbeiten, dafür stellt PHP einige Funktionen zur Verfügung, die mit der Zeichenfolge ob_ beginnen. Mozilla, Apple und Opera möchten ja eine Methode namens 'canvas' etablieren, mit der man per java-script Bilder malen kann, dies ist da das server-seitige Pendant, welches dann aber auch im Gegensatz zu 'canvas' wirklich funktioniert, auch ohne proprietären Unfug und ohne daß der Nutzer java-script aktiviert haben muß.
Beispiel: in XHTML eingebettete zufällig erzeugte PNGs mit base64.
Das PHP sieht dann wie folgt aus:

<?php
# php-Labor: Bild einbetten 
# Fehlerbericht anschalten:
error_reporting(E_ALL);
# Zufallsgerator initialisieren:
# (ab PHP 4.2 kann man darauf verzichten)
mt_srand ((double)microtime()*1000000);
  
# Bildgroesse einlesen oder selbst festlegen:
$horizontal=300;
if(isset($_GET['x'])) {
  $h=$_GET['x'];
  # Groesse begrenzen:
  if (($h >= 10) AND ($h <=1000)) {
    $horizontal=$h;
  } 
}
$vertikal   = 200; 
if(isset($_GET['y'])) {
  $v=$_GET['y'];
  # Groesse begrenzen:
  if (($v >= 10) AND ($v <=1000)) {
    $vertikal=$v;
  } 
}




# Bild erzeugen: 
$im = imagecreate($horizontal, $vertikal);
# Farbe festlegen 
$rot=mt_rand(0,255);
$gruen=mt_rand(0,255); 
$blau=mt_rand(0,255); 
$opacity=mt_rand(10,110); 
$farbe = imagecolorallocatealpha($im,$rot,$gruen,$blau,$opacity); 
# Bild fuellen:
imagefill ($im, 1, 1, $farbe);
# paar Rechtecke malen:
for ($i = 0; $i <= 5; $i++) {
  $x1 = mt_rand(3,$horizontal-3);
  $y1 = mt_rand(3,$vertikal-3);
  $x2 = mt_rand(3,$horizontal-3);
  $y2 = mt_rand(3,$vertikal-3);
$rot=mt_rand(0,255);
$gruen=mt_rand(0,255); 
$blau=mt_rand(0,255); 
$opacity=mt_rand(10,110); 
  $c  = imagecolorallocatealpha($im,$rot,$gruen,$blau,$opacity); 
  $dick = mt_rand(1,5);
    imagesetthickness ($im, $dick);
    imagerectangle ($im, $x1, $y1, $x2, $y2, $c);
}
# Bild in Speicher schreiben
ob_start();
ImagePNG($im);
# Speicher leeren:
imagedestroy ($im);
$im=ob_get_contents(); 
# Speicher leeren:
ob_clean();
# Bild base64-kodieren:
$im1=base64_encode ($im);

# Bild 2 erzeugen: 
$im = imagecreate($horizontal, $vertikal);
# Farbe festlegen 
$rot=mt_rand(0,255);
$gruen=mt_rand(0,255); 
$blau=mt_rand(0,255); 
$opacity=mt_rand(50,110); 
$farbe = imagecolorallocatealpha($im,$rot,$gruen,$blau,$opacity); 
# Bild fuellen:
imagefill ($im, 1, 1, $farbe);
# paar Rechtecke malen:
for ($i = 0; $i <= 5; $i++) {
  $x1 = mt_rand(3,$horizontal-3);
  $y1 = mt_rand(3,$vertikal-3);
  $x2 = mt_rand(3,$horizontal-3);
  $y2 = mt_rand(3,$vertikal-3);
$rot=mt_rand(0,255);
$gruen=mt_rand(0,255); 
$blau=mt_rand(0,255); 
$opacity=mt_rand(10,110); 
$c  = imagecolorallocatealpha($im,$rot,$gruen,$blau,$opacity); 
$dick = mt_rand(1,5);
    imagesetthickness ($im, $dick);
    imagerectangle ($im, $x1, $y1, $x2, $y2, $c);
}
# Bild in Speicher schreiben
ob_start();
ImagePNG($im);
# Speicher leeren:
imagedestroy ($im);
$im=ob_get_contents(); 
# Speicher leeren:
ob_clean();
# Bild base64-kodieren:
$im2=base64_encode ($im);

$content="Content-type: application/xhtml+xml";
header($content);
echo '<?xml version="1.0" encoding="iso-8859-1"?>'."  \n";
?>
<!DOCTYPE
 html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
 "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de">
<head>
<title>Beispiel data:image</title>
</head>
<body>
<h1>Zuf&auml;llig erzeugte Pixelgraphiken in xhtml-Datei integriert.</h1>
<p>
<?php
echo '<img style="position: fixed; top: 5em; left: 5em" src="data:image/png;base64,'.$im1.'" alt="Bsp. 1" />'."\n";
echo '<img style="position: fixed; top: 9em; left: 10em" src="data:image/png;base64,'.$im2.'" alt="Bsp. 2" />';
?>
</p>
</body>
</html>