fenric
@fenric

Обрезка изображения на лету. Как можно оптимизировать код на PHP?

Есть .htaccess

<IfModule mod_rewrite.c>
	RewriteEngine On
	
	RewriteCond %{REQUEST_FILENAME} \.[0-9]{2,3}x[0-9]{2,3}\.(jpg|jpeg|png|gif)$
	RewriteRule ^(.*)$ image.php [L]
	
	RewriteCond %{REQUEST_FILENAME} !-d
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteRule ^(.*)$ index.php [L]
</IfModule>


Есть image.php

<?php

// Управление протоколированием ошибок
error_reporting(0);

// Запрос клиента
$request = parse_url($_SERVER['REQUEST_URI'])['path'];

// Разбор запроса клиента на части
// Допустимо указывать значения ширина и высота от 10 до 999,
// это сделано для того, чтобы обезопасить сервер от запросов типа: [9999x9999],
// что в свою очередь повлечёт за собой потребление огромнейших ресурсов.
if (preg_match('/(.*?)\.([0-9]{2,3})x([0-9]{2,3})\.(jpg|jpeg|png|gif)$/i', $request, $match))
{
	// Часть пути без расширения, установочные ширина и высота, а также расширение изображения
	list(, $way_part, $set_width, $set_height, $extension) = $match;
	
	// Абсолютный путь к изображению
	if ($path = realpath(__DIR__ . $way_part . '.' . $extension))
	{
		// Реальные ширина и высота изображения
		list($src_width, $src_height) = getimagesize($path);
		
		// Расчёт необходимый для правильного CROP
		$x = $y = 0;
		$crop_width = $src_height * $set_width / $set_height;
		$crop_height = $src_width * $set_height / $set_width;
		
		if ($crop_width < $src_width)
		{
			$x = ($src_width - $crop_width) / 2;
			$src_width = $crop_width;
		}
		else
		{
			$y = ($src_height - $crop_height) / 2;
			$src_height = $crop_height;
		}
		
		// Выполнение всех необходимых операций для создания нового изображения с установочными шириной и высотой, и вывод его в браузер
		switch (strtolower($extension))
		{
			case 'jpg' :
			case 'jpeg' :
				// Формирование ответа информирующего клиента о MIME-типе изображения
				header('Content-Type: image/jpeg');
				
				// Создание нового полноцветного изображения
				// Возвращает идентификатор изображения, представляющий черное изображение заданного размера
				$new = imagecreatetruecolor($set_width, $set_height);
				
				// Копирование и изменение размера изображения с ресемплированием
				// Если координаты, ширина или высота исходного и конечного изображений различны, копируемый фрагмент будет растянут или сжат
				// Координаты отсчитываются от левого верхнего угла изображения
				imagecopyresampled($new, imagecreatefromjpeg($path), 0, 0, $x, $y, $set_width, $set_height, $src_width, $src_height);
				
				// Выводит изображение в браузер
				imagejpeg($new, NULL, 100);
				
				// Уничтожение изображения
				imagedestroy($new);
				break;
				
			// Для остальных расширений я удали участки кода, дабы не мозолить вам глаза...
		}
		
		// Нормальный выход из программы
		exit(1);
	}
}

// Формирование ответа информирующего клиента о 404 ошибке
header('HTTP/1.0 404 Not Found');

// Выход из программы с ошибкой
exit(0);


Собственно исходя из названия поста, и его содержимого могу предположить, что для желающих помочь картина прояснилась...
Всё работает, всё хорошо. Но всё было хорошо до тех пор, пока я вёл разработку локально, после того, как я стал проводить тестирование скрипта на хостинг площадке, выявилось, что изображения отдаются сервером довольно таки долго...
Вы можете использовать этот скрипт локально, он срабатывает если обратится к изображению, допустим 1.jpg не напрямую, а следующим образом 1.100x100.jpg, где 100x100 это необходимый размер...
Мой вопрос звучит следующим образом: есть ли идеи, на тот счёт, как можно оптимизировать данный скрипт, и увеличить скорость отдачи изображения сервером...
Скажу сразу, что к терминалу доступа нету, это простая хостинг площадка, т.е. у нас имеется только PHP...

За раннее выражаю огромную благодарность всем, кто включается в обсуждение.

P.S. К примеру если локально изображение отдаётся за 176 ms, то на хостинг площадке уже за 300... Всё бы ни чего, если оно было такое на странице одно, но их может быть очень много. Наверное многих сразу будет мучить вопрос, почему их заранее не обрезать? Ответ прост, дисковое пространство не резиновое, на данный момент изображений уже порядка 20 гигабайт... В некоторых разделах я позволяю себе делать 2 изображения, оригинальное, и 300х300, чтобы при обрезке "на лету", потреблялось меньше памяти...
  • Вопрос задан
  • 8701 просмотр
Решения вопроса 3
conf
@conf
Ruby developer
Сходу можно предложить 2 способа:
1) Кэшируйте результаты исполнения скрипта, т.е. сохраняйте обрезанные изображения в файл, если искомый файл уже есть, отдавайте его напрямую.
2) Улучшить алгоритм генерацию изображений, например, вместо GD взять ImageMagick, он работает пошустрее, проверьте, есть ли на вашем хостинге модули imagick или magickwand. Код генерации изображений, разумеется, придется переписать.
Ответ написан
Fesor
@Fesor
Full-stack developer (Symfony, Angular)
Только кеширование уже обработанных картинок спасет вас. Настроить RewriteCond так, что бы тот проверял есть ли такой файл. Не экономьте на спичках в виде места на HDD, оно дешевое в отличии от процессорного времени. А так у вас ресурсы и без того не жирного сервера тратятся на бесполезные операции по обрезке картинок.

Конечно же это не спасет в случае если каждый запрос к картинке уникальный (если на вашем фронтэнде силами js вычесляется оптимальный размер картинки для блока и подставляется сссылка), так как при каждом запросе будет генериться новая картинка. Но тут я бы сказал, что проще сделать 2-3 варианта одной картинки и уже силами css воевать на фронтэнде.
Ответ написан
demimurych
@demimurych
Обратите внимание еще на это. При работе с изображениями способом который Вы используете, все изображение для обработки загружается в память в рамках одного процесса. GD очень прожорлив в этом отношении как следствие вы вероятно провоцируете большой обьем дисковых операций (swap), что естественно сказывается на производительности.

Если вы боритесь за производительность, то в вашем случае наиболее оптимальным будет оплата дополнительного дискового прострнаства, и кеширование результатов работы скрипта.
Ответ написан
Пригласить эксперта
Ответы на вопрос 4
miraage
@miraage
Старый прогер
Imagemagick не подходит?
Ответ написан
Opaspap
@Opaspap
Если это не фотохостинг, то можно ресайзить на клиенте (при наличии поддержки в браузере canvas и File Api)

А вообще конечно да, не надо ресайзить на ходу, надо записывать ресайзнутые заранее или при первом обращении (но лучше всё таки при загрузке).
Ответ написан
А вы уверены, что там ищете? Если я верно понял из ветки комментариев, хостинг у вас jino.ru.
Смотрим сюда.
ImageMagick есть в качестве расширения PHP. Кроме того, можно исключить дополнительный слой абстракции и обращаться напрямую к консольной утилите, на которой основано расширение. Обычно она лежит в /usr/bin/convert. Доступ из PHP получается посредством exec(), например.
К какому терминалу доступа у вас нет, тоже непонятно. SSH никто не отменял.
Ответ написан
@SergeyKot
Действительно, как написал Fesor
<Только кеширование уже обработанных картинок спасет вас. Настроить RewriteCond так, что бы тот проверял есть ли такой файл. Не экономьте на спичках в виде места на HDD, оно дешевое в отличии от процессорного времени.


У меня на MobiCot - PHP Mobile Content Management Framework
тест www.cotonti.mobi/page.php?al=test_photo
Это типовая задача. Разбирайтесь, если поможет.

* @author Sergey Kochegarov
 * @copyright (c) 2010 Sergey Kochegarov SoftWare
function преобразовать_картинку //	функция из полученного файла картинки создает имя файла иконки,  ищет файл иконки, если нет подходязщей иконки, то создает новую иконку.
	(
	$файл_картинки,                     //	@param string	файл картинки из которого должно получится фото.
										//					Если файл картинки имеет имя none, или пустое имя, или FALSE, то это значит,
										//					что иконка не нужна - имеет место просто выделение подсказки
	$раздел_размещения_результата,      //  @param string	разздел в который размещается преобразованная картинка
	$высота_картинки=0,                 //  @param int 		высота фото
	$файл_картинки_по_умолчанию=FALSE,	//	@param string	Файл картинки, который будет использован в качестве альтернативного для создания фото - ситуация, когда не подходит файл картинки
	$ширина_картинки=0,                 //  @param int 		ширина фото
	$качество=80,    					//  @param int  	JPEG качество(quality)  %
	$преобразование='height',  			//  @param string	height-по высоте, width - по ширине, frame - вписать размер
	$обрезать=FALSE,
	$поворот=0,          				// @param int		поворот картинки
	$перезаписать=FALSE,     			//                  переписать файл результата
	$минимальная_высота=8,
	$минимальная_щирирна=8,
	$штамп=''							//  @param string
	)
{
if	(empty($файл_картинки)) return array(FALSE);
$имя_файла_картинки = mb_strtolower(basename(mb_substr($файл_картинки, 0, mb_strrpos($файл_картинки, '.'))));
if	(empty($имя_файла_картинки) or $имя_файла_картинки == 'none') return array(FALSE);
if (@file_exists($файл_картинки) and @is_readable($файл_картинки)) $файл_трансформируемой_картинки=$файл_картинки;
	elseif	(!empty($файл_картинки_по_умолчанию) and @file_exists($файл_картинки_по_умолчанию) and @is_readable($файл_картинки_по_умолчанию))    $файл_трансформируемой_картинки = $файл_картинки_по_умолчанию;
		else return array(FALSE);
$имя_файла_трансформируемой_картинки = mb_substr($файл_трансформируемой_картинки, 0, mb_strrpos($файл_трансформируемой_картинки, '.'));
$расширение_файла_трансформируемой_картинки = mb_substr($файл_трансформируемой_картинки, mb_strrpos($файл_трансформируемой_картинки, '.') + 1);
list($файл_результата,$ширина_результата,$высота_результата)=
		mobil_transform_img	(
							$имя_файла_трансформируемой_картинки,
							$расширение_файла_трансформируемой_картинки,
							$раздел_размещения_результата,
							$высота_картинки,$ширина_картинки,
							$качество,
							$преобразование,$обрезать,$поворот,
							$перезаписать,
							$минимальная_высота,$минимальная_щирирна,
							$штамп
							);

return array($файл_результата,$ширина_результата,$высота_результата);
}

function mobil_transform_img//	трансфомировать картинку (иконку)
		(
		$img_big,			//	@param string  	исходный графический файл без расширения
		$ext_img,           //  @param string  	расширение файла
		$path_out_img,      //  @param string  	раздел размещения файла картинки
		$out_img_height=0,  //  @param int 		высота иконки =0, то в пропорцию исходной картинки - width
		$out_img_width=0,   //  @param int 		ширина иконки =0, то в пропорцию исходной картинки - height
		$jpegquality=80,    //  @param int  	JPEG качество(quality)  %
		$type_res='height', //  @param string	height-по высоте, width - по ширине, frame - вписать размер, in_size  сделать по размеру
		$crop=FALSE,        //  @param bool	    обрезка. 0 или TRUE просто обрезка, минус - обрезка снизу или  слева. $type_res указывает сторону которую не трогать при обрезке.
							//					Обрезка производится на указанную величину, но не более размеров $out_img_height или $out_img_width.
							//					frame со всех сторон на указанную величину.
		$rotate=0,          // @param int		поворот картинки
		$rewrite=FALSE,     //                 переписать файл картинки
		$min_out_img_height=8,
		$min_out_img_width=8,
		$stamp=''			//  @param string   не реализовано
		)
{
global $cfg;

if (!function_exists('gd_info')) return FALSE;  //есть графическая библиотека?
if	(empty($out_img_width) and empty($out_img_height)) return FALSE;
$file_big=$img_big.'.'.$ext_img;

if	(mb_strtolower($type_res) == 'none')
	{
	list($out_img_width, $out_img_height) = getimagesize($file_big);
	return array($file_big,$out_img_width,$out_img_height);
	}

if	(empty($type_res)) $type_res='height';
$name_rez=$type_res.'-';
if	(!empty($crop)) $name_rez .="crop-".($crop === TRUE?0:$crop).'-';
if	(!empty($rotate)) $name_rez .="rotate-".($rotate === TRUE?0:$rotate).'-';;
$file_out_img=$path_out_img.$name_rez.$out_img_width.'-'.$out_img_height.'-'.basename($img_big).'.'.$ext_img;  //Получить  имя файла картинки
if (@file_exists($file_out_img) and @is_readable($file_out_img) and !$rewrite) //Существует выходной и читабильный файл картинки?
	{
	list($out_img_width, $out_img_height) = getimagesize($file_out_img);
	return array($file_out_img,$out_img_width,$out_img_height);
	}
elseif($rewrite)
	 {
	if (!(@file_exists($file_big) and @is_readable($file_big))) return FALSE;  //Существует исходный и читaбильный графический файл?
	//проверяем есть ли файл по маске и удаляет похожие
	$del_file=TRUE;
	foreach	($cfg['photo_file_delete'] as $fd=>$filename_no_del)
			{
			if	(is_array($filename_no_del)) continue;
			if	($filename_no_del == (basename($img_big).'.'.$ext_img))	{$del_file=FALSE;break;}
			}
	if ($del_file)
		{
		$cfg['photo_file_delete'][]=(basename($img_big).'.'.$ext_img);
		foreach (glob($path_out_img."*".basename($img_big).".".$ext_img) as $filename) {@unlink($filename);}
		}
     }

list($big_width, $big_height) = getimagesize($file_big);
if	(empty($out_img_width)) $out_img_width=floor($big_width * ($out_img_height/$big_height));
if	(empty($out_img_height)) $out_img_height=floor($big_height * ($out_img_width/$big_width));

$real_out_img_width=$out_img_width;
$real_out_img_height=$out_img_height;

switch($type_res)
	{
 	case 'height':
		{
        //if	(empty($big_height)) $big_height=1;
        $real_out_img_width=floor($big_width * ($out_img_height/$big_height));
		break;}
 	case 'width':
		{
  		$real_out_img_height=floor($big_height * ($out_img_width/$big_width));
		break;}
 	case 'frame':
		{
		if	($big_width/$out_img_width <= $big_height/$out_img_height)  $real_out_img_width=floor($big_width * ($out_img_height/$big_height));
		    else	$real_out_img_height=floor($big_height * ($out_img_width/$big_width));
		break;}
 	case 'in_size':
		{
  		$real_out_img_width=floor($big_height * ($out_img_width/$big_width));
  		$real_out_img_height=floor($big_width * ($out_img_height/$big_height));
		break;}
	default:
		{ return FALSE; }
     }


$cat_out_img_width=0;
$cat_out_img_height=0;
if	($crop === TRUE) $crop=0;
if	($crop !== FALSE)
	{
	switch($type_res)
		{
 		case 'height':
			{
   			if($crop === 0 and $real_out_img_width > $out_img_width) $crop=($real_out_img_width-$out_img_width)/2;
   			$cat_out_img_width=abs(floor($crop*$out_img_height/$big_height));
   			$out_img_width=$real_out_img_width-$cat_out_img_width;
   			if	($out_img_width < $min_out_img_width) $out_img_width=$min_out_img_width;
   			$cat_out_img_width=abs($crop);
            if	($crop > 0) $cat_out_img_width=0;
			break;}
 		case 'width':
			{
			if($crop === 0 and $real_out_img_height > $out_img_height) $crop=($real_out_img_height-$out_img_height)/2;
   			$cat_out_img_height=abs(floor($crop*$out_img_width/$big_width));
   			$out_img_height=$real_out_img_height-$cat_out_img_height;
   			if	($out_img_height < $min_out_img_height) $out_img_height=$min_out_img_height;
   			$cat_out_img_height=abs($crop);
            if	($crop < 0) $cat_out_img_height=0;

			break;}
		case 'in_size':
 		case 'frame':
			{
			$crop=abs($crop);
			$cat_out_img_width=abs(floor(2*$crop*$out_img_height/$big_height));
			$out_img_width=$real_out_img_width-$cat_out_img_width;
 			if	($out_img_width < $min_out_img_width) $out_img_width=$min_out_img_width;

			$cat_out_img_height=abs(floor(2*$crop*$real_out_img_width/$big_width));
 			$out_img_height=$real_out_img_height-$cat_out_img_height;
 			if	($out_img_height < $min_out_img_height) $out_img_height=$min_out_img_height;

 			$cat_out_img_height=$cat_out_img_width=$crop;
			break;}
		default:
			{ return FALSE; }
		}
	}
	else
		{
		$out_img_width=$real_out_img_width;
		$out_img_height=$real_out_img_height;
		}

switch($ext_img)
	{
	case 'gif':
		{$source = imagecreatefromgif($file_big);
		break;}

	case 'png':
		{$source = imagecreatefrompng($file_big);
		break;}
	case 'jpg':
	case 'jpeg':
    	{$source = imagecreatefromjpeg($file_big);
		break;}
	default:
		{ return FALSE; }
}

$rotate= -$rotate;
if ($cfg['th_amode']=='GD1')
	{
	$new = imagecreate($out_img_width, $out_img_height);
	imagecopyresized($new, $source, 0, 0, 0, 0, $out_img_width, $out_img_height, $big_width, $big_height);
	}
	else
	{
	$new = imagecreatetruecolor($out_img_width, $out_img_height);    // $out_img_height (-) обрезвет картинка снизу    $out_img_width (-) обрезает справа
	if	($ext_img  == 'gif')
		{
		$color_tr2 = imagecolortransparent($source);
		$color_ind = imagecolorsforindex($source, $color_tr2);
		$color_tr = imagecolorexact($new, $color_ind['red'], $color_ind['green'], $color_ind['blue']);
		}
		else
			{
			$col = imagecolorallocatealpha($new, 255, 255, 255,127);
			$color_tr = imagecolortransparent($new,$col);
			}
	imagefill($new,0,0,$color_tr);
	imagecopyresampled($new, $source, 0, 0, $cat_out_img_width, $cat_out_img_height, $real_out_img_width, $real_out_img_height, $big_width, $big_height);    // после $source 3 (+)двигает картинку  влево, 4 (+) двигает картинку вверх
	if	($rotate)	$new = imagerotate($new, $rotate, $color_tr);
	imagealphablending($new, FALSE);
	imagesavealpha($new, TRUE);
	}

switch($ext_img)
	{
	case 'gif':
		{imagegif($new, $file_out_img);
		break;}

	case 'png':
		{imagepng($new, $file_out_img);
		break;}

	default:
		{imagejpeg($new, $file_out_img, $jpegquality);
		break;}
	}

$out_img_width=imagesx($new);
$out_img_height=imagesy($new);

imagedestroy($new);
imagedestroy($source);
return array($file_out_img,$out_img_width,$out_img_height);
}
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы