Для зарегистрированных пользователей |
|
Обработка изображений средствами PHP
PHP приобрел всемирную популярность не только как удобное средство вывода HTML и обработки форм, но и, во многом, благодаря набору расширений, позволяющим выполнять всевозможные стоящие перед веб-разработчиком задачи. Одним из таких расширений является библиотека GD, предназначенная для работы с растровыми изображениями.
В этой статье мы рассмотрим работу со второй версией библиотеки GD.
Сборка
Для успешного выполнения примеров из этой статьи необходимо, чтобы PHP был собран с поддержкой второй версии библиотеки GD.
Для того, чтобы узнать наличие и используемую версию графической библиотеки, воспользуемся всеми нами, без сомнения, любимой функцией phpinfo(), и взглянем на раздел "GD":
gd
GD Support |
enabled |
GD Version |
bundled (2.0.15 compatible) |
FreeType Support |
enabled |
FreeType Linkage |
with freetype |
GIF Read Support |
enabled |
JPG Support |
enabled |
PNG Support |
enabled |
WBMP Support |
enabled |
XBM Support |
enabled |
JIS-mapped Japanese Font Support |
enabled |
Если ничего похожего вы не нашли - значит, PHP собран без поддержки графической библиотеки, и придется его пересобирать (или попросить об этом админа).
В противном случае, сначала взглянем на строку "GD Version". Если версия GD - первая (например, 1.6), то часть материала этой статьи вам не подойдет (и, в любом случае, рекомендую обновиться до второй версии). Вторая же версия тоже может быть разная. :) Обратите внимание на слово "bundled". Его присуствие означает, что PHP собран с библиотекой GD, поставляемой вместе с PHP: именно эту библиотеку рекомендуется использовать, так как разработчики PHP исправили множество ошибок и недочетов, присущих оригинальной GD. Впрочем, если используется внешняя библиотека (слово "bundled" в строке "GD Version" отсутствует) - ничего страшного, все приведенные ниже примеры будут работать.
В нижеследующих строках мы видим, какие графические форматы поддерживаются данной конфигурацией библиотеки GD. В частности, работа с форматом GIF поддерживается в режиме "только чтение" из-за проблем с лицензированием используемого в GIF алгоритма компрессии.
Итак, если требуется пересборка PHP, следует это и сделать, загрузив при необходимости последнюю версию PHP, и указать при сборке параметр:
-with-gd[=location]
Необязательный параметр location указывает путь к внешней библиотеке GD. Если его опустить (в смысле, не указывать) - используется bundled-версия.
Для поддержки работы с шрифтами TrueType, обсуждаемой в главе "Вывод текста...", также понадобится наличие библиотеки FreeType и следующие параметры конфигурационной строки:
-enable-gd-native-ttf -with-freetype-dir=/путь/к/библиотеке/FreeType
Примечание: Для сборки GD с поддержкой GIF Write под FreeBSD, необходимо предварительно установить переменную среды WITH_LZW: export WITH_LZW=yes
Примечание 2:Выше подразумевалось, что используется Unix-подобная ОС. Если же PHP установлен локально, да еще и под Windows - то надо просто раскомментить одну строчку в php.ini - надеюсь, догадаетесь, какую. :)
Генерация изображения с помощью PHP
Ну что ж, со сборкой PHP мы разобрались, и теперь нам не терпится нарисовать и вывести в броузер какую-нибудь картинку с помощью PHP. Надеюсь, вы прекрасно осознаете, что нельзя вот так вот просто взять и вывести картинку посреди HTML-кода в том же самом скрипте (если не осознаете, прочтите внимательно эту статью): в HTML-документе мы разместим, как и обычно, тэг , а в его атрибуте src укажем не картинку, как обычно, а PHP-скрипт:
Теперь приступим к написанию этого самого image.php.
Заголовок. Он же header.
Прежде всего, как броузер узнает, что image.php - это не HTML-документ и не что-то еще, а картинка?
Тип документа броузер определяет по заголовку Content-type. На самом деле, этот заголовок - обязательный и всегда присуствует; по умолчанию, PHP услужливо "отдает" заголовок Content-type: text/html. Обычно это поведение PHP нам прекрасно подходит - но не в данном случае. Придется нам вывести нужный заголовок самим:
header('Content-type: image/png'); ?>
Приступим теперь непосредственно к генерации картинки.
Создание изображения
Для создания изображения, в нашем распоряжении две функции:
1. imagecreate(). С помощью этой функции можно создать изображение на основе палитры, содержащей фиксированный набор цветов. Каждый цвет палитры необходимо описать с помощью функции imagecolorallocate(). Этот способ создания изображения был единственным при работе с первой версией GD, и необходим при работе с ориентированными на палитру форматами, такими как GIF. Однако введенный во второй версии (и отныне рекомендуемый разработчиками) способ, на мой взгляд, гораздо более удобен.
2. imagecreatetruecolor(). Эта функция создает TrueColor-изображение, то есть цвет каждой точки определяется произвольным цветом, задаваемым в координатах RGB. Помимо того, что это удобнее, чем работа с палитрой, такой подход позволяет производить масштабирование изображения с гораздо меньшими потерями качества. Но об этом позже. Сейчас просто создадим изображение:
$image = imagecreatetruecolor(80,60) or die('Cannot create image'); imagedestroy($image); ?>
Функция imagecreatetruecolor (как, кстати, и функция imagecreate), принимает два обязательных целочисленных параметра - ширину (в нашем примере - 80 пикселей) и высоту (60 пикселей) картинки, и возвращает идентификатор ресурса (в данном случае - изображения), который мы присваиваем переменной $image, которой в дальнейшем будем постоянно пользоваться.
Если по какой-либо страшной причине (недостатке памяти, например) создать изображение не получается, функция возвращает false - в этом случае мы прерываем работу скрипта.
Хотя мы с этим изображением, пока что, ничего не делаем, мы обязаны освободить выделенную для него память с помощью функции imagedestroy(): к сожалению, автоматическое освобождение памяти происходит не всегда, и игнорирование рекомендации всегда использовать imagedestroy может привести к весьма серьезным утечкам памяти.
Немножко порисуем
Итак, с помощью функции imagecreatetruecolor() мы создали "труколорное" и, если верить документации, черное изображение размером 80x60. Так что, те, в чьи планы входит создание Web-галереи репродукций "квадрата Малевича", могут смело переходить к следующей главе. :) Для остальных, продолжим. Нарисуем на картинке что-нибудь содержательное.
$image = imagecreatetruecolor(80,60) or die('Cannot create image'); imagefill($image, 0, 0, 0x000080);
imageellipse($image, 40, 30, 50, 50, 0xFFFF00);
imagefilledellipse($image, 30, 20, 10, 10, 0xFFFF00);
imagefilledellipse($image, 50, 20, 10, 10, 0xFFFF00);
imageline($image, 40, 28, 40, 38, 0xFFFF00);
imagearc($image, 40, 30, 40, 40, 45, 135, 0xFFFF00);
header('Content-type: image/png');
imagepng($image);
imagedestroy($image); ?>
Палка, палка, огуречик, вот и вышел человечек :)
Используемые для рисования функции весьма просты для понимания. Их описание (как и описание всех GD-функций) вы найдете здесь. В случае трудностей с английским, просто "поиграйтесь", меняя значения параметров - как вы, несомненно, уже догадались, это ни что иное, как координаты, и цвета в виде 0xRRGGBB.
Подробнее же мы разберем вот эту строку: imagepng($image);
Выше мы создали в памяти изображение, и всячески над ним извращались. :) Это мы делали без привязки к какому-либо выходному формату - просто работали с набором байтов в памяти. А вот функции вида imageформат (imagepng(), imagejpeg(), imagewbmp()...) генерируют на основе этого самого набора байтов, на который ссылается идентификатор $image, картинку в соответствующем формате, и выводят ее в выходной поток - то бишь, проще говоря, в броузер.
Нелишне здесь вспомнить и о строке header('Content-type: image/png'), где мы указали тип документа - обратите внимание: здесь png, и там png. :) Справедливости ради, надо заметить, что большинство броузеров воспринимают только часть "image" этого заголовка, а формат самой картинки уже определяют по ее заголовкам, характерным для каждого формата; однако, лучше все же не надеяться на "интеллект" броузера и указывать правильный формат изображения.
Вывод текста, а также диаграммы и коллекционеры марок
Используя полученные в результате рисования смайликов знания, попробуем порисовать диаграммы, а заодно и научимся выводить на картинках текст (не забудьте только о библиотеке FreeType, о необходимости наличия которой сказано в главе "Сборка".
Предположим, вы располагаете следующей важной статистической информацией о коллекционерах почтовых марок:
$Title = 'Количество марок у моих друзей';
$Data = array(
'Коля' => 16,
'Петя' => 14,
'Федя' => 11,
'Маша' => 17,
'Ипполит' => 8
);
?>
...и горите желанием представить эту информацию в виде "столбиков" - для удобного сравнения. Столбики мы рисовать уже умеем - стоит только немножко вспомнить азы арифметики:
define('GRAPH_WIDTH', 400); define('GRAPH_HEIGHT', 300); define('GRAPH_OFFSET_TOP', 40); define('GRAPH_OFFSET_LEFT', 40); define('GRAPH_OFFSET_RIGHT', 5); define('GRAPH_OFFSET_BOTTOM', 30); $colors = array(0xFF0000,0x00FF00,0x0000FF, 0xFFFF00,0x00FFFF,0xFF00FF);
require('data.php'); $col_width = (GRAPH_WIDTH - GRAPH_OFFSET_LEFT - GRAPH_OFFSET_RIGHT) / count($Data);
$col_maxheight = (GRAPH_HEIGHT - GRAPH_OFFSET_TOP - GRAPH_OFFSET_BOTTOM);
$max_value = max($Data);
$image = imagecreatetruecolor(GRAPH_WIDTH,GRAPH_HEIGHT) or die('Cannot create image'); imagefill($image, 0, 0, 0xFFFFFF); $x = GRAPH_OFFSET_LEFT;
$y = GRAPH_OFFSET_TOP + $col_maxheight;
$i = 0;
foreach($Data as $value) {
imagefilledrectangle( $image,
$x,
$y - round($value*$col_maxheight/$max_value),
$x + $col_width - 1,
$y,
$colors[$i++%count($colors)]
);
$x += $col_width;
}
imageline($image, GRAPH_OFFSET_LEFT - 5, GRAPH_OFFSET_TOP,
GRAPH_OFFSET_LEFT - 5, $y, 0xCCCCCC);
for($value=0; $value<=$max_value; $value++) {
imageline($image, GRAPH_OFFSET_LEFT - 7, $Y = $y - round($value*$col_maxheight/$max_value),
GRAPH_OFFSET_LEFT - 5, $Y, 0xCCCCCC);
imagestring($image, 1, GRAPH_OFFSET_LEFT / 2, $Y - 4, $value, 0x000000);
}
header('Content-type: image/png');
imagepng($image);
imagedestroy($image); ?>
Обратите внимание на строку imagestring($image, 1, GRAPH_OFFSET_LEFT / 2, $Y - 4, $value, 0x000000); с помощью которой мы выводим числа на координатной оси. Второй параметр - один из встроенных в GD шрифтов (от 1 до 5, чем больше число - тем крупнее шрифт).
Так зачем же, скажете вы, нам какие-то там TrueType-шрифты и FreeType-библиотеки, если мы и так прекрасно пишем на картинке? А вот затем, скажу я вам, что писать-то мы хотим по-русски, а встроенные шрифты о существовании кириллицы даже и не подозревают. А нам надо бы подписать столбики именно по-русски. Да и выбор встроенных шрифтов невелик.
Итак, нам понадобится:
- Функция imagettftext(), которая рисует выбранным TrueType-шрифтом на картинке,
- Какой-нибудь кириллический TrueType-шрифт. Возьмем, например, arial.ttf из всеми нами любимой Винды, да не просто возьмем, а положим его туда, где лежит наш скрипт,
- Поскольку функция imagettftext() воспринимает кодировку Unicode, но никак не Windows-1251, то нам пригодится вот такая функция для соответствующего преобразования:
function win2uni($s)
{
$s = convert_cyr_string($s,'w','i'); for ($result='', $i=0; $i<strlen($s); $i++) {
$charcode = ord($s[$i]);
$result .= ($charcode>175)?"".(1040+($charcode-176)).";":$s[$i];
}
return $result;
}
?>
- Функция imagettfbbox(), которая поможет нам вычислить высоту и ширину выводимого шрифтом текста.
Сначала потренируемся:
require('win2uni.php');
define('WIDTH', 200);
define('HEIGHT', 60);
define('FONT_NAME', 'arial.ttf');
define('FONT_SIZE', 20);
$image = imagecreatetruecolor(WIDTH,HEIGHT)
or die('Cannot create image');
$text = win2uni('Всем привет! :)');
$coord = imagettfbbox(
FONT_SIZE, 0, FONT_NAME, $text );
$width = $coord[2] - $coord[0];
$height = $coord[1] - $coord[7];
$X = (WIDTH - $width) / 2;
$Y = (HEIGHT + $height) / 2;
imagettftext(
$image, FONT_SIZE, 0, $X, $Y, 0xFFFFFF, FONT_NAME, $text
);
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
?>
Тренировка прошла успешно - всех поприветствовали. Можно теперь приступать к нашим диаграммам.
define('GRAPH_WIDTH', 400); define('GRAPH_HEIGHT', 300); define('GRAPH_OFFSET_TOP', 40); define('GRAPH_OFFSET_LEFT', 40); define('GRAPH_OFFSET_RIGHT', 5); define('GRAPH_OFFSET_BOTTOM', 30); define('FONT_NAME', 'arial.ttf'); define('FONT_SIZE', 12); $colors = array(0xFF0000,0x00FF00,0x0000FF, 0xFFFF00,0x00FFFF,0xFF00FF);
require('data.php');
require('win2uni.php');
$col_width = (GRAPH_WIDTH - GRAPH_OFFSET_LEFT - GRAPH_OFFSET_RIGHT) / count($Data);
$col_maxheight = (GRAPH_HEIGHT - GRAPH_OFFSET_TOP - GRAPH_OFFSET_BOTTOM);
$max_value = max($Data);
$image = imagecreatetruecolor(GRAPH_WIDTH,GRAPH_HEIGHT) or die('Cannot create image'); imagefill($image, 0, 0, 0xFFFFFF); $x = GRAPH_OFFSET_LEFT;
$y = GRAPH_OFFSET_TOP + $col_maxheight;
$i = 0;
foreach($Data as $name => $value) {
imagefilledrectangle( $image,
$x,
$y - round($value*$col_maxheight/$max_value),
$x + $col_width - 1,
$y,
$colors[$i++%count($colors)]
);
$text = win2uni($name);
$coord = imagettfbbox(FONT_SIZE,0,FONT_NAME,$text);
$text_x = $x + ($col_width - $coord[2] - $coord[0]) / 2;
$text_y = GRAPH_HEIGHT - 5;
imagettftext($image,FONT_SIZE,0,$text_x,$text_y,0x000000,FONT_NAME,$text);
$x += $col_width;
}
$text = win2uni($Title);
$coord = imagettfbbox(FONT_SIZE,0,FONT_NAME,$text);
$text_x = $x + ($col_width - $coord[2] - $coord[0]) / 2;
$text_y = (GRAPH_OFFSET_TOP - $coord[1] - $coord[7]) / 2;
imagettftext($image,FONT_SIZE,0,$text_x,$text_y,0x000000,FONT_NAME,$text);
imageline($image, GRAPH_OFFSET_LEFT - 5, GRAPH_OFFSET_TOP,
GRAPH_OFFSET_LEFT - 5, $y, 0xCCCCCC);
for($value=0; $value<=$max_value; $value++) {
imageline($image, GRAPH_OFFSET_LEFT - 7, $Y = $y - round($value*$col_maxheight/$max_value),
GRAPH_OFFSET_LEFT - 5, $Y, 0xCCCCCC);
imagestring($image, 1, GRAPH_OFFSET_LEFT / 2, $Y - 4, $value, 0x000000);
}
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
?>
Барабанная дробь... Запускаем...
Ура! Получилось! :)
Изменение размера: thumbnails, или "превьюшки"
Часто перед веб-разработчиком стоит задача генерации уменьшенных копий изображений для предварительного просмотра; можно даже сказать, что это - одно из самых распространенных применений библиотеки GD.
В GD1, поддерживающей только изображения на основе палитры, присуствовала лишь функция imagecopyresized(); качество уменьшенных изображений, генерируемых этой ей, мягко говоря, оставляло желать лучшего: работая с фиксированной палитрой, ограниченной 255 цветами, весьма затруднительно обеспечить качественный антиалиасинг.
Во второй версии библиотеки, с появлением поддержки TrueColor и imagecreatetruecolor(), введена новая функция - imagecopyresampled(), обеспечивающая весьма достойное качество "превьюшек".
Продемонстрируем работу с этой функцией. Предположим, у нас есть файл original.jpg, допустим, 400x250 пикселей, и мы хотим создать ее уменьшенный вариант small.jpg - 100x60. Можно поступить так:
define('SOURCE', 'original.jpg'); define('TARGET', 'small.jpg'); define('NEWX', 100); define('NEWY', 60); $size = getimagesize(SOURCE);
if ($size === false) die ('Bad image file!');
$source = imagecreatefromjpeg(SOURCE)
or die('Cannot load original JPEG');
$target = imagecreatetruecolor(NEWX, NEWY);
imagecopyresampled(
$target, $source, 0,0, 0,0, NEWX, NEWY, $size[0], $size[1] );
imagejpeg($target, TARGET, 100);
imagedestroy($target);
imagedestroy($source);
?>
Этот код работает, однако искажения, получаемые при непропорциональном изменении размера, выглядят не особенно симпатично. Более того, код получился не особенно-то универсальным: мало того, что мы можем работать только с JPEG-файлами, у нас еще и жестко заданы имена файлов и размеры получаемого изображения.
Итак, пусть у нас есть файл в любом поддерживаемом GD формате, и мы хотим создать "превьюшку" заданного размера в формате JPEG. Разработаем для этой цели функцию imgResize. Комментариев здесь почти не будет - так как используются уже изученные приемы и обычная арифметика. Постарайтесь разобраться в этом коде самостоятельно.
function img_resize($src, $dest, $width, $height, $rgb=0xFFFFFF, $quality=100)
{
if (!file_exists($src)) return false;
$size = getimagesize($src);
if ($size === false) return false;
$format = strtolower(substr($size['mime'], strpos($size['mime'], '/')+1));
$icfunc = "imagecreatefrom" . $format;
if (!function_exists($icfunc)) return false;
$x_ratio = $width / $size[0];
$y_ratio = $height / $size[1];
$ratio = min($x_ratio, $y_ratio);
$use_x_ratio = ($x_ratio == $ratio);
$new_width = $use_x_ratio ? $width : floor($size[0] * $ratio);
$new_height = !$use_x_ratio ? $height : floor($size[1] * $ratio);
$new_left = $use_x_ratio ? 0 : floor(($width - $new_width) / 2);
$new_top = !$use_x_ratio ? 0 : floor(($height - $new_height) / 2);
$isrc = $icfunc($src);
$idest = imagecreatetruecolor($width, $height);
imagefill($idest, 0, 0, $rgb);
imagecopyresampled($idest, $isrc, $new_left, $new_top, 0, 0,
$new_width, $new_height, $size[0], $size[1]);
imagejpeg($idest, $dest, $quality);
imagedestroy($isrc);
imagedestroy($idest);
return true;
}
?>
Разобрались? Поместим этот код в файл imgresize.php.
Пример использования функции img_resize:
require ('imgresize.php');
if (img_resize('original.jpg', 'small.jpg', 100, 60))
echo 'Image resized OK';
else
echo 'Resize failed!';
?>
|