[php] Bilder verkleinern

Das Große kleinmachen

Der Bildartist möchte auch in der Webansicht eine hohe Qualität vorlegen. Entweder man skaliert manuell alle Bilder in der Bildbearbeitung und legt sie auf dem Server ab, oder man überlässt die Aufgabe dem Server. Voraussetzung für folgende Code-Snippets ist natürlich php. In der Regel ist auf jedem Webserver, auf dem php läuft, auch die GDLib installiert. Eine Ansammlung von bilderstellenden und -verändernden Funktionen.

Was hab ich auf meinem Server?

phpinfo();listet alle Informationen zur php-Installation auf. Alle Module samt der Einstellungen werden ausgegeben. Nun kann man durchscrollen, um bei den Extensions nach gd bzw. imagick zu schauen. Übrigens kann man dafür auch simple php-Abfragen nutzen:
if(extension_loaded("gd"))
{echo"gdlib is installed
";}
else
{echo"gdlib is not here
";}
if(extension_loaded("imagick"))
{echo"imagemagick is installed
";}
else
{echo"imagemagick is not here
";}

GDlib – „Einfaches“ Verkleinern auf 50% bzw. ein festes Maß

Für das Verkleinern muß im Vornherein das Endmaß bekannt sein, also errechnen wir erstmal die neuen Bildmaße mittels einfachem Dreisatz. Die zu bearbeitende Bilddatei muß sich auf dem Server befinden.
$bildName="testbild.jpg";

# -- Ordner Originale
$ordnerO="./Fotos/";
# -- Ordner Verkleinerungen
$ordnerK="./Fotos/thumbs/";

$originalBild = $ordnerO.$bildName;

# -- Faktor berechnen bei vorgegebener Höhe in px
# -- oX alte Breite -- oY alte Höhe
# -- nX neue Breite -- nY neue Höhe
# -- getimagesize() ist eine gdlib-funktion!
$size = getimagesize($originalBild);
$oY = $size[1];
$oX = $size[0];

# -- Breite auf 50% festlegen
$nX = intval($oX*0.5);
$nY = intval($oY*$nX/$oX);

echo "Die neuen Bildmaße (relativ 50% zu Original) sind ";
echo "Breite:".$nX."px und Höhe:".$nY;
echo "
";

# -- Höhe auf fix 100px festlegen
# $nY = 100;
# $nX = intval($oX*$nY/$oY);
# echo "Die neuen Bildmaße (fixe Größe) sind ";
# echo "Breite:".$nX."px und Höhe:".$nY

# -- und nun die gdlib rechnen und speichern lassen
$altesBild = ImageCreateFromJPEG($originalBild);
$neuesBild = ImageCreateTrueColor($nX,$nY);
# -- Resized - Pixelwiederholung
# imageCopyResized($neuesBild, $altesBild, 0, 0, 0, 0,$nX, $nY, $oX, $oY);
# echo ".als Resized berechnet
";
# -- Resampled = bilinear Algorithm
imageCopyResampled($neuesBild, $altesBild, 0, 0, 0, 0,$nX, $nY, $oX, $oY);
imageJPEG($neuesBild,$ordnerK."thumb_".$bildName);
echo ".als Resampled (bilinear) berechnet";
echo "
";

Während imageCopyResized aussieht wie die Pixelwiederholung aus Photoshop (NearestNeighbour-Algorithmus), benutzt imageCopyResampled die bilineare Interpolation, wesentlich besser, aber noch nicht perfekt. Irgendwo irgendwann hab ich nen Codesnippet als php-Funktion gefunden für die bikubische Berechnung. Diese findet sich in den Beiträgen unter folgendem Link. php-snippet bicubic resampling for gdlib Ich habe den Code um den Farbpalettenteil gekürzt, nun sieht er so aus:


# -- found on - http://www.navioo.com/php/docs/function.imagecopyresampled.php
# -- written by rob and scott 2007

function imageCopyResampleBicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
{
$scaleX = ($src_w - 1) / $dst_w;
$scaleY = ($src_h - 1) / $dst_h;
$scaleX2 = $scaleX / 2.0;
$scaleY2 = $scaleY / 2.0;
# is it truecolor? $tc = imageistruecolor($src_img);
for ($y = $src_y; $y < $src_y + $dst_h; $y++) { $sY = $y * $scaleY; $siY = (int) $sY; $siY2 = (int) $sY + $scaleY2; for ($x = $src_x; $x < $src_x + $dst_w; $x++) { $sX = $x * $scaleX; $siX = (int) $sX; $siX2 = (int) $sX + $scaleX2; $c1 = imagecolorat($src_img, $siX, $siY2); $c2 = imagecolorat($src_img, $siX, $siY); $c3 = imagecolorat($src_img, $siX2, $siY2); $c4 = imagecolorat($src_img, $siX2, $siY); $r = (($c1 + $c2 + $c3 + $c4) >> 2) & 0xFF0000;
$g = ((($c1 & 0xFF00) + ($c2 & 0xFF00) + ($c3 & 0xFF00) + ($c4 & 0xFF00)) >> 2) & 0xFF00;
$b = ((($c1 & 0xFF) + ($c2 & 0xFF) + ($c3 & 0xFF) + ($c4 & 0xFF)) >> 2);
imagesetpixel($dst_img, $dst_x + $x - $src_x, $dst_y + $y - $src_y, $r+$g+$b);
}
}
}

Jetzt schauen wir uns mal das Ergebnis des php-Codes an. Das Originalbild (in Photoshop verkleinert auf Bloggröße)

(vlnr) Pixelwiederholung (resized) — Bilinear (resampled) — Bicubic (resampleBicubic)

Pixelwiederholung leidet deutlich unter Pixelartefakten an Kanten, Bilinear sieht erstmal ok aus, erweist sich reel doch als zu weich, die Bicubic (in dieser Form) ist letztlich auch nicht optimal und so manche Kante wird unzureichend (siehe zB Socke links) wiedergegeben. Was ist nun die Lösung?

Nachschärfen mit der gdlib und imageconvolution

Damit kann man die „Eigenen Filter“ aus Photoshop nachbauen, insbesondere den Hochpassfilter.

// define the sharpen matrix
$sharpen = array(
array(-1.2, -1.0, -1.2),
array(-1.0, 20.0, -1.0),
array(-1.2, -1.0, -1.2)
);

// calculate the sharpen divisor
$divisor = array_sum(array_map('array_sum', $sharpen));

// apply the matrix
imageconvolution($neuesBild, $sharpen, $divisor, 0);
imageJPEG($neuesBild,$ordnerK."thumb_ic_".$bildName);

(vlnr) bilinear (resampled) — bilinear nachgeschärft (resampled+imageconvolution) — bicubic

Ich halte das bilinear+imageconvolution-Thumbnail für ein recht gutes Ergebnis. Jeder kann auch nach eigenem Gutdünken Hand an die Konvolutionsmatrix anlegen. Dass es besser geht, beweist uns gleich die Extension imagemagick (imagick), die aber idR nicht standardmäßig installiert ist.

Imagemagick

Glücklich ist jener, der imagemagick (kurz imagick) als Extension zu laufen hat. Die Anweisungen (für das Verkleinern) sind kürzer und die Auswahl an Algorithmen ist groß, die Qualität ohne großen Aufwand deutlich besser als bei der gdlib. Hier also der Code dazu:

$thumb = new Imagick($originalBild);
# -- lanczos ausgewaehlt
# -- Bilinear = FILTER_TRIANGLE
# -- Bikubisch = FILTER_CATROM
$thumb->resizeImage($nX, $nY, Imagick::FILTER_LANCZOS, 1);
$thumb->writeImage($ordnerK."thumb_lan_".$bildName);
$thumb->destroy();

Die imagick-Ergebnisse in Bildform (darunter nochmal die Versionen aus der gdlib)
(vlnr) bilinear — bikubisch — lanczos

(vlnr) gdlib bilinear — gdlib bikubisch — gdlib bilinear+imageconvolution

Resümee

Nun, auf Anhieb würde ich sagen, Jupp, die LANCZOS-Version aus imagick schlägt alle anderen Rescaler. (wobei man Lanczos zugestehen muß, dass er eher für Upscaling-Operationen gedacht ist) – dennoch ist die Kombination aus bilinear und imageconvolution eine (meiner Ansicht nach) passable Lösung, wenn man kein imagick installiert hat (dies wird der höchstwahrscheinliche Fall sein, wenn man einen Webhost betreibt) – auch wenn die Nachschärfung im direkten Vergleich deutlich sichtbar ist. Der php-Sourcecode oben bietet noch Platz für Optimierungen, für die Verständlichkeit sind aber einige Arbeitsschritte bzw. Speicherbelegungen doppelt ausgeführt.

Ich versuche, in den nächsten Tagen noch den Mythos um die Bild-Verkleinerung in mehreren Durchgängen anzugehen. Lohnt es sich, ist der Unterschied sichtbar oder führt es zu sichtbaren Nachteilen? Bis demnächst.

Quellen:
imagick Filter usage
imagick Filter Constants
gdlib imagecopyresampled()
gdlib imageconvolution()

Ein Kommentar

  1. Vielen Dank für die Bildbeispiele und den Code! Die Qualität von Resampling + Convolution ist echt der Hammer – hab schon fast gedacht mit GD ließe sich nichts vernünftiges produzieren :).

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert