Eines meiner etwas zurückliegenden Projekte zwang mich externe Bilder durch meinen Server zu laden, zu verkleinern und schließlich auch die Durchschnittsfarbe zu berechnen. Aus Gründen der Performancesteigerung war ich nie ein Fan davon die Durchschnittsfarbe Pixel für Pixel des Originalbildes zu berechnen. Es gibt eine viel elegantere und fast genauso exakte Lösung:
Unser Ziel: Wir möchten ein Bild (jpg, gif, png) über einen HTTP-Stream laden, dieses lokal auf dem Server speichern, ein entsprechendes Thumbnail (eine verkleinerte Version) erzeugen, speichern und schlussendlich die dominierende – oder auch durchschnittliche – Farbe im Bild berechnen und abspeichern. Eventuell möchten wir zusätzlich alles in einer Datenbank hinterlegen – das soll aber zunächst außerhalb unserer Betrachtung liegen.
Für das Berechnen der Durchschnittsfarbe gibt es einen wunderbar einfachen Trick. Doch dazu später mehr. Beginnen wir zunächst einmal mit einer Konstante und einer Funktion, die wir später noch benötigen:
DEFINE('CTX', stream_context_create(array(
'http' => array(
'timeout' => 20,
'method' => 'get',
'header' => 'Referer: XYZ'
)
)));
Unsere Konstante CTX ist also ein array. Wir benötigen es für unsere HTTP-Anfrage, mit der wir das gewünschte Bild auf unseren Server laden. Für diese Anfrage verwenden wir die HTTP-GET-Methode, definieren einen Timeout von maximal 20 Sekunden und senden einen Referer im Header, den ich hier mal einfach nur “XYZ” genannt habe.
function randomstring_creator($len = 20) {
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
$string = '';
mt_srand((double) microtime() * 1000000);
for($i = 0; $i < $len; $i++) {
$string .= $chars{mt_rand(0, strlen($chars))};
}
return($string);
}
Die Funktion randomstring_creator() generiert uns eine zufällige Zeichenfolge mit einer beliebigen Länge. Wir benötigen diese Funktion, da wir in diesem Beispiel die Bilder lokal unter einem zufälligem Namen speichern werden.
Weiter geht es mit unseren Klassen. Beginnen wir mit der Klasse Image:
class Image {
var $img;
var $source;
var $local;
var $dimX;
var $dimY;
var $avg_colour;
function get($source, $quality = 90) {
// siehe unten
}
function calc_avg_colour() {
// siehe unten
}
}
Unsere Klasse besitzt also die Eigenschaften source (die Url, von der das Bild stammt), local (den lokalen Speicherpfad), dimX, dimY (die Abmessungen des Bildes in Pixel) und avg_colour (den farblichen Durchschnittswert als HEX-Code in der Form rrggbb). Die Eigenschaft img ist eine Referenz auf das Image-Objekt, dass ich aber später noch näher beleuchten werde.
Die beiden Funktionen get() und calc_avg_colour() der Klasse sollten selbsterklärend sein. Mit der Funktion get(), die gleichzeitig auch als Konstruktor dient, laden wir das Bild, dass unter source zu finden ist, auf unseren Server und speichern es in der gewünschten JPEG-Qualität (90 als Standard) lokal.
calc_avg_colour() berechnet – wie der Name schon sagt – die Durchschnittsfarbe des in der Eigenschaft img gespeicherten Bildes.
Wir wollen zusätzlich aber auch kleine Abbilder des Originalbildes erstellen und erweitern dazu unsere Image-Klasse um die Klasse Thumbnail:
class Thumbnail extends Image {
function create($parent, $X = 300, $Y = 300, $quality = 60) {
// siehe unten
}
}
Diese Erweiterung besitzt zunächst nur eine Funktion, mit der das Thumbnail erstellt wird. Die übergebene Variable parent enthält dabei eine bereits existierende Image-Klasse, die herunterskaliert werden soll. Die maximale Breite und Höhe werden mit den Variablen X und Y (standard bei 300 Pixel) übergeben. Die JPEG-Qualität beträgt standardmäßig nur 60.
Das eigentliche Programm sieht dann nur noch so aus:
$imgurl = 'original.jpg';
// neues Objekt der Klasse Image erzeugen
$img = new Image;
// Bild aus der imgurl laden
if($img->get($imgurl)) {
// ein Objekt der Klasse Thumbnail erzeugen
$thumb = new Thumbnail;
// und ein Thumbnail des oben definierten Objektes der Klasse Image generieren
$thumb->create($img, 300, 300);
// dann noch die Durschschnittsfarbe berechnen
$thumb->calc_avg_colour();
// und Daten ausgeben... fertig
echo('Image: ' . $img->source . "\n");
echo('Thumbnail: ' . $thumb->source . "\n");
echo('Average colour: #' . $thumb->avg_colour . "\n");
} else {
echo('Could not get the image.');
}
So, dann sehen wir uns also mal unsere Funktionen der Klassen an. Beginnen wir mit der Funktion get():
function get($source, $quality = 90) {
$this->source = $source;
// wir laden das Bild von $source:
if(($file = file_get_contents($this->source, false, CTX))) {
// wenn erfolgreich, erzeugen wir mit den geladenen Daten
// ein neues Bild und speichern dieses in der Eigenschaft img
$this->img = imagecreatefromstring($file);
// wir lesen die Höhe und Breite des Bildes aus
$this->dimX = imagesx($this->img);
$this->dimY = imagesy($this->img);
// und speichern das Bild unter zufälligem Namen mit imagejpeg lokal im Ordner "raw"
$this->local = 'raw/' . randomstring_creator() . '.jpg';
imagejpeg($this->img, $this->local, $quality);
// bei Erfolg true zurückgeben
return(true);
} else {
// false, wenn das Bild nicht geladen werden konnte
return(false);
}
}
Ein Thumbnail erzeugen wir mit dieser Funktion der Klasse Thumbnail:
function create($parent, $X = 300, $Y = 300, $quality = 60) {
// zunächst die vorläufigen Abmessungen speichern
$this->dimX = $X;
$this->dimY = $Y;
// die Abmessungen und deren Verhältnis des Originalbildes holen
$origX = $parent->dimX;
$origY = $parent->dimY;
$ratio = $origX / $origY;
// wenn das Originalbild breiter als hoch ist, kürzen wir die Höhe
if($X / $Y > $ratio) {
$this->dimX = $Y * $ratio;
} else {
// wenn das Originalbild höher als breit ist, kürzen wir die Breite
$this->dimY = $X / $ratio;
}
// hier erstellen wir ein neues Bild mit den oben berechneten Abmessungen
$this->img = imagecreatetruecolor($this->dimX, $this->dimY);
// und kopieren das Originalbild einfach in das eben erstellte kleinere, dabei wird es verkleinert
imagecopyresampled($this->img, $parent->img, 0, 0, 0, 0, $this->dimX, $this->dimY, $origX, $origY);
// jetzt speichern wir das neue Thumbnail noch unter zufälligem Namen im Ordner "thumb"
$this->source = $parent->local;
$this->local = 'thumb/' . randomstring_creator() . '.jpg';
imagejpeg($this->img, $this->local, $quality);
}
Das Herzstück: Wir wollen die Durchschnittsfarbe ermitteln. Dazu könnten wir einfach alle Farbwerte aller Pixel zusammen addieren und dann durch die Anzahl der Pixel teilen. Bei einem Thumbnail von der Größe 300 x 300 Pixel, würden wir aber eine Schleife mit 90.000 Durchläufen benötigen. Es gibt einen besseren Weg – einen ganz logischen Trick.
Durchschnittsfarbe berechnen
Wir haben oben beim Erstellen des Thumbnails die Funktion imagecopyresampled() verwendet. Was wenn wir unser Thumbnail einfach auf eine Größe von 1×1 Pixel skalieren? Genau: Es bleibt ein Pixel mit der Durschnittsfarbe übrig. Dessen Farbe auszulesen ist mit imagecolorat() dann nur noch eine Kleinigkeit. Los gehts:
function calc_avg_colour() {
// ein temporäres Bild mit den Abmessungen 1x1 Pixel erstellen
$avrg = imagecreatetruecolor(1, 1);
// Das Thumbnail einfach reinkopieren, dabei auf 1x1 Pixel skalieren
imagecopyresampled($avrg, $this->img, 0, 0, 0, 0, 1, 1, $this->dimX, $this->dimY);
// Die Farbe des verbleibenden Pixels auslesen
$rgb = imagecolorat($avrg, 0, 0);
// Das temporäre Bild wieder löschen
imagedestroy($avrg);
// Und die Farbe in HEX umwandeln
$r = dechex($rgb >> 16);
$g = dechex($rgb >> 8 & 255);
$b = dechex($rgb & 255);
if(strlen($r) < 2) $r = 0 . $r;
if(strlen($g) < 2) $g = 0 . $g;
if(strlen($b) < 2) $b = 0 . $b;
// tataaaa... da ist unsere Durchschnittsfarbe:
$this->avg_colour = $r . $g . $b;
}
Super. Das wäre es. Ende und aus.
Cool, danke! Genau sowas habe ich gesucht!
Das brauche ich für (m)ein Custom WordPress Plugin