Миниатюры на PHP
создание миниатюр изображений средствами 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 в URL задает путь к файлу от корневой директории и начинается со знака '/'.
$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-тип, соответствующий файлу. В зависимости от этого типа мы будем вызывать свою функцию для открытия и загрузки изображения: imagecreatefromGIF, imagecreatefromJPEG или imagecreatefromPNG.
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 позволяет выбрать один из двух способов работы с альфа-каналом. Если второй параметр этой функции установлен в true, то при рисовании полупрозрачным цветом новое изображение частично накладывается на старое. Если же он установлен в false, то происходит полное замещение пикселей исходного изображения на пиксели нового. Ясно, что нам нужен именно второй режим. Функция imagesavealpha необходима для того, чтобы при вызове функции imagePNG альфа-канал был сохранен в результирующее изображение.
$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, не будут выводиться в браузер.
Посмотрим на написанный код. Каждый раз при обращении к странице с миниатюрами сервер создает их заново, что при большой посещаемости сайта приведет к значительной нагрузке на сервер. Обычным выходом в данной ситуации является кеширование — сохранение результатов работы скрипта в файлы.
Кеширование динамической информации имеет смысл, если она обновляется существенно реже, чем происходят обращения к странице, на которой она отображается. Есть ситуации, когда не нужно кешировать картинку, генерируемую на лету. Например, счетчик посещений, который для каждой загрузки страницы должен показывать, очевидно, разные значения. Однако в большинстве случаев изображения, для которых создаются миниатюры, изменяются сравнительно редко, в таких случаях кеширование оправданно.
Рассмотрим, как можно сделать кеширование миниатюр. Мы будем сохранять их в файлы с именами, являющимися md5-хешами имен файлов с исходными изображениями, например в папку img_cache в корневой директории веб-сервера.
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 в начале), и поместить его в корневую директорию веб-сервера. Там же нужно создать директорию img_cache и установить права доступа к ней в 777. Всё готово!
Пусть изображение pict.jpg лежит в директории images, то есть его можно вставить на страницу с помощью кода <img src="/images/pict.jpg" ... >. Тогда код, помещающий миниатюру, примет вид <img src="/preview.php?file=/images/pict.jpg&max_size=100"... >.
Разумеется, в этом скрипте можно реализовать и другие возможности. В статье была заложена основа, а остальное зависит от вашей фантазии.