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

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

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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

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

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

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

   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 script:

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

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

imagedestroy($newresource);

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

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

СОВЕТЫ

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

Ответ 6

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

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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