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


82

Який найефективніший спосіб змінити розмір великих зображень у PHP?

В даний час я використовую функцію GD imagecopy, відібрану для зйомки зображень із високою роздільною здатністю та чистого їх розміру до розміру для перегляду в Інтернеті (приблизно 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);

Відповіді:


45

Люди кажуть, що ImageMagick набагато швидший. У кращому випадку просто порівняйте обидві бібліотеки і виміряйте це.

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

Щось, що найкраще всі інші, не може бути найкращим для вас.

Крім того, на мій погляд, ImageMagick має набагато кращий інтерфейс API.


2
На серверах, з якими я працював, GD часто закінчується оперативна пам’ять і виходить з ладу, тоді як ImageMagick ніколи цього не робить.
Абхі Беккерт,

Я не можу не погодитися більше. Я вважаю imagemagick кошмаром для роботи. Я часто отримую 500 помилок сервера для великих зображень. Слід визнати, що бібліотека GD зазнала аварії раніше. Але все ж ми іноді говоримо лише про зображення розміром 6 Мб, і 500 помилок - це просто найгірше.
Одинока особа,

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?
Уолф

8
Копіювати / вставити із запропонованого редагування: Це Тім Еккель, автор цієї функції. Якість $ 1 + 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
Фло

Це дійсно PHP-скрипт, а не розширення, яке потрібно встановити, тому добре для середовищ спільного хостингу. Під час спроби завантажити зображення у форматі jpeg <1 Мб із розмірами 4000x3000 я зіткнувся з помилкою "Дозволений об'єм пам'яті N байт вичерпано". Використання 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 : розмір вихідного зображення такий самий, як і новий розмір, який ви встановили. Не буде дотримуватися співвідношення висота / ширина.

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

заливка : розмір вихідного зображення буде таким самим, як новий розмір, який ви надаєте, він буде обрізати та змінити розмір зображення, якщо потрібно, і збереже співвідношення висоти / ширини зображення. Цей варіант - це те, що вам потрібно у вашому запитанні.

   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-обробка командного рядка

Ви можете підказати користувачеві, що файл завантажується та планується обробляти. Роботу 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.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.