Миниатюры на PHP
В ходе разработки и обслуживания сайтов часто возникает необходимость в создании миниатюр — уменьшенных копий изображений. В тексте страницы вместо большой картинки можно поместить такую копию, являющуюся ссылкой на исходный файл. Работа фотоальбомов и галерей изображений, менеджеров файлов в CMS немыслима без автоматического создания миниатюр. Мы разберем скрипт, генерирующий подобные миниатюры, и на его примере познакомимся с некоторыми возможностями PHP для работы с изображениями.
PHP обрабатывает изображения с помощью библиотеки GD. Почти на всех хостинговых площадках эта библиотека установлена. Если вдруг вам не повезло, обратитесь в техническую поддержку вашего хостинга. В нашем скрипте будем использовать функции, появившиеся в библиотеке GD версии 2.0.1.
Сначала определим требования, которым будет отвечать генератор миниатюр. Он должен создавать уменьшенные копии изображений, сохраненных в форматах GIF, JPEG и PNG. Если исходное изображение обладало прозрачностью, то и миниатюра должна сохранять прозрачность. Также не следует пренебрегать безопасностью и быстродействием.
Мы определились с тем, чего хотим. Теперь можно приступать к программированию. Исходный файл и размеры конечной картинки будем передавать через URL:
$width = isset($_GET['width']) ? (int) $_GET['width'] : 0;
$height = isset($_GET['height']) ? (int) $_GET['height'] : 0;
$max_size = isset($_GET['max_size']) ? (int) $_GET['max_size'] : 0;
$file_name = $_GET['file'];
Для указания размера миниатюры предусмотрим два способа. Можно задать параметр max_size, тогда размеры изображения на выходе будут пропорциональны исходным, но изменятся так, что его можно будет вписать в квадрат размером max_size*max_size. Можно также задавать размер изображения на выходе напрямую в параметрах width и height (при отсутствии одного из них картинка будет сжата пропорционально).
Договоримся, что скрипт будет генерировать миниатюры для файлов, расположенных в корневой директории
$file_name = str_replace('..', '', $file_name);
$file_name = $_SERVER['DOCUMENT_ROOT'] . $file_name;
if (!is_file($file_name)) {
echo 'Ошибка: файл не найден';
exit();
}
Рассмотрим функцию, которая будет выполнять основную задачу скрипта.
function make_thumbnail($file_name, $thumb_width, $thumb_height,
$max_size) {
Для начала получим информацию об исходном изображении:
$image_info = getimagesize($file_name);
Функция getimagesize возвращает массив $image_info, содержащий значения некоторых характеристик изображения:
- $image_info[0] и $image_info[1] — это ширина и высота изображения,
- $image_info[2] — константа вида IMAGETYPE_XXX, определяющая формат файла (например, IMAGETYPE_GIF или IMAGETYPE_JPEG),
- $image_info[3] — строка 'height="yyy" width="xxx"', предназначенная для вставки в тег <img ...>
- $image_info['mime'] —
mime-тип, соответствующий файлу.
В зависимости от
switch ($image_info['mime']) {
case 'image/gif':
if (imagetypes() & IMG_GIF) {
$image = imagecreatefromGIF($file_name);
}
else {
$err_str = 'GD не поддерживает GIF';
}
break;
case 'image/jpeg':
if (imagetypes() & IMG_JPG) {
$image = imagecreatefromJPEG($file_name);
}
else {
$err_str = 'GD не поддерживает JPEG';
}
break;
case 'image/png':
if (imagetypes() & IMG_PNG) {
$image = imagecreatefromPNG($file_name);
}
else {
$err_str = 'GD не поддерживает PNG';
}
break;
default:
$err_str = 'GD не поддерживает ' . $image_info['mime'];
}
if (isset($err_str)) {
return $err_str;
}
Теперь нужно определить размеры миниатюры, если они не заданы явно.
$image_width = imagesx($image);
$image_height = imagesy($image);
//задано ограничение на высоту и ширину:
if ($max_size) {
if ($image_width < $image_height) {
$thumb_height = $max_size;
$thumb_width =
round($max_size * $image_width / $image_height);
}
else {
$thumb_width = $max_size;
$thumb_height =
round($max_size * $image_height / $image_width);
}
}
//задана только ширина
elseif ($thumb_width && !$thumb_height) {
$thumb_height =
round($thumb_width * $image_height / $image_width);
}
//задана только высота
elseif (!$thumb_width && $thumb_height) {
$thumb_width =
round($thumb_height * $image_width / $image_height);
}
//не задан ни один из размеров
else {
$thumb_width = $image_width;
$thumb_height = $image_height;
}
Функция imagecreatetruecolor создает полноцветное изображение указанного в параметрах размера и возвращает его идентификатор. imagealphablending позволяет выбрать один из двух способов работы с
$thumb = imagecreatetruecolor($thumb_width, $thumb_height);
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
Теперь настало время вызвать функцию, которая и выполнит основную задачу этого скрипта. imagecopyresampled копирует прямоугольную область из первого изображения во второе. Она принимает на вход идентификаторы изображений, координаты верхнего левого угла области во втором изображении, координаты верхнего левого угла области в первом изображении, ширину и высоту области во втором изображении и ширину и высоту области во первом. Если размеры областей различны, копируемое изображение растягивается или сжимается. При этом применяется сглаживание, так что качество картинки остается удовлетворительным.
Следует отметить, что есть и другая функция, imagecopyresized, которая похожа на imagecopyresampled. Качество картинки, созданной imagecopyresized, трудно признать удовлетворительным, поскольку эта функция не производит сглаживание, но она работает быстрее. Поэтому в некоторых ситуациях вполне допустимо ее использовать.
imagecopyresampled($thumb, $image, 0, 0, 0, 0,
$thumb_width, $thumb_height, $image_width, $image_height);
Так как мы хотим сохранить прозрачность исходного изображения, миниатюру приходится сохранять в формате PNG. Функция imagePNG выводит в браузер изображение с указанным идентификатором.
imagePNG($thumb);
//освобождаем память
imagedestroy($image);
imagedestroy($thumb);
}
На этом работу над скриптом можно было бы завершить. Действительно, собрав вышеприведенный код в один файл (при этом текст функции необходимо поместить в самом начале) и разместив в конце операторы
//промежуточный вариант!
header('Content-Type: image/png');
make_thumbnail($file_name, $width, $height, $max_size);
мы получим работоспособный скрипт.
Следует отметить, что функция header устанавливает заголовки ответа сервера, и в данном случае она указывает на тип (картинка PNG) возвращаемых данных. Заголовок Content-Type, как и любые заголовки, необходимо устанавливать до операций вывода. Однако при работе с графикой устанавливать этот заголовок нужно как можно ниже, так как сообщения об ошибках, появляющихся после установки Content-Type: image/png не будут выводиться в браузер.
Посмотрим на написанный код. Каждый раз при обращении к странице с миниатюрами сервер создает их заново, что при большой посещаемости сайта приведет к значительной нагрузке на сервер. Обычным выходом в данной ситуации является кеширование — сохранение результатов работы скрипта в файлы.
Кеширование динамической информации имеет смысл, если она обновляется существенно реже, чем происходят обращения к странице, на которой она отображается. Есть ситуации, когда не нужно кешировать картинку, генерируемую налету. Например, счетчик посещений, который для каждой загрузки страницы должен показывать, очевидно, разные значения. Однако в большинстве случаев изображения, для которых создаются миниатюры, изменяются сравнительно редко, в таких случаях кеширование оправдано.
Рассмотрим, как можно сделать кеширование миниатюр. Мы будем сохранять их в файлы с именами, являющимися
define ('IMG_CACHE', $_SERVER['DOCUMENT_ROOT'].'/img_cache/');
//для корректной работы filemtime
clearstatcache();
//имя файла с кешем
$cache_file_name = md5($file_name);
Определим время изменения файла с кешем, если он существует.
$cache_mtime = 0;
if (is_file(IMG_CACHE . $cache_file_name)) {
$cache_mtime = filemtime(IMG_CACHE . $cache_file_name);
}
Сравним время изменения исходного файла с изображением и время изменения файла в кеше. Если изображение менялось после попадания миниатюры в кеш, создается новая миниатюра и обновляется кеш. Если же изображение не менялось, мы просто используем копию миниатюры из кеша. Таким образом, кеш будет поддерживаться в актуальном состоянии.
Наша функция make_thumbnail сразу отправляет миниатюру браузеру. Однако предварительно нужно сохранить миниатюру в кеш. Поэтому мы не должны вызывать функцию make_thumbnail непосредственно, так, как сделали это выше. Нужно перехватить вывод с помощью буферизации. После выполнения участка кода между ob_start и ob_end_clean в переменной $thumbnail будут содержаться те данные, которые функция imagePNG собиралась отправить браузеру, а в переменной $thumb_size — размер данных.
if ($cache_mtime < filemtime($file_name)) {
//буферизация вывода
ob_start();
$result = make_thumbnail($file_name, $width, $height, $max_size);
$thumbnail = ob_get_contents();
$thumb_size = ob_get_length();
ob_end_clean();
if ($result) {
echo 'Ошибка: ' . $result;
exit();
}
//кеширование миниатюры
$fd = fopen(IMG_CACHE . $cache_file_name, "wb");
fwrite($fd, $thumbnail);
fclose($fd);
$cache_mtime = filemtime(IMG_CACHE . $cache_file_name);
}
else {
//загрузка миниатюры из кеша
$fd = fopen(IMG_CACHE . $cache_file_name, "rb");
$thumb_size = filesize(IMG_CACHE . $cache_file_name);
$thumbnail = fread ($fd, $thumb_size);
fclose ($fd);
}
Стоит отметить, что в директории img_cache будут оставаться уменьшенные копии удаленных картинок. Поэтому при масштабном изменении структуры сайта кеш нужно очищать вручную.
Осталось установить заголовки и отправить миниатюру браузеру.
header('Content-Type: image/png');
//время создания миниатюры
header('Last-Modified: '.gmdate('D, d M Y H:i:s', $cache_mtime).' GMT');
header('Content-Length: '.$thumb_size);
//вывод миниатюры в браузер
echo $thumbnail;
Теперь нужно собрать участки кода в один файл, например, preview.php (разместив функцию make_thumbnail в начале) и поместить его в корневую директорию
Пусть изображение pict.jpg лежит в директории images, то есть его можно вставить на страницу с помощью кода <img src="/images/pict.jpg" ... >. Тогда код, помещающий миниатюру, примет вид <img src="/preview.php?file=/images/pict.jpg&max_size=100" ... >.
Разумеется, в этом скрипте можно реализовать и другие возможности. В статье была заложена основа, а остальное зависит от вашей фантазии.
Комментарии
На одном сайте после перехода на ImageMagick количество загружаемых фотографий выросло на 30%. Это как раз те случаи, когда GD падал с ошибками. К тому же качество масштабирования у него оставляет желать лучшего.
В то время как я это делал модуль ImageMagic для PHP был ущербным, поэтому вызывали через командную строку.
$options = '';
$options .= « -crop {$p['crop']['w']}x{$p['crop']['h']}+{$p['crop']['x']}+{$p['crop']['y']}»;
$options .= « -filter Lanczos -thumbnail {$p['w']}x{$p['h']}!»;
$shell_src_file = escapeshellcmd($p['src_file']);
$shell_dst_file = escapeshellcmd($p['dst_file']);
$command = «convert '{$shell_src_file}[0]' {$options} -quality 80 '{$shell_dst_file}'»;
$is_saved = !exec($command);
Я использую описанный мной подход в менеджере картинок в CMS собственного изготовления. Никаких проблем при работе с ним у меня не возникало.
Вполне возможно, что на сайтах, где основной контент — изображения, ваш подход будет работать лучше. С другой стороны, если правильное создание миниатюр критично, то лучший способ — генерить миниатюры при загрузке, сохранять их в файлы и сразу проверять результат.
Функция imagecopyresampled() создает картинки нового размера вполне приемлемого качества. На изображении никаких артефактов не заметно.
Вообще, трудно
Убедитесь, что по адресу [домен]/images/pict.jpg (или
Но есть одна проблема оригинальный файл размером 800х533 и «весом» 282 кб., после сжатия его до размеров 600х400 начинает «весить» — ! 360 кб. ((( Как избежать этого?
imagealphablending($thumb, false);
imagesavealpha($thumb, true);
$file_name = str_replace('..', '', $file_name);
$file_name = $_SERVER['DOCUMENT_ROOT'] . $file_name;
if (!is_file($file_name)) {
echo 'Ошибка: файл не найден';
exit();
}
?>
а если в имени файла 2 точки подряд (например te..st..jpg)? их же вырезает и такого файла не находит (получается имя файла в переменной testjpg, а сам файл не переименовывается).
Если вы хотите обрабатывать такие имена файлов, замените str_replace('..', ", $file_name) на
вот можно глянуть тут :)
Что только не предлагали делать с этой прозрачностью на фоновом слое в топиках инета. Вот извините за выражение …ы. Нихера незнают, а в топе по запросу.
Я уже даже подумывал использовать вместо GD создания фонового слоя, прозрачную 1 пиксельную картинку, увеличивать ее до нужного размера и совмещать с источником.
Да и по вопросу так сказать кеша «динамических картинок» — идея неплохая. Я думал сделать
Спасибо огромное.
Оставьте свой комментарий