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

82

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

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

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

Выполнение такого рода операции изменения размера с большими изображениями имеет тенденцию к увеличению использования памяти с очень большим запасом (большие изображения могут привести к увеличению использования памяти для сценария выше 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);
maxsilver
источник

Ответы:

45

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

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

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

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

Гжегож Герлик
источник
2
На серверах, с которыми я работал, у GD часто заканчивается ОЗУ и происходит сбой, в то время как ImageMagick никогда этого не делает.
Abhi Beckert
Я не могу больше не согласиться. Работа с imagemagick для меня - кошмар. Я часто получаю 500 ошибок сервера для больших изображений. По общему признанию, библиотека GD вылетела бы раньше. Но все же иногда мы говорим только об изображениях размером 6 МБ, и 500 ошибок - это худшее.
Single Entity
20

Вот отрывок из документации 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

Кевин
источник
Вы знаете, что бы вы поставили для $ dst_x, $ dst_y, $ src_x, $ src_y?
JasonDavis,
Разве нельзя заменить $quality + 1на ($quality + 1)? Как бы то ни было, вы просто изменяете размер с помощью бесполезного дополнительного пикселя. Где проверка на короткое замыкание, когда $dst_w * $quality> $src_w?
Walf
8
Скопировать / вставить из предложенного редактирования: это Тим Экель, автор этой функции. Значение $ quality + 1 является правильным, оно используется для предотвращения появления черной границы шириной в один пиксель, а не для изменения качества. Кроме того, эта функция является подключаемым модулем, совместимым с imagecopyresampled, поэтому вопросы по синтаксису см. В команде imagecopyresampled, она идентична.
Andomar
Чем это решение лучше предложенного в вопросе? вы все еще используете библиотеку GD с теми же функциями.
TMS
1
@Tomas, на самом деле, он тоже использует imagecopyresized(). По сути, он сначала изменяет размер изображения до приемлемого размера ( final dimensionsумножается на quality), а затем передискретизирует его, а не просто передискретизирует полноразмерное изображение. Это может привести к более низкому качеству конечного изображения, но для больших изображений он использует гораздо меньше ресурсов, чем imagecopyresampled()один, поскольку алгоритм передискретизации должен иметь дело только с изображением, размер которого по умолчанию в 3 раза превышает окончательный размер, по сравнению с полноразмерным изображением ( который может быть намного больше, особенно для фотографий, размер которых изменяется для миниатюр).
0b10011
12

phpThumb по возможности использует ImageMagick для повышения скорости (при необходимости возвращается к GD) и, похоже, неплохо кеширует, чтобы снизить нагрузку на сервер. Его довольно легко попробовать (чтобы изменить размер изображения, просто вызовите phpThumb.php с запросом GET, который включает имя графического файла и выходные размеры), поэтому вы можете попробовать его, чтобы увидеть, соответствует ли он вашим потребностям.

фенри
источник
но это не часть стандартного PHP, как кажется ... так что он не будет доступен на большинстве хостингов :(
TMS
1
мне кажется, что это всего лишь php-скрипт, у вас должны быть только php gd и imagemagick
Flo
Это действительно сценарий PHP, а не расширение, которое вам необходимо установить, поэтому он подходит для сред общего хостинга. Я столкнулся с ошибкой «Допустимый размер памяти N байтов исчерпан» при попытке загрузить изображения jpeg <1 МБ с размерами 4000x3000. Использование phpThumb (и, следовательно, ImageMagick) решило проблему, и его было очень легко включить в мой код.
w5m
10

Для больших изображений используйте 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();
Стив-о
источник
9

Судя по вашему вопросу, похоже, что вы новичок в 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, вычисление аргументов измененного размера изображения Этот шаг связан с вашим вопросом, цель следующей функции - получить аргументы изменения размера для функции GD imagecopyresampled(), код довольно длинный, но он отлично работает, у него даже есть три варианта: растяжение, сжатие , и заполните.

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

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

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и $ ресурс , который мы получили из выше в следующей функции и получить новый ресурс измененного изображения:

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

ЧАЕВЫЕ

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

орех
источник
4

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

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

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

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

Салман А
источник
Я не вижу причин для пункта 3 - используйте GD для среднего размера. Почему бы не использовать и для них ImageMagick? Это сильно упростило бы код.
TMS
Намного лучше, чем cron, был бы сценарий, использующий inotifywait, так что изменение размера начнется мгновенно, а не дожидается запуска задания cron.
ColinM
3

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

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

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

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

алессиоалекс
источник
3

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

Alasdair
источник