Effiziente Größenänderung von JPEG-Bildern in PHP

81

Was ist der effizienteste Weg, um die Größe großer Bilder in PHP zu ändern?

Ich verwende derzeit die GD- Funktion imagecopyresampled, um hochauflösende Bilder aufzunehmen und sie sauber auf eine Größe für die Webansicht zu verkleinern (ungefähr 700 Pixel breit und 700 Pixel hoch).

Dies funktioniert hervorragend bei kleinen Fotos (unter 2 MB) und der gesamte Größenänderungsvorgang dauert auf dem Server weniger als eine Sekunde. Die Website wird jedoch möglicherweise Fotografen bedienen, die möglicherweise Bilder mit einer Größe von bis zu 10 MB (oder Bilder mit einer Größe von bis zu 5000 x 4000 Pixel) hochladen.

Wenn Sie diese Art der Größenänderung bei großen Bildern ausführen, wird die Speichernutzung tendenziell um einen sehr großen Rand erhöht (größere Bilder können die Speichernutzung für das Skript auf über 80 MB erhöhen). Gibt es eine Möglichkeit, diesen Größenänderungsvorgang effizienter zu gestalten? Sollte ich eine alternative Bildbibliothek wie ImageMagick verwenden ?

Im Moment sieht der Größenänderungscode ungefähr so ​​aus

function makeThumbnail($sourcefile, $endfile, $thumbwidth, $thumbheight, $quality) {
    // Takes the sourcefile (path/to/image.jpg) and makes a thumbnail from it
    // and places it at endfile (path/to/thumb.jpg).

    // Load image and get image size.
    $img = imagecreatefromjpeg($sourcefile);
    $width = imagesx( $img );
    $height = imagesy( $img );

    if ($width > $height) {
        $newwidth = $thumbwidth;
        $divisor = $width / $thumbwidth;
        $newheight = floor( $height / $divisor);
    } else {
        $newheight = $thumbheight;
        $divisor = $height / $thumbheight;
        $newwidth = floor( $width / $divisor );
    }

    // Create a new temporary image.
    $tmpimg = imagecreatetruecolor( $newwidth, $newheight );

    // Copy and resize old image into new image.
    imagecopyresampled( $tmpimg, $img, 0, 0, 0, 0, $newwidth, $newheight, $width, $height );

    // Save thumbnail into a file.
    imagejpeg( $tmpimg, $endfile, $quality);

    // release the memory
    imagedestroy($tmpimg);
    imagedestroy($img);
Maxsilber
quelle

Antworten:

45

Die Leute sagen, dass ImageMagick viel schneller ist. Im besten Fall vergleichen Sie einfach beide Bibliotheken und messen Sie das.

  1. Bereiten Sie 1000 typische Bilder vor.
  2. Schreiben Sie zwei Skripte - eines für GD und eines für ImageMagick.
  3. Führen Sie beide einige Male aus.
  4. Vergleichen Sie die Ergebnisse (Gesamtausführungszeit, CPU- und E / A-Auslastung, Bildqualität des Ergebnisses).

Etwas, das das Beste für alle anderen ist, könnte nicht das Beste für Sie sein.

Meiner Meinung nach hat ImageMagick auch eine viel bessere API-Oberfläche.

Grzegorz Gierlik
quelle
2
Auf Servern, mit denen ich gearbeitet habe, geht GD häufig der Arbeitsspeicher aus und stürzt ab, während ImageMagick dies niemals tut.
Abhi Beckert
Mehr kann ich nicht widersprechen. Ich finde Imagemagick ein Albtraum, mit dem ich arbeiten kann. Ich erhalte häufig 500 Serverfehler für große Bilder. Zugegeben, die GD-Bibliothek würde früher abstürzen. Trotzdem sprechen wir manchmal nur von 6-MB-Bildern, und 500 Fehler sind nur die schlimmsten.
Single Entity
19

Hier ist ein Ausschnitt aus den php.net-Dokumenten, die ich in einem Projekt verwendet habe und die einwandfrei funktionieren:

<?
function fastimagecopyresampled (&$dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h, $quality = 3) {
    // Plug-and-Play fastimagecopyresampled function replaces much slower imagecopyresampled.
    // Just include this function and change all "imagecopyresampled" references to "fastimagecopyresampled".
    // Typically from 30 to 60 times faster when reducing high resolution images down to thumbnail size using the default quality setting.
    // Author: Tim Eckel - Date: 09/07/07 - Version: 1.1 - Project: FreeRingers.net - Freely distributable - These comments must remain.
    //
    // Optional "quality" parameter (defaults is 3). Fractional values are allowed, for example 1.5. Must be greater than zero.
    // Between 0 and 1 = Fast, but mosaic results, closer to 0 increases the mosaic effect.
    // 1 = Up to 350 times faster. Poor results, looks very similar to imagecopyresized.
    // 2 = Up to 95 times faster.  Images appear a little sharp, some prefer this over a quality of 3.
    // 3 = Up to 60 times faster.  Will give high quality smooth results very close to imagecopyresampled, just faster.
    // 4 = Up to 25 times faster.  Almost identical to imagecopyresampled for most images.
    // 5 = No speedup. Just uses imagecopyresampled, no advantage over imagecopyresampled.

    if (empty($src_image) || empty($dst_image) || $quality <= 0) { return false; }
    if ($quality < 5 && (($dst_w * $quality) < $src_w || ($dst_h * $quality) < $src_h)) {
        $temp = imagecreatetruecolor ($dst_w * $quality + 1, $dst_h * $quality + 1);
        imagecopyresized ($temp, $src_image, 0, 0, $src_x, $src_y, $dst_w * $quality + 1, $dst_h * $quality + 1, $src_w, $src_h);
        imagecopyresampled ($dst_image, $temp, $dst_x, $dst_y, 0, 0, $dst_w, $dst_h, $dst_w * $quality, $dst_h * $quality);
        imagedestroy ($temp);
    } else imagecopyresampled ($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
    return true;
}
?>

http://us.php.net/manual/en/function.imagecopyresampled.php#77679

Kevin
quelle
Wissen Sie, was Sie für $ dst_x, $ dst_y, $ src_x, $ src_y setzen würden?
JasonDavis
Sollten Sie nicht ersetzen $quality + 1mit ($quality + 1)? So wie es ist, ändern Sie nur die Größe mit einem nutzlosen zusätzlichen Pixel. Wo ist die Prüfung auf Kurzschluss, wenn $dst_w * $quality> $src_w?
Walf
8
Kopieren / Einfügen aus der vorgeschlagenen Bearbeitung: Dies ist Tim Eckel, der Autor dieser Funktion. Die $ Qualität + 1 ist korrekt. Sie wird verwendet, um einen ein Pixel breiten schwarzen Rand zu vermeiden und die Qualität nicht zu ändern. Diese Funktion ist auch Plug-in-kompatibel mit imagecopyresampled. Bei Fragen zur Syntax siehe den Befehl imagecopyresampled ist sie identisch.
Andomar
Wie ist diese Lösung besser als die in der Frage vorgeschlagene? Sie verwenden weiterhin die GD-Bibliothek mit denselben Funktionen.
TMS
1
@Tomas, eigentlich wird es imagecopyresized()auch verwendet. Grundsätzlich wird das Bild zuerst auf eine überschaubare Größe geändert ( final dimensionsmultipliziert mit quality) und dann erneut abgetastet, anstatt einfach das Bild in voller Größe neu abzutasten. Es kann in einer geringeren Qualität Endbild führen, aber es nutzt weit weniger Ressourcen für größere Bilder als imagecopyresampled()allein als Resampling - Algorithmus nur mit einem Bild die Größe von 3 x die endgültigen Dimensionen standardmäßig zu tun hat, im Vergleich zu dem Bild in voller Größe ( Dies kann weitaus größer sein, insbesondere bei Fotos, deren Größe für Miniaturansichten geändert wird.
0b10011
12

phpThumb verwendet ImageMagick, wann immer dies aus Gründen der Geschwindigkeit möglich ist (ggf. auf GD zurückgreifen), und scheint ziemlich gut zwischenzuspeichern, um die Belastung des Servers zu verringern. Das Ausprobieren ist ziemlich einfach (um die Größe eines Bildes zu ändern, rufen Sie einfach phpThumb.php mit einer GET-Abfrage auf, die den grafischen Dateinamen und die Ausgabedimensionen enthält), damit Sie versuchen können, festzustellen, ob es Ihren Anforderungen entspricht.

Phenry
quelle
aber dies ist nicht Teil, wenn Standard-PHP, wie es scheint ... so wird es auf den meisten Hostings nicht verfügbar sein :(
TMS
1
sieht für mich so aus, als wäre es nur ein PHP-Skript, man muss nur PHP GD und Imagemagick haben
Flo
Es ist in der Tat eher ein PHP-Skript als eine Erweiterung, die Sie installieren müssen, und eignet sich daher gut für gemeinsam genutzte Hosting-Umgebungen. Beim Hochladen von JPEG-Bildern <1 MB mit den Abmessungen 4000 x 3000 ist der Fehler "Zulässige Speichergröße von N Bytes erschöpft" aufgetreten. Die Verwendung von phpThumb (und damit ImageMagick) löste das Problem und war sehr einfach in meinen Code zu integrieren.
w5m
10

Verwenden Sie bei größeren Bildern libjpeg, um die Größe beim Laden von Bildern in ImageMagick zu ändern. Dadurch wird die Speichernutzung erheblich reduziert und die Leistung verbessert. Mit GD ist dies nicht möglich.

$im = new Imagick();
try {
  $im->pingImage($file_name);
} catch (ImagickException $e) {
  throw new Exception(_('Invalid or corrupted image file, please try uploading another image.'));
}

$width  = $im->getImageWidth();
$height = $im->getImageHeight();
if ($width > $config['width_threshold'] || $height > $config['height_threshold'])
{
  try {
/* send thumbnail parameters to Imagick so that libjpeg can resize images
 * as they are loaded instead of consuming additional resources to pass back
 * to PHP.
 */
    $fitbyWidth = ($config['width_threshold'] / $width) > ($config['height_threshold'] / $height);
    $aspectRatio = $height / $width;
    if ($fitbyWidth) {
      $im->setSize($config['width_threshold'], abs($width * $aspectRatio));
    } else {
      $im->setSize(abs($height / $aspectRatio), $config['height_threshold']);
    }
    $im->readImage($file_name);

/* Imagick::thumbnailImage(fit = true) has a bug that it does fit both dimensions
 */
//  $im->thumbnailImage($config['width_threshold'], $config['height_threshold'], true);

// workaround:
    if ($fitbyWidth) {
      $im->thumbnailImage($config['width_threshold'], 0, false);
    } else {
      $im->thumbnailImage(0, $config['height_threshold'], false);
    }

    $im->setImageFileName($thumbnail_name);
    $im->writeImage();
  }
  catch (ImagickException $e)
  {
    header('HTTP/1.1 500 Internal Server Error');
    throw new Exception(_('An error occured reszing the image.'));
  }
}

/* cleanup Imagick
 */
$im->destroy();
Steve-o
quelle
9

Aus Ihrer Frage geht hervor, dass Sie ein bisschen neu bei GD sind. Ich werde einige meiner Erfahrungen teilen. Vielleicht ist dies ein wenig abseits des Themas, aber ich denke, es wird für jemanden hilfreich sein, der neu bei GD ist, wie Sie:

Schritt 1, Datei validieren. Verwenden Sie die folgende Funktion, um zu überprüfen, ob die $_FILES['image']['tmp_name']Datei eine gültige Datei ist:

   function getContentsFromImage($image) {
      if (@is_file($image) == true) {
         return file_get_contents($image);
      } else {
         throw new \Exception('Invalid image');
      }
   }
   $contents = getContentsFromImage($_FILES['image']['tmp_name']);

Schritt 2, Dateiformat abrufen Versuchen Sie die folgende Funktion mit der Erweiterung finfo, um das Dateiformat der Datei (Inhalt) zu überprüfen. Sie würden sagen, warum verwenden Sie nicht einfach $_FILES["image"]["type"], um das Dateiformat zu überprüfen? Weil es nur Dateierweiterung prüft nicht den Dateiinhalt, wenn jemand eine Datei ursprünglich genannt umbenennen World.png zu world.jpg , $_FILES["image"]["type"]kehrt jpeg nicht png, so $_FILES["image"]["type"]kann falsches Ergebnis zurück.

   function getFormatFromContents($contents) {
      $finfo = new \finfo();
      $mimetype = $finfo->buffer($contents, FILEINFO_MIME_TYPE);
      switch ($mimetype) {
         case 'image/jpeg':
            return 'jpeg';
            break;
         case 'image/png':
            return 'png';
            break;
         case 'image/gif':
            return 'gif';
            break;
         default:
            throw new \Exception('Unknown or unsupported image format');
      }
   }
   $format = getFormatFromContents($contents);

Schritt 3, GD-Ressource abrufen GD-Ressource aus zuvor vorhandenen Inhalten abrufen :

   function getGDResourceFromContents($contents) {
      $resource = @imagecreatefromstring($contents);
      if ($resource == false) {
         throw new \Exception('Cannot process image');
      }
      return $resource;
   }
   $resource = getGDResourceFromContents($contents);

Schritt 4, Bildgröße abrufen Jetzt können Sie die Bildgröße mit dem folgenden einfachen Code abrufen:

  $width = imagesx($resource);
  $height = imagesy($resource);

Nun wollen wir sehen, welche Variable wir dann vom Originalbild erhalten haben:

       $contents, $format, $resource, $width, $height
       OK, lets move on

Schritt 5, Berechnen von Größenargumenten für Bilder Dieser Schritt bezieht sich auf Ihre Frage. Der Zweck der folgenden Funktion besteht darin, Größenänderungsargumente für die GD-Funktion imagecopyresampled()abzurufen. Der Code ist etwas lang, funktioniert aber hervorragend und bietet sogar drei Optionen: Strecken, Verkleinern , und Fülle.

strecken : Die Dimension des Ausgabebilds entspricht der neuen Dimension, die Sie festgelegt haben. Das Verhältnis von Höhe zu Breite wird nicht eingehalten.

Verkleinern : Die Bemaßung des Ausgabebilds überschreitet nicht die neue Bemaßung, die Sie angeben, und behält das Verhältnis von Bildhöhe zu -breite bei.

Füllen : Die Dimension des Ausgabebilds entspricht der neuen Dimension, die Sie angeben. Bei Bedarf wird das Bild zugeschnitten und in der Größe geändert , und das Verhältnis von Bildhöhe zu -breite wird beibehalten. Diese Option benötigen Sie in Ihrer Frage.

   function getResizeArgs($width, $height, $newwidth, $newheight, $option) {
      if ($option === 'stretch') {
         if ($width === $newwidth && $height === $newheight) {
            return false;
         }
         $dst_w = $newwidth;
         $dst_h = $newheight;
         $src_w = $width;
         $src_h = $height;
         $src_x = 0;
         $src_y = 0;
      } else if ($option === 'shrink') {
         if ($width <= $newwidth && $height <= $newheight) {
            return false;
         } else if ($width / $height >= $newwidth / $newheight) {
            $dst_w = $newwidth;
            $dst_h = (int) round(($newwidth * $height) / $width);
         } else {
            $dst_w = (int) round(($newheight * $width) / $height);
            $dst_h = $newheight;
         }
         $src_x = 0;
         $src_y = 0;
         $src_w = $width;
         $src_h = $height;
      } else if ($option === 'fill') {
         if ($width === $newwidth && $height === $newheight) {
            return false;
         }
         if ($width / $height >= $newwidth / $newheight) {
            $src_w = (int) round(($newwidth * $height) / $newheight);
            $src_h = $height;
            $src_x = (int) round(($width - $src_w) / 2);
            $src_y = 0;
         } else {
            $src_w = $width;
            $src_h = (int) round(($width * $newheight) / $newwidth);
            $src_x = 0;
            $src_y = (int) round(($height - $src_h) / 2);
         }
         $dst_w = $newwidth;
         $dst_h = $newheight;
      }
      if ($src_w < 1 || $src_h < 1) {
         throw new \Exception('Image width or height is too small');
      }
      return array(
          'dst_x' => 0,
          'dst_y' => 0,
          'src_x' => $src_x,
          'src_y' => $src_y,
          'dst_w' => $dst_w,
          'dst_h' => $dst_h,
          'src_w' => $src_w,
          'src_h' => $src_h
      );
   }
   $args = getResizeArgs($width, $height, 150, 170, 'fill');

Schritt 6, Größe ändern Bild Verwenden $args, $width, $height, $formatund $ Ressource , die wir von oben in die folgende Funktion bekam und die neue Ressource des skalierte Bild erhalten:

   function runResize($width, $height, $format, $resource, $args) {
      if ($args === false) {
         return; //if $args equal to false, this means no resize occurs;
      }
      $newimage = imagecreatetruecolor($args['dst_w'], $args['dst_h']);
      if ($format === 'png') {
         imagealphablending($newimage, false);
         imagesavealpha($newimage, true);
         $transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127);
         imagefill($newimage, 0, 0, $transparentindex);
      } else if ($format === 'gif') {
         $transparentindex = imagecolorallocatealpha($newimage, 255, 255, 255, 127);
         imagefill($newimage, 0, 0, $transparentindex);
         imagecolortransparent($newimage, $transparentindex);
      }
      imagecopyresampled($newimage, $resource, $args['dst_x'], $args['dst_y'], $args['src_x'], $args['src_y'], $args['dst_w'], $args['dst_h'], $args['src_w'], $args['src_h']);
      imagedestroy($resource);
      return $newimage;
   }
   $newresource = runResize($width, $height, $format, $resource, $args);

Schritt 7, Neue Inhalte abrufen . Verwenden Sie die folgende Funktion, um Inhalte aus der neuen GD-Ressource abzurufen:

   function getContentsFromGDResource($resource, $format) {
      ob_start();
      switch ($format) {
         case 'gif':
            imagegif($resource);
            break;
         case 'jpeg':
            imagejpeg($resource, NULL, 100);
            break;
         case 'png':
            imagepng($resource, NULL, 9);
      }
      $contents = ob_get_contents();
      ob_end_clean();
      return $contents;
   }
   $newcontents = getContentsFromGDResource($newresource, $format);

Schritt 8 Erweiterung abrufen, Verwenden Sie die folgende Funktion, um die Erweiterung des Bildformats abzurufen (Hinweis: Das Bildformat entspricht nicht der Bilderweiterung):

   function getExtensionFromFormat($format) {
      switch ($format) {
         case 'gif':
            return 'gif';
            break;
         case 'jpeg':
            return 'jpg';
            break;
         case 'png':
            return 'png';
      }
   }
   $extension = getExtensionFromFormat($format);

Schritt 9 Bild speichern Wenn wir einen Benutzer namens mike haben, können Sie Folgendes tun: Er wird im selben Ordner wie dieses PHP-Skript gespeichert:

$user_name = 'mike';
$filename = $user_name . '.' . $extension;
file_put_contents($filename, $newcontents);

Schritt 10 Ressource zerstören Vergessen Sie nicht, GD-Ressource zu zerstören!

imagedestroy($newresource);

oder Sie können Ihren gesamten Code in eine Klasse schreiben und einfach Folgendes verwenden:

   public function __destruct() {
      @imagedestroy($this->resource);
   }

TIPPS

Ich empfehle, das vom Benutzer hochgeladene Dateiformat nicht zu konvertieren, da Sie auf viele Probleme stoßen werden.

Nuss
quelle
4

Ich schlage vor, dass Sie etwas in diese Richtung arbeiten:

  1. Führen Sie eine getimagesize () für die hochgeladene Datei durch, um Bildtyp und -größe zu überprüfen
  2. Speichern Sie hochgeladene JPEG-Bilder mit einer Größe von weniger als 700 x 700 Pixel im Zielordner "wie sie sind".
  3. Verwenden Sie die GD-Bibliothek für mittelgroße Bilder (Codebeispiel finden Sie in diesem Artikel: Ändern der Bildgröße mithilfe von PHP und GD-Bibliothek )
  4. Verwenden Sie ImageMagick für große Bilder. Sie können ImageMagick im Hintergrund verwenden, wenn Sie dies bevorzugen.

Um ImageMagick im Hintergrund zu verwenden, verschieben Sie die hochgeladenen Dateien in einen temporären Ordner und planen Sie einen CRON-Job, der alle Dateien in JPEG "konvertiert" und die Größe entsprechend ändert. Siehe Befehlssyntax unter: imagemagick-Befehlszeilenverarbeitung

Sie können den Benutzer auffordern, dass die Datei hochgeladen und für die Verarbeitung geplant ist. Der CRON-Job kann so geplant werden, dass er täglich in einem bestimmten Intervall ausgeführt wird. Das Quellbild kann nach der Verarbeitung gelöscht werden, um sicherzustellen, dass ein Bild nicht zweimal verarbeitet wird.

Salman A.
quelle
Ich sehe keinen Grund für Punkt 3 - benutze GD für mittelgroße. Warum nicht auch ImageMagick für sie verwenden? Das würde den Code sehr vereinfachen.
TMS
Viel besser als cron wäre ein Skript, das inotifywait verwendet, sodass die Größenänderung sofort beginnt, anstatt auf den Start des cron-Jobs zu warten.
ColinM
3

Ich habe große Dinge über die Imagick-Bibliothek gehört, leider konnte ich sie nicht auf meinem Arbeitscomputer und auch nicht zu Hause installieren (und vertraue mir, ich habe Stunden und Stunden in allen Arten von Foren verbracht).

Nach den Worten habe ich beschlossen, diese PHP-Klasse auszuprobieren:

http://www.verot.net/php_class_upload.htm

Es ist ziemlich cool und ich kann die Größe aller Arten von Bildern ändern (ich kann sie auch in JPG konvertieren).

Alessioalex
quelle
3

ImageMagick ist Multithread-fähig, scheint also schneller zu sein, verbraucht jedoch viel mehr Ressourcen als GD. Wenn Sie mehrere PHP-Skripte parallel mit GD ausführen würden, würden sie ImageMagick für einfache Operationen schneller schlagen. ExactImage ist weniger leistungsfähig als ImageMagick, aber viel schneller, obwohl es nicht über PHP verfügbar ist, müssen Sie es auf dem Server installieren und ausführen exec.

Alasdair
quelle