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

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

В настоящее время я использую функцию imagecopyresampled GD, чтобы брать изображения с высоким разрешением и аккуратно уменьшать их размер до размера для просмотра в Интернете (примерно 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);
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
82
0
86 169
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

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

но это не часть стандартного PHP, как кажется ... поэтому он не будет доступен на большинстве хостингов :(

TMS 10.08.2011 23:21

мне кажется, что это всего лишь скрипт php, у вас должны быть только php gd и imagemagick

Flo 15.11.2011 19:44

Это действительно сценарий PHP, а не расширение, которое вам нужно установить, поэтому он подходит для сред общего хостинга. Я столкнулся с ошибкой «Допустимый размер памяти в N байтов исчерпан» при попытке загрузить изображения в формате jpeg.

w5m 08.02.2013 17:06

Вот фрагмент документации 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 15.08.2009 02:54

Разве вы не должны заменить $quality + 1 на ($quality + 1)? Как бы то ни было, вы просто изменяете размер с помощью бесполезного дополнительного пикселя. Где проверка на короткое замыкание, когда $dst_w * $quality> $src_w?

Walf 18.02.2011 06:05

Скопируйте / вставьте из предложенного редактирования: это Тим Экель, автор этой функции. Значение $ quality + 1 является правильным, оно используется, чтобы избежать черной границы шириной в один пиксель, а не изменить качество. Кроме того, эта функция является подключаемым модулем, совместимым с imagecopyresampled, поэтому вопросы по синтаксису см. В команде imagecopyresampled, она идентична.

Andomar 19.02.2011 19:13

чем это решение лучше предложенного в вопросе? вы все еще используете библиотеку GD с теми же функциями.

TMS 10.08.2011 23:25

@Tomas, по словам Тима Экеля, это как, в этом примере используются функции GD, что имеет значение.

Xeoncross 14.03.2012 23:58

@Tomas, на самом деле, он тоже использует imagecopyresized(). По сути, он сначала изменяет размер изображения до приемлемого размера (final dimensions, умноженный на quality), тогда передискретизирует его, а не просто передискретизирует полноразмерное изображение. Это может приводит к более низкому качеству конечного изображения, но он использует гораздо меньше ресурсов для больших изображений, чем только imagecopyresampled(), поскольку алгоритм передискретизации должен иметь дело только с изображением, размер которого по умолчанию в 3 раза больше конечных размеров, по сравнению с полноразмерным изображением. (который может быть на далеко больше, особенно для фотографий, размер которых изменяется для миниатюр).

0b10011 14.06.2012 20:50

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

Alvaro 07.04.2016 16:04

Он всегда возвращает черное изображение для меня, протестировано только на изображениях> 3 МБ

Single Entity 26.08.2016 00:52
Ответ принят как подходящий

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

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

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

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

На серверах, с которыми я работал, у GD часто заканчивается ОЗУ и происходит сбой, в то время как ImageMagick никогда этого не делает.

Abhi Beckert 11.07.2013 15:04

Я не могу больше не согласиться. Я считаю imagemagick кошмаром для работы. Я часто получаю 500 ошибок сервера для больших изображений. По общему признанию, библиотека GD вылетела бы раньше. Но все же иногда мы говорим только об изображениях размером 6 МБ, и 500 ошибок - это худшее.

Single Entity 26.08.2016 00:28

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

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

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

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

Я не вижу причин для пункта 3 - используйте GD для среднего размера. Почему бы не использовать и для них ImageMagick? Это сильно упростило бы код.

TMS 10.08.2011 23:23

Намного лучше, чем cron, был бы сценарий, использующий inotifywait, так что изменение размера начнется мгновенно, а не дожидается запуска задания cron.

ColinM 12.07.2012 23:26

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

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

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

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

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

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

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

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

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

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

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

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

ЧАЕВЫЕ

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

Другие вопросы по теме