Pergunta

Qual é a maneira mais eficiente de redimensionar imagens grandes em PHP?

Atualmente estou usando o GD função imagecopyresampled para capturar imagens de alta resolução e redimensioná-las de forma limpa para um tamanho para visualização na web (aproximadamente 700 pixels de largura por 700 pixels de altura).

Isso funciona muito bem em fotos pequenas (menos de 2 MB) e toda a operação de redimensionamento leva menos de um segundo no servidor.No entanto, o site eventualmente atenderá fotógrafos que possam enviar imagens de até 10 MB (ou imagens de até 5000x4000 pixels).

Fazer esse tipo de operação de redimensionamento com imagens grandes tende a aumentar o uso de memória em uma margem muito grande (imagens maiores podem aumentar o uso de memória do script além de 80 MB).Existe alguma maneira de tornar esta operação de redimensionamento mais eficiente?Devo usar uma biblioteca de imagens alternativa, como ImagemMagick?

No momento, o código de redimensionamento se parece com isto

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);
Foi útil?

Solução

As pessoas dizem que o ImageMagick é muito mais rápido.Na melhor das hipóteses, basta comparar as duas bibliotecas e medir isso.

  1. Prepare 1000 imagens típicas.
  2. Escreva dois scripts - um para GD, um para imagemagick.
  3. Execute os dois algumas vezes.
  4. Compare os resultados (tempo total de execução, CPU e uso de E/S, qualidade da imagem resultante).

Algo que é melhor para todos, não poderia ser o melhor para você.

Além disso, na minha opinião, o ImageMagick tem uma interface API muito melhor.

Outras dicas

Aqui está um trecho da documentação do php.net que usei em um projeto e funciona bem:

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

phpThumb usa o ImageMagick sempre que possível para obter velocidade (recorrendo ao GD se necessário) e parece armazenar em cache muito bem para reduzir a carga no servidor.É muito leve de testar (para redimensionar uma imagem, basta chamar phpThumb.php com uma consulta GET que inclui o nome do arquivo gráfico e as dimensões de saída), então você pode tentar para ver se ele atende às suas necessidades.

Para imagens maiores, use libjpeg para redimensionar no carregamento da imagem no ImageMagick e, assim, reduzir significativamente o uso de memória e melhorar o desempenho, o que não é possível com 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();

De você, parece que você é meio novo para o GD, vou compartilhar uma experiência minha, talvez isso seja um pouco fora do tópico, mas acho que será útil para alguém novo no GD como você:

Etapa 1, validar o arquivo. Use a seguinte função para verificar se o $_FILES['image']['tmp_name'] arquivo é um arquivo válido:

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

Etapa 2, obter o formato do arquivo Tente a seguinte função com extensão finfo para verificar o formato do arquivo (conteúdo).Você diria por que simplesmente não usa $_FILES["image"]["type"] verificar o formato do arquivo?Porque isso APENAS verifique a extensão do arquivo e não o conteúdo do arquivo, se alguém renomear um arquivo chamado originalmente mundo.png para mundo.jpg, $_FILES["image"]["type"] retornará jpeg e não png, então $_FILES["image"]["type"] pode retornar resultado errado.

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

Etapa 3, obter recurso GD Obtenha recursos GD dos conteúdos que temos antes:

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

Etapa 4, obtenha a dimensão da imagem Agora você pode obter a dimensão da imagem com o seguinte código simples:

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

Agora, Vamos ver qual variável obtivemos da imagem original:

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

Etapa 5, calcule os argumentos da imagem redimensionada Esta etapa está relacionada à sua pergunta, o objetivo da função a seguir é obter argumentos de redimensionamento para a função GD imagecopyresampled(), o código é meio longo, mas funciona muito bem, tem até três opções:esticar, encolher e preencher.

esticar:a dimensão da imagem de saída é igual à nova dimensão que você definiu.Não manterá a relação altura/largura.

encolher:a dimensão da imagem de saída não excederá a nova dimensão fornecida e manterá a relação altura/largura da imagem.

preencher:a dimensão da imagem de saída será a mesma que a nova dimensão que você forneceu, será cortar e redimensionar imagem, se necessário, e mantenha a relação altura/largura da imagem. Esta opção é o que você precisa em sua pergunta.

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

Etapa 6, redimensionar a imagem Usar $args, $width, $height, $format e $resource que obtivemos acima na seguinte função e obtemos o novo recurso da imagem redimensionada:

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

Etapa 7, obtenha novos conteúdos, Use a seguinte função para obter o conteúdo do novo recurso GD:

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

Etapa 8 obter extensão, Use a seguinte função para obter a extensão do formato da imagem (observe que o formato da imagem não é igual à extensão da imagem):

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

Etapa 9 salvar imagem Se tivermos um usuário chamado mike, você pode fazer o seguinte, ele será salvo na mesma pasta deste script php:

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

Etapa 10 destruir recurso Não se esqueça de destruir o recurso GD!

imagedestroy($newresource);

ou você pode escrever todo o seu código em uma classe e simplesmente usar o seguinte:

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

PONTAS

Eu recomendo não converter o formato de arquivo carregado pelo usuário, você encontrará muitos problemas.

Sugiro que você trabalhe algo nesse sentido:

  1. Execute um getimagesize( ) no arquivo enviado para verificar o tipo e tamanho da imagem
  2. Salve qualquer imagem JPEG enviada menor que 700x700px na pasta de destino "como está"
  3. Use a biblioteca GD para imagens de tamanho médio (consulte este artigo para obter um exemplo de código: Redimensionar imagens usando PHP e biblioteca GD)
  4. Use ImageMagick para imagens grandes.Você pode usar o ImageMagick em segundo plano, se preferir.

Para usar o ImageMagick em segundo plano, mova os arquivos carregados para uma pasta temporária e agende um trabalho CRON que "converta" todos os arquivos para JPEG e os redimensione de acordo.Veja a sintaxe do comando em: processamento de linha de comando imagemagick

Você pode avisar ao usuário que o arquivo foi carregado e programado para ser processado.A tarefa CRON pode ser agendada para execução diária em um intervalo específico.A imagem de origem pode ser excluída após o processamento para garantir que a imagem não seja processada duas vezes.

Já ouvi grandes coisas sobre a biblioteca Imagick, infelizmente não consegui instalá-la no meu computador de trabalho e nem em casa (e acredite, passei horas e horas em todos os tipos de fóruns).

Posteriormente, decidi tentar esta classe PHP:

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

É muito legal e posso redimensionar todos os tipos de imagens (também posso convertê-las para JPG).

O ImageMagick é multithread, então parece ser mais rápido, mas na verdade usa muito mais recursos que o GD.Se você executasse vários scripts PHP em paralelo, todos usando GD, eles superariam o ImageMagick em velocidade para operações simples.ExactImage é menos poderoso que ImageMagick mas muito mais rápido, embora não esteja disponível através de PHP, você terá que instalá-lo no servidor e executá-lo exec.

Para imagens maiores use phpThumb().Aqui está como usá-lo: http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/.Também funciona para grandes imagens corrompidas.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top