XHTML SVG CSS PHP

Dr. O. Hoffmann

PHP-Labor

Wenn ich alles Große genau betrachte, so sehe ich, daß es aus lauter Kleinigkeiten zusammengesetzt ist, und wenn ich ganz genau hinsehe, erkenne ich, daß es so etwas wie eine Kleinigkeit gar nicht gibt.
Michelangelo

Graphik skalieren und kopieren

Ein Bild skalieren

Es kommt schon einmal vor, daß man die Größe eines Bildes ändern möchte.
Besonders einfach ist das bei SVG-Bildern, denn die sind von sich aus skalierbar, dort muß nur die Bildgröße an sich und als Zahl geändert werden. Vorteil des SVG-Formates ist, daß es bei beliebiger Vergrößerung nicht zu Qualitätsverlusten kommt. Mittels des Attributes preserveAspectRatio des Elementes svg kann einfach entschieden werden, ob das Verhältnis von Höhe zu Breite Vorrang vor den Angaben zu Höhe zu Breite einzeln haben soll. Wenn das SVG-Bild sich von der Größe her an den umgebenden Container anpassen soll, kann auch einfach Höhe und Breite auf hundert Prozent gesetzt werden. Ähnlich wie unten bei der Pixelgraphik könnte man das aber auch noch mit PHP verfeinern, aber eben auch mit SVG selbst und mit SVG auch für darin eingebundene Pixelgraphik, was eine dynamische Vorverarbeitung mit PHP oder anderen Skriptsprachen überflüssig macht. Hier nun ein Beispiel mit Linienscharen (diese können sich von Aufruf zu Aufruf bei diesem Bildtyp ändern, was nichts mit der Skalierung zu tun hat. Entscheidend sind beim SVG-Bild die Attribute width und height des Elementes svg relativ zum Attribut viewBox, vergleiche die Quelltexte der Beispiele):

Kleines SVG-Bild
Der gleiche SVG-Bildtyp, nur größer skaliert
Der gleiche SVG-Bildtyp, nur auf hundert Prozent skaliert
Letzteres paßt sich sogar dynamisch an, wenn sich die Größe des Darstellungsbereiches ändert.
Weil bei SVG ganz verschiedene Maßeinheiten angegeben werden können, etwa cm, mm, in, px, %, em, ex, pt, pc, kann die Bildgröße damit bereits sehr flexibel an ein gegebenes Problem angepaßt werden, ohne den eigentlichen Inhalt des Bildes überhaupt zu ändern.

Zu SVG siehe auch den Menüpunkt Vektorgraphik. Es ist zur Anzeige allerdings erforderlich, als Nutzer ein technisch aktuelles Darstellungsprogramm zu verwenden, welches SVG direkt darstellen kann (Opera 8 oder 9, Geckos ab Version1.8, etwa ab SeaMonkey1.0, Firefox1.5, Amaya 9, Konqueror 3.3+KSVG etc), oder bei älteren Darstellungsprogrammen ist ein plugin erforderlich.
Durch die zunehmende Verbreitung dieser SVG-fähigen Programme seit etwa 2005 rückt SVG für solche Anwendungen als simples und flexibles Werkzeug auch zur Transformation von Pixelgraphik immer mehr in den Vordergrund.

Eine Änderung der Bildgröße ist zwar in der Regel nicht bei jedem Aufruf eines Pixelbildes (JPEG, PNG) sinnvoll, kann aber unter besonderen Umständen notwendig werden, wo dann die klassische Methode darin liegt, wenn es schon nicht vorher mit einem geeigneten Graphikprogramm passiert, dieses mit PHP zu machen.
Das ist etwa der Fall, wenn ein Bild vom Nutzer heraufgeladen wurde, von dem automatisch ein kleines Bild als Vorschau erzeugt werden soll (für Vorschaubilder ist eine Verkleinerung mit SVG nicht sinnvoll, weil es sich dann nach wie vor um das Originalbild handelt, was der Nutzer laden muß). Zu dem Zwecke täte man das kleine Bild zwar unter einem eindeutigen Dateinamen abspeichern, statt es wie hier darzustellen, aber auch das ist durch Angabe eines entsprechenden Parameters bei der Bildausgabe einfach möglich. Immer neu berechnet sollte das skalierte Bild nicht werden, weil dafür viel Rechenleistung und Speicher genutzt wird. Es sei denn, die Rechnung ist mit einer einmaligen Manipulation des Bildes verbunden, etwa ist immer eine andere Größe erforderlich oder es findet eine andere Veränderung am Bild statt, die wir hier nicht weiter erörtern wollen.
Bei einer Pixelgraphik kommt es bei einer Vergrößerung nahezu zwangsläufig zu einer Verschlechterung der Bildqualität. Bei einer Verkleinerung kommt es auf das Verfahren an - was PHP betrifft, so kommt es darauf an, die zueinander passenden Funktionen auszuwählen, sonst kann es besonders bei bunten Bildern zu eigenartigen Farbveränderungen kommen...

Beispielbilder anklickern zum Skalieren:

Bild skalieren Bild skalieren
Bild skalieren Bild skalieren

Schauen wir uns einfach einmal an, wie es geht:

<?php
# php-Labor: Bild skalieren 
# 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);
    
# Bildgroesse einlesen oder selbst festlegen:
$horizontal=400;
if(isset($_GET['x'])) {
  $h=$_GET['x'];
  # Groesse begrenzen:
  if (($h >= 20) AND ($h <=1000)) {
    $horizontal=$h;
  } 
}
$vertikal   = 300; 
if(isset($_GET['y'])) {
  $v=$_GET['y'];
  # Groesse begrenzen:
  if (($v >= 20) AND ($v <=1000)) {
    $vertikal=$v;
  } 
}
# Der Modus legt fest, ob das Bild verzerrt
# werden darf oder nicht. 1 ist unverzerrt.
# Da gibt es dann unter Umstaenden einen
# nicht besonders huebschen Rand, wenn das
# Verhaeltnis von Hoehe zu Breite nicht passt.
# Mit Modus 2 wird weder verzerrt noch
# bleibt ein Rand, dafuer wird die vorgegebene
# Bildgroesse ueberschrieben.

$mod = 1; 
if(isset($_GET['modus'])) {
  $modus=$_GET['modus'];
  # eingabe ueberpruefen:
  if (($modus == 0) OR ($modus ==1) OR ($modus ==2)) {
    $mod=$modus;
  } 
}

# Fuer Modus=2 den Anteil festlegen.
# Das ist demzufolge der Modus des
# stufenlosen unverzerrten Vergroesserns
$anteil = 1.0; 
if(isset($_GET['ant'])) {
  $ant=$_GET['ant'];
  # eingabe ueberpruefen:
  if (($ant >= 0.1) AND ($ant <=10.0)) {
    $anteil=$ant;
  } 
}

# Wir laden ein Bild von der Festplatte.
# Im array imininfo stehen interessante Informationen
# ueber das Bild. Im Element 3 steht auch noch
# drin, um welchen Bildtyp es sich handelt.
$ms="olaf-toif.jpg";
$imininfo = GetImageSize ($ms);
$x2=$imininfo[0];
$y2=$imininfo[1];

$imin = ImageCreateFromJPEG ($ms);



if ($mod==2) {
    $horizontal=round($x2*$anteil);
    $vertikal=round($y2*$anteil);
    $mod=0;
}


# Bild wenn moeglich im Truecolorformat erzeugen.
# Das muss man bei neuerem php/gd nehmen,
# um brauchbar Bilder zu skalieren,
# wie auch einige hier verwendete
# Funktionen. Mit verwandten Funktionen
# funktioniert es aber aehnlich bei
# aelterem php/gd, siehe unten 

if ($gdversion >=2) {
$im = imagecreatetruecolor($horizontal, $vertikal); 
} else {
$im = imagecreate($horizontal, $vertikal); 
}


# Werte fuer den simplen Fall vorgeben:
$x0=0;
$y0=0;
$xw=$horizontal;
$yw=$vertikal;


# und jetzt gucken wir, ob und wie wir 
# groesser oder kleiner machen wollen
# wenn der komplizierte Fall ohne 
# Verzerrung und mit Rand vorliegt.

if ($mod==1){

  $skalh=$horizontal/$x2;
  $skalv=$vertikal/$y2;

  if ($skalh < $skalv) {

    $y0=round(($vertikal-$y2*$skalh)/2);
    $x0=0;
    $xw=$horizontal;
    $yw=round($y2*$skalh);

  } elseif ($skalh > $skalv) {

    $x0=round(($horizontal-$x2*$skalv)/2);
    $y0=0;
    $xw=round($x2*$skalv);
    $yw=$vertikal;

  }

  # wir versuchen fuer den in diesem Fall vorhandenen
  # Rand eine eingermassen brauchbare Farbe zu ermitteln,
  # was man natuerlich auch schlauer versuchen kann:

  $ci = imagecolorat($imin, 1, $y2-1); 
  $ct = imagecolorsforindex($imin, $ci); 
  $cs = ImageColorAllocate($im,$ct["red"],$ct["green"],$ct["blue"]); 

  imagefill ($im, 1, 1, $cs);


}

# Bild skalieren:

if ($gdversion >=2) {
imagecopyresampled ($im, $imin, $x0, $y0, 0, 0, $xw, $yw, $x2, $y2);
} else {
imagecopyresized ($im, $imin, $x0, $y0, 0, 0, $xw, $yw, $x2, $y2);
}

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

# Bild raushauen 
ImagePNG($im);

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

# oder abspeichern als png:
# $file="bildskaliert.png";
# ImagePNG($im, $file);


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

Wasserzeichen

Bei Papier gibt es manchmal sogenannte Wasserzeichen, also Stellen unterschiedlicher Papierdichte oder -zusammensetzung, die dann entweder über den Hersteller des Papiers Auskunft geben oder über den Besitzer oder aber etwa bei Geld als Beleg für die Echtheit verwendet werden. Bei digitalen Bildern wird dieser Begriff meist verwendet, um eine Methode zu benennen, bei der dem Bild eine sichtbare Kennzeichnung des Rechteinhabers hinzugefügt wird. Anders als bei gegebenenfalls zusätzlich vorhandenen sogenannten Exif-Informationen wird dazu der graphische Bildinhalt selbst modifiziert, etwa indem ein teiltransparentes Bild über das Originalbild gelegt wird, um zu unterbinden, daß das Bild sinnvoll anderweitig als nicht autorisierte Kopie ohne Hinweis auf den Autor veröffentlicht werden kann.

Solch ein Zeichen kann bei Pixelgraphik recht leicht mit PHP und der GD-Bibliothek hinzugefügt werden, wenn das Wasserzeichenbild etwa als PNG vorliegt. Symbole oder einfacher Text können auch ohne Vorlage hinzugefügt werden.

Für das folgende Beispiel habe ich zunächst eine Vorlage im Format SVG erstellt und diese dann mit dem Programm Inkscape in eine Pixelgraphik im Format PNG konvertiert. Im Allgemeinen ist nicht bekannt, ob das Original hell oder dunkel ist. Daher wurde bei der Vorlage ein schwarzer Rand zu einer weißen Schrift gewählt, es entsteht also mindestens ein Kontrast zwischen einem von beiden relativ zu Original, was eine sichtbare eindeutige Spur sicherstellt.

Anhand der Abmesssungen des mit diesem Wasserzeichen zu versehenden Bildes und der Abmessungen des Wasserzeichens selbst wird mit PHP eine Skalierung berechnet und das teiltransparente Bild in das Original kopiert und dann ausgegeben. Bei einer typischen Anwendung würde das Ergebnis sodann als Bild abgespeichert werden, damit die etwas aufwendigere Berechnung nicht bei jedem Aufruf durchgeführt werden muß. Zudem ist es dann nicht notwendig, das unmarkierte Original auf dem server zu belassen, was ja kontraproduktiv wäre, wenn dieses vor ungenehmigten Zugriff bewahrt werden soll.

Ausgabe (wiederholtes Aufrufen wechselt zufällig zwischen drei Bildern). Der Quelltext sieht wie folgt aus:

<?php
# php-Labor: Bild skalieren 
# 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);
$bild = mt_rand(1,3);

# Wir laden ein Bild von der Festplatte.
# Im array imininfo stehen interessante Informationen
# ueber das Bild. Im Element 3 steht auch noch
# drin, um welchen Bildtyp es sich handelt.
if ($bild==1) {
$ms='olaf-toif.jpg';
} elseif ($bild==2) {
$ms='zitronenfalter400.jpg';
} else {
$ms='passionsblume400.jpg';
}

$imininfo = GetImageSize ($ms);
$x2=$imininfo[0];
$y2=$imininfo[1];

$im = ImageCreateFromJPEG ($ms);

# Wasserzeichen von der Festplatte laden:
$wazi='waz.png';
$wazinfo = GetImageSize ($wazi);
$x1=$wazinfo[0];
$y1=$wazinfo[1];

$waz = ImageCreateFromPNG ($wazi);

#Skalierung des Wasserzeichens vorbereiten:
$xs=$x2/$x1;
$ys=$y2/$y1;

# Skalierung des Wasserzeichens
if ($xs < $ys) {
$skal=$xs;
} else {
$skal=$ys;
}
$xw=floor($x1*$skal);
$yw=floor($y1*$skal);
$x0=floor(($x2-$xw))/2;
$y0=floor(($y2-$yw)/2);


# Wasserzeichen skalieren und kopieren:

if ($gdversion >=2) {
imagecopyresampled ($im, $waz, $x0, $y0, 0, 0, $xw, $yw, $x1, $y1);
} else {
imagecopyresized ($im, $waz, $x0, $y0, 0, 0, $xw, $yw, $x1, $y1);
}

# header senden
# und Bild raushauen 

Header('Content-type: image/jpeg');
ImageJPEG($im);

# was meist sinnvoller ist, um Rechenleistung zu sparen,
# ist das Bild einmal erzeugen und dann als JPEG
# abspeichern:
# $file='mitwaz'.$ms;
# ImageJPG($im, $file);


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

Signatur

Bei einer Signatur wird ähnlich vorgegangen, nur ist diese meist kleiner und nicht zwangsläfig teiltransparent, befindet sich meist am Rand des Bildes und dient dazu, den Künstler oder Autor eindeutig zu identifizieren. Eine solche Signatur ist meist in einem inhaltlich nicht zentralen Bereich untergebracht, kann dort also auch relativ einfach durch Verkleinern des Bildes entfernt werden, dient also nicht der Rechtewahrung, sondern vor allem als Beleg für Autorenschaft oder 'Echtheit', wobei letzteres bei digitalen Produkten natürlich von untergeordneter Bedeutung ist, weil eine Kopie nicht weniger echt ist als ein Original. eine Besonderheit digitaler Medien.

Jedenfalls sind die Kriterien an eine Signatur an sich andere als die an ein Wasserzeichen. Anders als beim Wasserzeichen sollte die Signatur eigentlich schwer zu fälschen sein, damit nicht andere Werke als die des Autors ausgegeben werden können. Die Vermutung des Mißbrauchs ist also eher eine andere; beim Wasserzeichen ist es unerwünscht, daß das Bild ungefragt anderweitig veröffentlicht wird, bei einer Signatur ist es unerwünscht, daß Werke von anderen Autoren einem in der Regel bekannteren Autor zugeordnet werden. Durch Verlust der Signatur verlöre ein Werk also an Wert, durch Verlust des Wasserzeichens gewönne es an Wert, weil es dann allgemein verwendbar wäre.

Bei der Vorlage ist bei einer Signatur keine Teiltransparenz mehr notwendig. Wenn davon ausgegangen werden kann, daß das Original groß genug ist, muß das Signaturbild auch nicht mehr skaliert werden, was die Qualität und Lesbarkeit der Signatur im Endresultat verbessert.

<?php
# php-Labor: Bild skalieren 
# 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);
$bild = mt_rand(1,3);

# Wir laden ein Bild von der Festplatte.
# Im array imininfo stehen interessante Informationen
# ueber das Bild. Im Element 3 steht auch noch
# drin, um welchen Bildtyp es sich handelt.
if ($bild==1) {
$ms='olaf-toif.jpg';
} elseif ($bild==2) {
$ms='zitronenfalter400.jpg';
} else {
$ms='passionsblume400.jpg';
}

$imininfo = GetImageSize ($ms);
$x2=$imininfo[0];
$y2=$imininfo[1];

$im = ImageCreateFromJPEG ($ms);

# Signatur von der Festplatte laden:
$wazi='waz03.png';
$wazinfo = GetImageSize ($wazi);
$x1=$wazinfo[0];
$y1=$wazinfo[1];

$waz = ImageCreateFromPNG ($wazi);

$xw=72;
$yw=64;
$x0=$x2-$xw;
$y0=$y2-$yw;


# Signatur kopieren:

if ($gdversion >=2) {
imagecopyresampled ($im, $waz, $x0, $y0, 0, 0, $xw, $yw, $x1, $y1);
} else {
imagecopyresized ($im, $waz, $x0, $y0, 0, 0, $xw, $yw, $x1, $y1);
}

# header senden
# und Bild raushauen 

Header('Content-type: image/jpeg');
ImageJPEG($im);

# was meist sinnvoller ist, um Rechenleistung zu sparen,
# ist das Bild einmal erzeugen und dann als JPEG
# abspeichern:
# $file='mitwaz'.$ms;
# ImageJPG($im, $file);


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