Эффективное изменение размера изображения JPEG в PHP
Каков наиболее эффективный способ изменения размера больших изображений в 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);
9 answers
Люди говорят, что ImageMagick намного быстрее. В лучшем случае просто сравните обе библиотеки и измерьте это.
- Подготовьте 1000 типичных изображений.
- Напишите два сценария - один для GD, другой для ImageMagick.
- Запустите их оба несколько раз.
- Сравните результаты (общее время выполнения, использование процессора и ввода-вывода, качество изображения результата).
То, что лучше всех остальных, не может быть лучшим для вас.
Кроме того, на мой взгляд, 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();
Из вашего вопроса, похоже, вы немного новичок в 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, вычисление аргументов изображения с измененным размером Этот шаг связан с вашим вопросом, цель следующей функции - получить аргументы изменения размера для функции GDimagecopyresampled()
, код довольно длинный, но он отлично работает, у него даже есть три варианта: растянуть, сжать и заполнить.
растяжка: размер выходного изображения такой же, как у нового заданное вами измерение. Не будет поддерживать соотношение высоты и ширины.
сжиматься: размер выходного изображения не будет превышать заданный вами новый размер, и сохраняйте соотношение высоты и ширины изображения.
заполнить: размер выходного изображения будет таким же, как и новый размер, который вы даете, он будет обрезка и изменение размера изображение, если необходимо, и сохраняйте соотношение высоты и ширины изображения. Этот вариант - то, что вам нужно в вашем вопросе.
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 сохранение изображение Если у нас есть пользователь по имени Майк, вы можете сделать следующее, оно сохранится в той же папке, что и этот php-скрипт:
$user_name = 'mike';
$filename = $user_name . '.' . $extension;
file_put_contents($filename, $newcontents);
Шаг 10 уничтожьте ресурс Не забудьте уничтожить ресурс GD!
imagedestroy($newresource);
Или вы можете записать весь свой код в класс и просто использовать следующее:
public function __destruct() {
@imagedestroy($this->resource);
}
СОВЕТЫ
Я рекомендую не конвертировать формат файла, который загружает пользователь, вы столкнетесь со многими проблемами.
Я предлагаю вам поработать над чем-то в этом роде:
- Выполните getimagesize() для загруженного файла, чтобы проверить тип и размер изображения
- Сохраните любое загруженное изображение JPEG размером менее 700x700 пикселей в папке назначения "как есть"
- Используйте библиотеку GD для изображений среднего размера (пример кода см. в этой статье: Изменение размера изображений с помощью PHP и библиотеки GD)
- Используйте ImageMagick для больших изображений. Вы можете использовать ImageMagick в фоновом режиме, если вы предпочитать.
Чтобы использовать ImageMagick в фоновом режиме, переместите загруженные файлы во временную папку и запланируйте задание CRON, которое "преобразует" все файлы в формат jpeg и соответствующим образом изменяет их размер. См. Синтаксис команд по адресу: imagemagick- обработка командной строки
Вы можете подсказать пользователю, что файл загружен и запланирован для обработки. Задание 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 /. Это также работает для больших поврежденных изображений.