81 votes

Redimensionnement efficace des images JPEG en PHP

Quel est le moyen le plus efficace pour redimensionner des images de grande taille en PHP?

Je suis actuellement en utilisant le GD fonction imagecopyresampled de prendre des images en haute résolution, et proprement redimensionner à une taille pour l'affichage sur le web (près de 700 pixels de large sur 700 pixels de haut).

Cela fonctionne très bien sur les petites (moins de 2 MO) les photos et l'ensemble de l'opération de redimensionnement prend moins d'une seconde sur le serveur. Cependant, le site finira par le service des photographes qui peut être télécharger des images jusqu'à 10 MO (ou des images jusqu'à 5000x4000 de pixels de taille).

Faire ce genre d'opération de redimensionnement avec de grandes images, tend à augmenter l'utilisation de la mémoire par une très grande marge (augmentation de la taille des images peut pic de l'utilisation de la mémoire pour le script passé de 80 MO). Est-il possible de faire cette opération de redimensionnement plus efficace? Dois-je utiliser une autre image de la bibliothèque comme ImageMagick?

Maintenant, le code de redimensionnement ressemble à quelque chose comme ceci

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);

45voto

Grzegorz Gierlik Points 6465

Les gens disent que ImageMagick est beaucoup plus rapide. Au mieux, il suffit de comparer les deux bibliothèques et de le mesurer.

  1. Préparez 1000 images typiques.
  2. Ecrivez deux scripts - un pour GD, un pour ImageMagick.
  3. Exécutez les deux à quelques reprises.
  4. Comparez les résultats (temps total d'exécution, utilisation de la CPU et des E / S, qualité d'image des résultats).

Quelque chose que le meilleur tout le monde ne pourrait pas être le meilleur pour vous.

En outre, à mon avis, ImageMagick a une interface API bien meilleure.

19voto

Kevin Points 6567

Voici un extrait de la documentation php.net que j'ai utilisée dans un projet et qui fonctionne bien:

 <?
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

12voto

phenry Points 1574

phpThumb utilise ImageMagick chaque fois que cela est possible pour la vitesse (retombe sur GD si nécessaire) et semble mettre en cache assez bien pour réduire la charge sur le serveur. C'est assez léger à essayer (pour redimensionner une image, appelez simplement phpThumb.php avec une requête GET incluant le nom de fichier graphique et les dimensions de sortie), afin que vous puissiez essayer de voir si cela répond à vos besoins.

10voto

Steve-o Points 9043

Pour les images plus volumineuses, utilisez libjpeg pour redimensionner le chargement des images dans ImageMagick, réduisant ainsi considérablement l'utilisation de la mémoire et améliorant les performances. Ce n'est pas possible avec GD.

 $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();
 

9voto

nut Points 356

De vous quesion, il semble que vous êtes un peu nouveau pour GD, je vais partager quelques experence de la mine, c'est peut-être un peu hors sujet, mais je pense que ce sera utile à quelqu'un de nouveau à GD comme vous:

L'étape 1, valider le fichier. Utiliser la fonction suivante pour vérifier si la variable $_FILES['image']['tmp_name'] fichier est valide fichier:

   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']);

L'étape 2, obtenir le fichier au format Essayez la fonction suivante avec finfo extension de vérifier le format de fichier du fichier(contenu). Vous dirais pourquoi ne pas simplement utiliser $_FILES["image"]["type"] pour vérifier le format de fichier? Parce qu'il SEULEMENT de vérifier l'extension de fichier pas le contenu du fichier, si quelqu'un fait de renommer un fichier appelé à l'origine du monde.png world.jpg, $_FILES["image"]["type"] sera de retour jpeg pas png, donc la variable $_FILES["image"]["type"] peut retourner un résultat incorrect.

   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);

Étape.3, Obtenir GD ressource Obtenir GD ressources de contenu, nous avons avant:

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

L'étape 4, obtenir de l'importance de l'image Maintenant vous pouvez obtenir une image de dimension avec le code simple suivant:

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

Maintenant, voyons ce que la variable que nous avons obtenu à partir de l'image d'origine, puis:

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

L'étape 5, calculer l'image redimensionnée arguments de Cette étape est liée à votre question, le but de la fonction suivante est d'obtenir redimensionner arguments pour GD fonction imagecopyresampled(), le code est un peu long, mais il fonctionne très bien, il a même trois options: s'étirer, se rétrécir, et de le remplir.

tronçon: de l'image de sortie de dimension est la même que la nouvelle dimension que vous définissez. Ne pas garder le rapport hauteur/largeur.

shrink: affichage de l'image est de dimension n'excédera pas la nouvelle dimension que vous donnez, et de garder l'image le rapport hauteur/largeur.

remplissage: de l'image de sortie dimension sera la même que la nouvelle dimension que vous donnez, il va rogner et redimensionner l'image si nécessaire, et de garder l'image le rapport hauteur/largeur. Cette option est ce que vous avez besoin dans votre question.

   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');

L'étape 6, le redimensionnement de l'image , Utilisez $args, $largeur, $hauteur, $de format $et des ressources que nous avons obtenu à partir de ci-dessus dans la fonction suivante et obtenir la nouvelle ressource de l'image:

   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);

Étape 7, d'obtenir de nouveaux contenus, Utiliser la fonction suivante pour obtenir le contenu de la nouvelle GD ressources:

   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);

Étape 8 obtenir d'extension, Utilisez la fonction suivante pour obtenir l'extension de format de l'image(à noter que le format d'image n'est pas égal à l'image de l'extension):

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

Étape 9 enregistrer l'image Si nous avons un utilisateur du nom de mike, vous pouvez effectuer les opérations suivantes, il vous fera gagner le même dossier que ce script php:

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

Étape 10 de détruire des ressources N'oubliez pas de détruire GD ressources!

imagedestroy($newresource);

ou vous pouvez écrire tout le code dans une classe, et il suffit d'utiliser la syntaxe suivante:

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

CONSEILS

Je recommande de ne pas convertir le format de fichier que l'utilisateur de télécharger, vous allez rencontrer beaucoup de problèmes, si vous le voulez vraiment, voir les rubriques suivantes:


Cependant, la façon la plus simple est d'utiliser mon script écrit en Bibliothèque GD: GDEnhancer:

Vous n'avez pas à vous soucier du format de fichier, et vous pouvez enregistrer à n'importe quel format de fichier que vous voulez, le script permet de convertir automatiquement pour vous, il va rendre le canal alpha lorsque convertir GIF en PNG, etc, et vous n'aurez pas de problèmes. Il est également à garder en GIF animé, animation, GD n'est pas.

voici un exemple de code:

<?php
use gdenhancer\GDEnhancer;
include_once '../gdenhancer/GDEnhancer.php'; //path of your GDEnhancer.php
$image = new GDEnhancer($tmp_name);
$image->backgroundResize(170, 150, 'fill'); //option 'shrink' or 'fill'
$save = $image->save();
$user_name = 'mike';
$filename = 'avatars/' . $user_name . '.' . $save['extension'];
file_put_contents($filename, $save['contents']);
?>

Voici la Démo


GD a une forte courbe d'apprentissage, vous avez à apprendre et tester votre code, encore et encore, de plus, il a quelques bugs tho, comme ne prennent pas en charge l'animation GIF et ainsi de suite, vous devez les fixer vous-mêmes :)

Si vous avez plus de questions, il suffit de laisser des commentaires ~

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X