Ein Porträt kommt hochkant aus der Kamera, gebraucht wird ein quadratisches Profilbild. Ein Beleg kommt aus dem Scanner, und um das eigentliche Dokument liegt ein weißer Rand, dessen Breite bei jedem Gerät anders ausfällt. Beide Aufgaben erledigt PHP imagecrop() aus der GD-Bibliothek mit wenigen Zeilen. Ein Detail verschweigen dabei viele Beispiele im Netz, und es kostet regelmäßig einen Abend Fehlersuche: PHP imagecrop() gibt ein neues Bild aus und rührt das übergebene nicht an.
Das Bild zeigt das Rechteck, mit dem die Funktion arbeitet, samt seinen vier Angaben. Bevor der erste Aufruf folgt, lohnt sich eine kurze Einordnung.
Was PHP imagecrop() macht und was es nicht macht
Zuschneiden und Skalieren sind zwei verschiedene Dinge. PHP imagecrop() schneidet einen Teil aus dem Bild heraus und lässt die verbleibenden Pixel unangetastet, während das Verkleinern die vorhandenen Pixel neu berechnet. Wie das Verkleinern und das Erzeugen von Vorschaubildern funktioniert, steht im Tutorial PHP Bilder skalieren und Thumbnails erzeugen.
Die Signatur ist kurz: ein Bild hinein, ein Rechteck dazu, ein neues Bild oder false heraus. Keine Qualitätsstufe, kein Interpolationsverfahren. Das Quellbild besorgt eine Ladefunktion der GD-Bibliothek, also imagecreatefromjpeg() oder imagecreatefrompng().
Das Rechteck aus x, y, Breite und Höhe
Das Rechteck ist der einzige echte Parameter, den PHP imagecrop() kennt: ein assoziatives Array mit genau vier Schlüsseln. Die Angaben x und y bezeichnen die obere linke Ecke des Ausschnitts, gezählt ab dem Nullpunkt in der linken oberen Bildecke. Das ist der erste Stolperstein, denn gemeint ist die Ecke und nicht der Mittelpunkt. Dazu kommen Breite und Höhe in Pixeln, nicht etwa die Koordinaten der gegenüberliegenden Ecke.
Die Maße des Quellbildes holen imagesx() und imagesy(). Jede Rechnung, die einen Ausschnitt mittig setzt oder auf ein Seitenverhältnis bringt, beginnt bei diesen beiden Werten. Kommen die Zahlen aus einem Auswahlwerkzeug im Browser, stehen sie oft als Gleitkommazahl in der Anfrage. Ohne Umwandlung in Ganzzahlen entstehen Rundungsfehler an den Kanten.
Der erste Zuschnitt mit PHP imagecrop()
Damit steht alles bereit. Das folgende Beispiel schneidet aus einem Bild von 800 mal 600 Pixeln einen Bereich von 400 mal 300 Pixeln heraus.
<?php
$quelle = imagecreatefromjpeg('foto.jpg'); /* 800 x 600 */
$ausschnitt = imagecrop($quelle, [
'x' => 100,
'y' => 50,
'width' => 400,
'height' => 300,
]);
if ($ausschnitt === false) {
exit('Zuschnitt fehlgeschlagen');
}
/* Gemessen: $quelle ist weiterhin 800 x 600, */
/* $ausschnitt ist ein neues GdImage mit 400 x 300 */
imagejpeg($ausschnitt, 'ausschnitt.jpg', 90);
Die beiden letzten Kommentarzeilen sind der Kern des Ganzen. Wer den Rückgabewert nicht auffängt und danach $quelle speichert, hat nichts geschnitten und sucht den Fehler an der falschen Stelle. Und die Abfrage auf false deckt einen Fall ab, der im Test tatsächlich eintritt: ein Rechteck mit der Breite null liefert genau diesen Wert, und das nachfolgende imagejpeg() quittiert das mit einem Typfehler.
Quadratischer Ausschnitt aus der Bildmitte für Avatare
Der meistgesuchte Anwendungsfall ist das Profilbild. Wer ein hochkantes Foto einfach auf ein Quadrat skaliert, staucht die Gesichter. Richtig ist ein echter Ausschnitt: die kürzere Kante gibt die Seitenlänge vor, und der Überstand der längeren Achse wird halbiert. Genau dafür ist PHP imagecrop() gemacht, denn die Rechnung passt in vier Zeilen.
<?php
function quadratAusMitte(GdImage $bild): GdImage|false
{
$breite = imagesx($bild);
$hoehe = imagesy($bild);
/* Die kuerzere Kante bestimmt die Seitenlaenge */
$kante = min($breite, $hoehe);
return imagecrop($bild, [
'x' => (int) (($breite - $kante) / 2),
'y' => (int) (($hoehe - $kante) / 2),
'width' => $kante,
'height' => $kante,
]);
}
$foto = imagecreatefromjpeg('portrait.jpg'); /* 600 x 900 */
$avatar = quadratAusMitte($foto);
/* Gemessen: Kante 600, Start bei x = 0 und y = 150,
Ergebnis 600 x 600. Ein Querformat 1200 x 800
ergibt dagegen 800 x 800 ab x = 200. */
if ($avatar !== false) {
imagejpeg($avatar, 'avatar.jpg', 90);
}
Wird der Überstand nicht halbiert, sitzt der Ausschnitt am oberen Bildrand, und bei Porträts fehlt dann das Kinn. Die beiden Divisionen entscheiden also über einen brauchbaren Avatar. Stammen die Fotos aus einem Formular, passt davor der sichere Datei-Upload.
Ränder automatisch entfernen mit imagecropauto()
Beim Scannen ist die Randbreite unbekannt und ändert sich von Vorlage zu Vorlage. Feste Koordinaten helfen hier nicht weiter. Die Schwesterfunktion imagecropauto() sucht den Rand selbst und schneidet ihn ab. Sie nimmt einen Modus entgegen, dazu wahlweise einen Schwellwert und eine Farbe, und liefert wie PHP imagecrop() ein neues Bild oder false.
<?php
/* Testvorlage: 400 x 300 weiss, Inhalt von 60,40 bis
339,259, also ein Nutzbereich von 280 x 220 */
$scan = imagecreatefrompng('beleg.png');
$ohneRand = imagecropauto($scan, IMG_CROP_WHITE);
if ($ohneRand === false) {
/* false heisst hier: nach dem Schnitt bliebe nichts uebrig */
$ohneRand = $scan; /* also mit dem Original weiterarbeiten */
}
/* Gemessen: 280 x 220, der weisse Rahmen ist exakt weg.
IMG_CROP_SIDES liefert hier dasselbe Ergebnis,
IMG_CROP_DEFAULT dagegen unveraendert 400 x 300. */
imagepng($ohneRand, 'beleg-beschnitten.png');
Zwei Ergebnisse aus dem Test sind wichtig. Auf einem komplett weißen Bild kommt false zurück, weil nach dem Abschneiden nichts übrig bliebe. Und auf einem Bild ganz ohne weißen Rand kommt das Bild unverändert zurück. Der Rückgabewert false bedeutet also nicht zwingend einen Fehler, aber er bedeutet immer, dass kein Bild vorliegt.
Die sechs Modi von imagecropauto() im Überblick
Welcher Modus greift, hängt allein davon ab, woraus der Rand besteht. Bei PHP imagecrop() gibt der Aufrufer das Rechteck vor, hier bestimmt es das Bild selbst. Die folgende Tabelle ordnet die sechs Konstanten ihrem Einsatzzweck zu.
| Konstante | Wofür gedacht | Besonderheit |
IMG_CROP_DEFAULT | Voreinstellung, verhält sich laut Handbuch wie der transparente Modus | Ohne Alphakanal passiert nichts |
IMG_CROP_TRANSPARENT | Durchsichtige Ränder um Logos und Symbole | Setzt einen Alphakanal voraus |
IMG_CROP_BLACK | Schwarze Balken, etwa aus Videostandbildern | Nur bei reinem Schwarz |
IMG_CROP_WHITE | Scans, Belege und Zeichnungen auf weißem Papier | Nur bei reinem Weiß |
IMG_CROP_SIDES | Fotos mit unbekannter Randfarbe, wertet die vier Bildecken aus | Gute erste Wahl bei Fotos |
IMG_CROP_THRESHOLD | Ränder, die nur ungefähr einer Farbe entsprechen | Farbe ist Pflicht, sonst Abbruch |
Die letzte Zeile ist die wichtigste. Der Schwellwertmodus vergleicht jedes Randpixel mit einer Farbe, die er nicht erraten kann, deshalb muss sie ausdrücklich mitkommen. Der Schwellwert selbst liegt zwischen 0 und 1 und beschreibt, wie stark eine Farbe abweichen darf, um noch als Rand durchzugehen. Fehlt die Farbe, wirft PHP 8 einen ValueError mit dem Wortlaut, dass der vierte Wert größer oder gleich null sein muss. Ein -1 genügt ebenfalls nicht.
<?php
/* Vorlage: 400 x 300, Rand in 252,252,252, also fast
weiss. Inhalt von 70,45 bis 329,254 = 260 x 210 */
$bild = imagecreatefromjpeg('foto-mit-rand.jpg');
$weiss = imagecolorallocate($bild, 255, 255, 255);
$beschnitten = imagecropauto($bild, IMG_CROP_THRESHOLD, 0.2, $weiss);
if ($beschnitten === false) {
exit('Kein passender Rand erkannt');
}
/* Gemessen: 265 x 212. Die fuenf Pixel Zugabe stammen
aus den JPEG-Artefakten an der Inhaltskante. */
imagejpeg($beschnitten, 'ohne-rand.jpg', 92);
/* Ohne den vierten Wert:
ValueError: imagecropauto(): Argument #4 ($color)
must be greater than or equal to 0 when using
the threshold mode */
Der Modus IMG_CROP_DEFAULT verhält sich nach dem Handbuch wie der transparente Modus. Bei einem Foto ohne Alphakanal führt das zu keinem Ergebnis, im Test kam das Bild unverändert in voller Größe zurück. Als praktische Empfehlung greift man dort zu IMG_CROP_SIDES, weil dieser Modus die vier Bildecken auswertet und damit ohne Vorwissen über die Randfarbe auskommt.
Erst zuschneiden, dann skalieren
Wenn beides zusammenkommt, entscheidet die Reihenfolge über die Schärfe. Zuerst schneidet PHP imagecrop() den gewünschten Bereich aus der vollen Auflösung, danach bringt imagescale() das Ergebnis auf die Zielgröße. Umgekehrt stammt der Ausschnitt aus bereits reduzierten Pixeln, und das Vorschaubild wirkt weich.
<?php
$original = imagecreatefromjpeg('produkt.jpg'); /* 2000 x 1500 */
/* Schritt 1: Ausschnitt im gewuenschten Verhaeltnis */
$ausschnitt = imagecrop($original, [
'x' => 0,
'y' => 0,
'width' => 1200,
'height' => 900,
]);
if ($ausschnitt === false) {
exit('Zuschnitt fehlgeschlagen');
}
/* Schritt 2: erst danach verkleinern */
$vorschau = imagescale($ausschnitt, 400, 300, IMG_BICUBIC);
/* Gemessen: 1200 x 900, danach 400 x 300.
imagescale($ausschnitt, 400, -1) liefert dasselbe,
die Hoehe folgt dann dem Seitenverhaeltnis. */
imagejpeg($vorschau, 'produkt-vorschau.jpg', 85);
Damit ist auch die Frage beantwortet, welche der drei GD-Funktionen wann zuständig ist. Der Ausschnitt gehört zu PHP imagecrop(), die Größe zu imagescale(), und imagecopyresampled() wird nur dort gebraucht, wo beides in einem einzigen Aufruf zusammenfallen soll. Die Details zum Verkleinern und zum Seitenverhältnis stehen im Tutorial zu den Thumbnails.
Transparenz bei PNG erhalten
Beim Zuschneiden von Logos mit durchsichtigem Hintergrund passiert oft dasselbe: der Ausschnitt gelingt, aber in der gespeicherten Datei ist der freie Bereich schwarz. Der Grund liegt nicht bei PHP imagecrop(), sondern beim Speichern. Die beiden Schalter imagealphablending() und imagesavealpha() gehören auf das Ergebnisbild, nicht nur auf die Quelle.
<?php
$logo = imagecreatefrompng('logo.png');
$teil = imagecrop($logo, [
'x' => 20,
'y' => 20,
'width' => 200,
'height' => 200,
]);
if ($teil === false) {
exit('Zuschnitt fehlgeschlagen');
}
/* Entscheidend: beide Aufrufe auf dem Ergebnisbild */
imagealphablending($teil, false);
imagesavealpha($teil, true);
imagepng($teil, 'logo-teil.png');
/* Nachgemessen mit imagecolorsforindex an der Ecke:
ohne diese zwei Zeilen alpha = 0, also deckendes
Schwarz. Mit ihnen alpha = 127, voll durchsichtig. */
Die gemessenen Alphawerte erklären die Beobachtung genau. Ein Alphawert von 0 steht in GD für volle Deckkraft, 127 für vollständige Durchsichtigkeit. Ohne die beiden Schalter schreibt imagepng() den Kanal nicht mit, und aus dem freien Bereich wird deckendes Schwarz. Wer danach noch ein Logo aufbringen will, findet den Weg im Tutorial zu Wasserzeichen mit GD.
GdImage seit PHP 8 und die Frage nach imagedestroy()
Bis PHP 7 waren GD-Bilder Ressourcen, seit PHP 8.0 sind es Objekte der Klasse GdImage. Praktisch bedeutet das: der Speicher wird freigegeben, sobald keine Referenz mehr auf das Objekt zeigt. imagedestroy() hat damit seine Aufgabe verloren. Der Aufruf schadet nicht, aber er suggeriert eine Ressourcen-Semantik, die es nicht mehr gibt.
<?php
foreach (glob('quelle/*.jpg') as $datei) {
$bild = imagecreatefromjpeg($datei);
if ($bild === false) {
continue;
}
$avatar = quadratAusMitte($bild);
if ($avatar !== false) {
imagejpeg($avatar, 'ziel/' . basename($datei), 88);
}
/* Seit PHP 8.0 sind das Objekte, kein imagedestroy noetig.
unset gibt den Speicher sofort frei statt erst beim
naechsten Schleifendurchlauf. */
unset($bild, $avatar);
}
In einer langen Schleife lohnt sich das unset() trotzdem, weil die beiden Variablen sonst bis zur nächsten Zuweisung belegt bleiben und zwei große Bilder gleichzeitig im Speicher stehen. Am Aufruf von PHP imagecrop() ändert der Typwechsel nichts: die Funktion nahm früher eine Ressource entgegen und nimmt heute ein Objekt. Wer noch Altcode mit is_resource() pflegt, findet die Grundlagen in der Einführung zur GD-Bibliothek.
Typische Fehler beim Zuschneiden
Zwei Eigenschaften von PHP imagecrop() erzeugen zusammen so gut wie jede Frage zu dieser Funktion: sie arbeitet nicht auf dem Original, und sie meldet Grenzverletzungen nicht. Was jetzt kommt, sind Spielarten dieser beiden Sätze.
Nach dem Aufruf hat sich nichts geändert Das Ergebnis von PHP imagecrop() wurde nicht aufgefangen. Das Quellbild bleibt in jedem Fall so groß, wie es war. Gespeichert werden muss die neue Variable.
Schwarzer oder durchsichtiger Streifen am Rand Das Rechteck ragt über die Bildkante hinaus. Ein Ausschnitt ab 700,500 mit 400 mal 400 Pixeln aus einem Bild von 800 mal 600 liefert im Test ein Ergebnis von 400 mal 400, dessen Überstand komplett schwarz ist. Eine Fehlermeldung gibt es nicht.
Der Ausschnitt sitzt oben statt mittig Der Überstand der längeren Achse wurde nicht halbiert. Ohne die Division durch zwei beginnt jeder Zuschnitt am Bildanfang.
Der Typfehler beim Speichern Das Ergebnis war false, und die Speicherfunktion bekommt keinen gültigen Bildtyp. Eine Abfrage auf false vor jeder Weiterverarbeitung fängt das ab.
Gegen den zweiten Punkt hilft eine kleine Hilfsfunktion, die das Rechteck vor dem Aufruf von PHP imagecrop() auf die vorhandenen Bildmaße stutzt. Sie ist in jedem Projekt einmal zu schreiben und danach nie wieder ein Thema.
<?php
function sicheresRechteck(
GdImage $bild,
int $x,
int $y,
int $breite,
int $hoehe
): array {
$maxX = imagesx($bild);
$maxY = imagesy($bild);
$x = max(0, min($x, $maxX - 1));
$y = max(0, min($y, $maxY - 1));
return [
'x' => $x,
'y' => $y,
'width' => min($breite, $maxX - $x),
'height' => min($hoehe, $maxY - $y),
];
}
$bild = imagecreatefromjpeg('foto.jpg'); /* 800 x 600 */
/* Werte aus dem Browser sind oft zu gross gerundet */
$rechteck = sicheresRechteck($bild, 900, 700, 800, 800);
/* Gemessen: x = 799, y = 599, width = 1, height = 1.
Bei 700,500 mit 800 x 800 kommt 700,500,100,100
heraus, das Ergebnis ist sauber 100 x 100. */
$ergebnis = imagecrop($bild, $rechteck);
Zuschnitt, Randsuche und Größenänderung greifen in einer festen Reihenfolge ineinander, und die Prüfung auf false sitzt mittendrin.
flowchart TD
A[Bild zuschneiden] --> B{Rand bekannt?}
B -->|Ja, feste Masse| C[imagecrop Rechteck]
B -->|Nein, Rand weg| D[imagecropauto Modus]
C --> E{Ergebnis false?}
D --> E
E -->|Ja| F[Abbruch mit Meldung]
E -->|Nein| G{Danach kleiner?}
G -->|Ja| H[imagescale auf Zielmass]
G -->|Nein| I[Direkt speichern]
H --> J[imagepng oder imagejpeg]
I --> J
Fazit
PHP imagecrop() braucht nur zwei Angaben, ein Bild und ein Rechteck aus x, y, Breite und Höhe. Der wichtigste Satz zur Funktion ist trotzdem ein anderer: sie liefert ein neues Bild und lässt das Original in Ruhe. Wer das berücksichtigt, den Rückgabewert auf false abfragt und sein Rechteck vorher auf die Bildmaße stutzt, hat die drei häufigsten Fehlerquellen bereits hinter sich.
Für den unbekannten Rand ist imagecropauto() zuständig, mit IMG_CROP_WHITE für Scans, IMG_CROP_SIDES für Fotos und IMG_CROP_THRESHOLD samt Farbe für alles Ungenaue. Kommt eine Größenänderung dazu, gehört sie hinter den Zuschnitt und nicht davor. Bei PNG-Dateien sichern imagealphablending() und imagesavealpha() auf dem Ergebnisbild die Transparenz. Mehr braucht es nicht, um mit PHP imagecrop() einen Bilderstapel zuverlässig auf ein einheitliches Format zu bringen.