Автоматизированный вывод уменьшенных копий изображений

Создание уменьшенных копий изображений может быть достаточно кропотливой работой, особенно если система управления сайтом не предоставляет для этого соответствующего функционала. Как правило, в таких случаях придется создавать маленькие картинки вручную. Все это влечет за собой увеличение времени на создание или поддержание сайта. Ниже предлагается автоматизировать процесс создания превьюшек.

Для автоматического создания уменьшенных копий будем использовать следующую технологию.

Необходимо определить скрипт, который будет перехватывать управление на вебсервере, когда файл не существует. В этот скрипт надо будет передать размеры уменьшенной копии. Так как у нас будет создаваться реальный файл на диске, то размер уменьшенной копии надо передавать в URL к файлу. Используем для файла http://server/path/file.jpg следующий формат http://server/path/small/WxH/file.jpg, где W и H — ширина и высота уменьшенной копии.

Использование такого формата приводит к тому, что надо будет создать папку small рядом с файлом http://server/path/file.jpg, т.е. в папке /path, в которой находится файл file.jpg. В нее скрипт будет создавать подпапки с именем WxH для копий с заданными размерами, в которые будут уже записываться файлы с именем оригинального файла file.jpg. В связи с этим папка должна быть доступна для записи скриптом. К слову сказать, саму папку small скрипт тоже пытается создать, но лучше создать ее самостоятельно, чтобы не было проблем с записью, т.к. настройки у хостинга могут быть недостаточно либеральны.

Для перехвата управления скриптом, необходимо указать в файле .htaccess следующие строки.

RewriteEngine On
RewriteBase /
RewriteCond  %{REQUEST_FILENAME} !-f
RewriteCond  %{REQUEST_FILENAME} !-d
RewriteRule /small/\d+x\d+/[^/]+$ smallimage.php [L]

Если файл не существует, его нужно создать и разместить в корневой папке сайта.

В корень сайта также разместите файл smallimage.php следующего содержания.

<?php
if(!extension_loaded('gd')) sendStatus404(); //GD не установлена

/*
Константа FORCE_CREATE_SMALL_FILE ипользуется для управления
записью файла уменьшенной копии и может принимать следующие значения:
0 - требовать обязательного создания файла.
	Это может привести к значительному расходу дисковой квоты
    на хостинге, но будет вызывать скрипт для ресайзинга всего
    один раз при первом запросе.
1 - если файл не создается (например из-за запрета записи на диск),
	то просто выдавать файл клиенту.
2 - не писать файл на диск.
	Режимы 1 и 2 могут привести к значительным затратам ресурсов
    сервера	(времени и оперативной памяти),
    что может не понравится хостеру.
*/
define('FORCE_CREATE_SMALL_FILE', 1); 

if(!preg_match('~^(.+)/small/(\d+)x(\d+)/([^/]+)$~',
	$_SERVER['REQUEST_URI'], $m)) sendStatus404();

$O_DIR = $m[1];
$SMALL_W = $m[2];
$SMALL_H = $m[3];
$FILENAME = $m[4];

$source = $_SERVER['DOCUMENT_ROOT'] . $O_DIR . '/' . $FILENAME;
if(!is_file($source)) sendStatus404();

$filename = $_SERVER['DOCUMENT_ROOT'] . $_SERVER['REQUEST_URI'];

$isDir = true;
if(FORCE_CREATE_SMALL_FILE < 2){
	// пробуем создать каталоги
	$small_dir = $_SERVER['DOCUMENT_ROOT'] . $O_DIR . '/small';
	if(!is_dir($small_dir) && !@mkdir($small_dir, 0777)){ 
		if(FORCE_CREATE_SMALL_FILE == 0) sendStatus404($small_dir);
		$isDir = false;
	}
	if($isDir) {
		$small_dir .= '/' . $SMALL_W . 'x' . $SMALL_H;
		if(!is_dir($small_dir) && !@mkdir($small_dir, 0777)){
			if(FORCE_CREATE_SMALL_FILE == 0) sendStatus404();
			$isDir = false;
		}
	}
}

$is = @getimagesize($source);
if(!is_array($is)) sendStatus404();

// смещение маленького изображения внутри области $SMALL_W x $SMALL_H
$off_x = $off_y = 0;
// оригинальные размеры изображения
$W = $is[0]; 
$H = $is[1];
// размеры пропорционально уменьшенного изображения
$w = $SMALL_W;
$h = intval(round($H * $SMALL_W/ $W));
if($SMALL_H > $h){
	$off_y = round(($SMALL_H - $h) / 2);
}
else{
	$w = intval(round($W * $SMALL_H/$H));	
	if($SMALL_W > $w){
		$off_x= round(($SMALL_W - $w)/2);
	}
}

$im = false;
// пробуем открыть файл соотвутствующими функциями
switch($is[2]){
case IMAGETYPE_GIF: 
	if((imagetypes() & IMG_GIF)!=0)
    	$im = imagecreatefromgif($source);
	else sendStatus404(); // библиотека GD не поддерживает GIF
	break;
case IMAGETYPE_JPEG:
	if((imagetypes() & IMG_JPG)!=0)
    	$im = imagecreatefromjpeg($source);
	else sendStatus404(); // библиотека GD не поддерживает JPEG
	break;
case IMAGETYPE_PNG:
	if((imagetypes() & IMG_PNG)==0)	
	$im = imagecreatefrompng($source);
	else sendStatus404(); // библиотека GD не поддерживает PNG
	break;
}
if(!$im) sendStatus404(); //ошибка при открытии файла

$GDVersion = getGDVersion();
// создаем изображение уменьшенной копии
if ($GDVersion >= 2)
	$sim = @imagecreatetruecolor($SMALL_W, $SMALL_H);
else
	$sim = @imagecreate($SMALL_W, $SMALL_H);

if(!$sim) {
	@imagedestroy($im);
	sendStatus404();
}

// задаем фон в формате RGB под уменьшенной копией
// если необходимо укажите свой, исходя из дизайна	
$fill_color = imagecolorallocate ($sim, 255, 255, 255);
// заполняем новое изображение фоновым цветом
imagefilledrectangle($sim, 0, 0,
	$SMALL_W + 1, $SMALL_H + 1, $fill_color);
// копируем с изменением размера
// imagecopyresampled дает лучшие результаты, чем imagecopyresized
//imagecopyresized($sim, $im, $off_x, $off_y, 0, 0, $w, $h, $W, $H);
imagecopyresampled($sim, $im, $off_x, $off_y, 0, 0, $w, $h, $W, $H);

//выводим файл клиенту
switch(FORCE_CREATE_SMALL_FILE){
case 0:
	if(@imagejpeg($sim, $filename, 80)) {
		sendImageHeaders();
		readfile($filename);
	}
	else sendStatus404('', false);
	break;
case 1:
	sendImageHeaders();
	if($isDir && @imagejpeg($sim, $filename, 80)) readfile($filename);
	else @imagejpeg($sim, null, 80);	
	break;
default:
	sendImageHeaders();
	@imagejpeg($sim, null, 80);	
	break;
}
// освобождаем ресурсы
@imagedestroy($im);
@imagedestroy($sim);
exit();

// вспомогательные функции
function sendImageHeaders(){
	sendStatus200();
	header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
	header('Expires: '.gmdate('D, d M Y H:i:s', time()+86400).' GMT');
	header('Content-type: image/jpeg');
}
function sendStatus200(){
	if(defined('CSM_HEADER_IS_SENDED')) return;
	$header = (SAPI_NAME == 'cgi' OR SAPI_NAME == 'cgi-fcgi') ?
		'Status: 200 OK' :
		'HTTP/1.1 200 OK';
	header($header);
	define('CSM_HEADER_IS_SENDED', $header);
}

function sendStatus404($ERROR_TEXT_404 = '', $bExit = true){
    if(!defined('CSM_HEADER_IS_SENDED')){
        $SAPI_NAME = php_sapi_name();
        $header = ($SAPI_NAME == 'cgi' || $SAPI_NAME == 'cgi-fcgi') ?
            'Status: 404 Not Found' :
            'HTTP/1.1 404 Not Found';
        header($header);
        define('CSM_HEADER_IS_SENDED', $header);
    }
    if($ERROR_TEXT_404 !='') print $ERROR_TEXT_404;
    if($bExit) exit();
}

function getGDVersion() {
	static $ver = -1;
	if($ver != -1) return $ver;
	if(function_exists('gd_info')) {
		$gd_info = gd_info();
		if(preg_match('/(\d)/', $gd_info['GD Version'], $m))
        	return $ver = $m[1];
	}
	if(preg_match('/phpinfo/', ini_get('disable_functions')))
    	return $ver = 1;
	ob_start();
    // осторожно! были случаи зависания сервера на этом месте
	phpinfo(INFO_MODULES); 
	$info = ob_get_contents();
	ob_end_clean();
	$info = stristr($info, 'gd version');
	if($info !== false && preg_match('/(\d)/', $info, $m))
    	return $ver = $m[1];
	return $ver = 1;
}
?>

Данный скрипт обрабатывает основные форматы графики веба: GIF, JPEG и PNG. Но при желании можно добавить и другие форматы, поддерживаемые библиотекой обработки изображений GD.

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

Данная технология использует технологии, которые распространены на вебсерверах apache (наиболее распространненый в мире вебсервер), поэтому на других вебсерверах без переделок скорее всего работать не будет.

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