Эффективное изменение размера изображения JPEG в PHP

Каков наиболее эффективный способ изменения размера больших изображений в PHP?

В настоящее время я использую функцию GD imagecopyresampled для получения изображений с высоким разрешением и чистому изменению размеров до размера для веб-просмотра (примерно 700 пикселей в ширину и 700 пикселей в высоту).

Это отлично работает на небольших (менее 2 МБ) фотографиях, и вся операция изменения размера занимает менее секунды на сервере. Тем не менее, сайт в конечном итоге будет обслуживать фотографов, которые могут загружать изображения размером до 10 МБ (или изображения размером до 5000×4000 пикселей).

Выполнение такого рода операции изменения размера с большими изображениями имеет тенденцию увеличивать использование памяти с большим размахом (большие изображения могут увеличивать использование памяти для сценария за 80 МБ). Есть ли способ сделать операцию изменения размера более эффективной? Должен ли я использовать альтернативную библиотеку изображений, например ImageMagick ?

Прямо сейчас код изменения размера выглядит примерно так:

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

Люди говорят, что ImageMagick намного быстрее. В лучшем случае просто сравните обе библиотеки и измерьте это.

  1. Подготовьте 1000 типичных изображений.
  2. Напишите два сценария: один для GD, один для ImageMagick.
  3. Запустите их обоих несколько раз.
  4. Сравните результаты (общее время выполнения, использование процессора и ввода-вывода, качество изображения результата).

Что-то, что лучше всех остальных, не может быть лучшим для вас.

Кроме того, на мой взгляд, ImageMagick имеет гораздо лучший интерфейс API.

Вот фрагмент из документов php.net, которые я использовал в проекте, и отлично работает:

 <? 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 использует ImageMagick, когда это возможно, для скорости (при необходимости возвращаясь к GD) и, похоже, достаточно хорошо кэширует, чтобы уменьшить нагрузку на сервер. Очень легко опробовать (чтобы изменить размер изображения, просто позвоните в phpThumb.php с запросом GET, который включает в себя графическое имя файла и размеры вывода), поэтому вы можете дать ему шанс увидеть, соответствует ли он вашим потребностям.

Для более крупных изображений используйте libjpeg для изменения размера изображения при загрузке изображения в ImageMagick и тем самым значительно уменьшая использование памяти и улучшая производительность, с 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(); 

От вас quesion, кажется, вы новичок в GD, я поделюсь своим опытом, может быть, это немного не по теме, но я думаю, что это будет полезно для кого-то нового для GD, подобного вам:

Шаг 1, проверьте файл. Используйте следующую функцию, чтобы проверить, является ли $_FILES['image']['tmp_name'] действительным файлом:

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

Шаг 2, получите формат файла. Попробуйте следующую функцию с расширением finfo, чтобы проверить формат файла (содержимого). Вы могли бы сказать, почему бы вам просто не использовать $_FILES["image"]["type"] для проверки формата файла? Поскольку он ТОЛЬКО проверяет расширение файла, а не содержимое файла, если кто-то переименует файл, первоначально названный world.png, world.jpg , $_FILES["image"]["type"] вернет jpeg не png, поэтому $_FILES["image"]["type"] может возвращать неправильный результат.

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

Шаг 3. Получить GD-ресурс Получить GD-ресурс из содержимого, которое у нас было до:

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

Шаг 4, размер изображения Теперь вы можете получить размер изображения с помощью следующего простого кода:

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

Теперь давайте посмотрим, какую переменную мы получили от исходного изображения:

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

Шаг 5, вычисление измененных аргументов изображения Этот шаг связан с вашим вопросом, целью следующей функции является получение аргументов resize для функции GD imagecopyresampled() , код длинный, но он отлично работает, он даже имеет три варианта: растягивать, сжимать и заливать.

stretch : размер выходного изображения такой же, как и новый размер, который вы задали. Не будет поддерживать соотношение высоты и ширины.

shrink : размер выходного изображения не будет превышать новое измерение, которое вы даете, и сохраняйте соотношение высоты и ширины изображения.

fill : размер выходного изображения будет таким же, как и новое измерение, которое вы даете, оно будет обрезать и изменить размер изображения, если это необходимо, и сохранить соотношение высоты и ширины изображения. Этот вариант вам нужен в вашем вопросе.

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

Шаг 6, изменение размера изображения Используйте $args , $width , $height , $format и $ resource, которые мы получили сверху, в следующую функцию и получаем новый ресурс измененного размера:

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

Шаг 7, получите новое содержимое. Используйте следующую функцию для получения содержимого из нового ресурса 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); 

Шаг 8 получить расширение. Используйте следующую функцию, чтобы получить расширение из формата изображения (обратите внимание, формат изображения не равен расширению изображения):

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

Шаг 9 Сохранить изображение Если у нас есть пользователь с именем mike, вы можете сделать следующее: он будет сохранен в той же папке, что и этот скрипт php:

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

Шаг 10 уничтожить ресурс Не забудьте уничтожить ресурс GD!

 imagedestroy($newresource); 

или вы можете записать весь свой код в класс и просто использовать следующее:

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

ЧАЕВЫЕ

Я рекомендую не конвертировать формат файла, который загружается пользователем, вы встретите много проблем.

Я предлагаю вам что-то сделать в этом направлении:

  1. Выполните getimagesize () в загруженном файле, чтобы проверить тип и размер изображения
  2. Сохраните любое загруженное изображение JPEG меньше 700x700px в папку назначения «как есть»,
  3. Используйте библиотеку GD для изображений среднего размера (см. Эту статью для примера кода: Изменение размера изображений с использованием библиотеки PHP и GD )
  4. Используйте ImageMagick для больших изображений. Вы можете использовать ImageMagick в фоновом режиме, если хотите.

Чтобы использовать ImageMagick в фоновом режиме, переместите загруженные файлы во временную папку и запланируйте задание CRON, которое «конвертирует» все файлы в jpeg и соответственно изменяет их размеры. См. Командный синтаксис при обработке изображений: imagemagick-command line

Вы можете указать пользователю, что файл загружен и запланирован для обработки. Задачу CRON можно планировать ежедневно с определенным интервалом. Исходное изображение можно удалить после обработки, чтобы гарантировать, что изображение не обрабатывается дважды.

Я слышал большие вещи о библиотеке Imagick, к сожалению, я не мог установить ее на своем рабочем компьютере и ни дома (и, поверьте, я часами работал на всех форумах).

Послесловие, я решил попробовать этот класс PHP:

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

Это довольно круто, и я могу изменять размеры всех видов изображений (я также могу конвертировать их в JPG).

ImageMagick многопоточен, поэтому он работает быстрее, но на самом деле использует намного больше ресурсов, чем GD. Если вы запускали несколько скриптов PHP параллельно, используя GD, они бы быстро побежали ImageMagick для простых операций. ExactImage менее эффективен, чем ImageMagick, но намного быстрее, но не доступен через PHP, вам придется установить его на сервер и запустить через exec .

Для больших изображений используйте phpThumb () . Вот как его использовать: http://abcoder.com/php/problem-with-resizing-corrupted-images-using-php-image-functions/ . Он также работает для больших поврежденных изображений.