Обработка изображений средствами PHP и GD

Материал из PhpWiki.

(Перенаправлено с Gd)
Перейти к: навигация, поиск

Содержание

Сборка

Для успешного выполнения примеров из этой статьи необходимо, чтобы PHP был собран с поддержкой второй версии библиотеки GD.

Для того, чтобы узнать наличие и используемую версию графической библиотеки, воспользуемся всеми нами, без сомнения, любимой функцией phpinfo(), и взглянем на раздел "GD":

 gd
GD Support enabled
GD Version bundled (2.0.15 compatible)
...

Если ничего похожего вы не нашли - значит, 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

Примечание: Выше подразумевалось, что используется Unix-подобная ОС. Если же PHP установлен локально, да еще и под Windows - то надо просто раскомментить одну строчку в php.ini - надеюсь, догадаетесь, какую. :)

Генерация изображения с помощью PHP

Надеюсь, вы прекрасно осознаете, что нельзя вот так вот просто взять и вывести картинку посреди HTML-кода в том же самом скрипте (если не осознаете, прочтите внимательно эту статью): в HTML-документе мы разместим, как и обычно, тэг <img>, а в его атрибуте src укажем не картинку, как обычно, а PHP-скрипт:

<img src="/i/articles/image/image.php">

Теперь приступим к написанию этого самого image.php.

Заголовок. Он же header.

Прежде всего, как броузер узнает, что image.php - это не HTML-документ и не что-то еще, а картинка?

Тип документа броузер определяет по заголовку Content-type. На самом деле, этот заголовок - обязательный и всегда присуствует; по умолчанию, PHP услужливо "отдает" заголовок Content-type: text/html. Обычно это поведение PHP нам прекрасно подходит - но не в данном случае. Придется нам вывести нужный заголовок самим:

<?php
header('Content-type: image/png'); // устанавливаем тип документа - "изображение в формате PNG".
?>

Приступим теперь непосредственно к генерации картинки.

Создание изображения

Для создания изображения, в нашем распоряжении две функции:

1. imagecreate(). С помощью этой функции можно создать изображение на основе палитры, содержащей фиксированный набор цветов. Каждый цвет палитры необходимо описать с помощью функции imagecolorallocate(). Этот способ создания изображения был единственным при работе с первой версией GD, и необходим при работе с ориентированными на палитру форматами, такими как GIF. Однако введенный во второй версии (и отныне рекомендуемый разработчиками) способ, на мой взгляд, гораздо более удобен.

2. imagecreatetruecolor(). Эта функция создает TrueColor-изображение, то есть цвет каждой точки определяется произвольным цветом, задаваемым в координатах RGB. Помимо того, что это удобнее, чем работа с палитрой, такой подход позволяет производить масштабирование изображения с гораздо меньшими потерями качества. Но об этом позже. Сейчас просто создадим изображение:

<?php
$image = imagecreatetruecolor(80,60) // создаем изображение...
or die('Cannot create image'); // ...или прерываем работу скрипта в случае ошибки
 
imagedestroy($image); // освобождаем память, выделенную для изображения
?>

Функция imagecreatetruecolor (как, кстати, и функция imagecreate), принимает два обязательных целочисленных параметра - ширину (в нашем примере - 80 пикселей) и высоту (60 пикселей) картинки, и возвращает идентификатор ресурса (в данном случае - изображения), который мы присваиваем переменной $image, которой в дальнейшем будем постоянно пользоваться.

Если по какой-либо страшной причине (недостатке памяти, например) создать изображение не получается, функция возвращает false - в этом случае мы прерываем работу скрипта.

Хотя мы с этим изображением, пока что, ничего не делаем, мы обязаны освободить выделенную для него память с помощью функции imagedestroy(): к сожалению, автоматическое освобождение памяти происходит не всегда, и игнорирование рекомендации всегда использовать imagedestroy может привести к весьма серьезным утечкам памяти.

Немножко порисуем

Итак, с помощью функции imagecreatetruecolor() мы создали "труколорное" и, если верить документации, черное изображение размером 80x60. Так что, те, в чьи планы входит создание Web-галереи репродукций "квадрата Малевича", могут смело переходить к следующей главе. :) Для остальных, продолжим. Нарисуем на картинке что-нибудь содержательное.

<?php
 
$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);
 
// Устанавливаем тип документа - "изображение в формате PNG"...
header('Content-type: image/png');
// ...И, наконец, выведем сгенерированную картинку в формате PNG:
imagepng($image);
 
imagedestroy($image); // освобождаем память, выделенную для изображения
 
?>

Палка, палка, огуречик, вот и вышел человечек :)

Используемые для рисования функции весьма просты для понимания. Их описание (как и описание всех GD-функций) вы найдете здесь. В случае трудностей с английским, просто "поиграйтесь", меняя значения параметров - как вы, несомненно, уже догадались, это ни что иное, как координаты, и цвета в виде 0xRRGGBB.

Подробнее же мы разберем вот эту строку: imagepng($image);

Выше мы создали в памяти изображение, и всячески над ним извращались. :) Это мы делали без привязки к какому-либо выходному формату - просто работали с набором байтов в памяти. А вот функции вида imageформат (imagepng(), imagejpeg(), imagewbmp()...) генерируют на основе этого самого набора байтов, на который ссылается идентификатор $image, картинку в соответствующем формате, и выводят ее в выходной поток - то бишь, проще говоря, в броузер.

Нелишне здесь вспомнить и о строке header('Content-type: image/png'), где мы указали тип документа - обратите внимание: здесь png, и там png. :) Справедливости ради, надо заметить, что большинство броузеров воспринимают только часть "image" этого заголовка, а формат самой картинки уже определяют по ее заголовкам, характерным для каждого формата; однако, лучше все же не надеяться на "интеллект" броузера и указывать правильный формат изображения.

Вывод текста

...а также диаграммы и коллекционеры марок.

Используя полученные в результате рисования смайликов знания, попробуем порисовать диаграммы, а заодно и научимся выводить на картинках текст (не забудьте только о библиотеке FreeType, о необходимости наличия которой сказано в главе "Сборка".

Предположим, вы располагаете следующей важной статистической информацией о коллекционерах почтовых марок:

<?php
// Назовем этот файл data.php - он нам еще понадобится.
 
$Title = 'Количество марок у моих друзей';
$Data = array(
'Коля' => 16,
'Петя' => 14,
'Федя' => 11,
'Маша' => 17,
'Ипполит' => 8
);
?>

...и горите желанием представить эту информацию в виде "столбиков" - для удобного сравнения. Столбики мы рисовать уже умеем - стоит только немножко вспомнить азы арифметики:

<?php
 
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'); // Вот и понадобился 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);
}
 
// Устанавливаем тип документа - "изображение в формате PNG"... 
header('Content-type: image/png');
// ...И, наконец, выведем сгенерированную картинку в формате 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, то нам пригодится вот такая функция для соответствующего преобразования:
<?php // Разместим этот код в файле win2uni.php...
 
// Преобразование Windows 1251 -> Unicode
function win2uni($s)
{
$s = convert_cyr_string($s,'w','i'); // преобразование win1251 -> iso8859-5
// преобразование iso8859-5 -> unicode:
for ($result='', $i=0; $i<strlen($s); $i++) {
$charcode = ord($s[$i]);
$result .= ($charcode>175)?"&#".(1040+($charcode-176)).";":$s[$i];
}
return $result;
}
?>
  • Функция imagettfbbox(), которая поможет нам вычислить высоту и ширину выводимого шрифтом текста.

Сначала потренируемся:

<?php
 
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');
 
// Не забываем преобразовать текст в кодировку Unicode
$text = win2uni('Всем привет! :)');
 
$coord = imagettfbbox(
FONT_SIZE, // размер шрифта
0, // угол наклона шрифта (0 = не наклоняем)
FONT_NAME, // имя шрифта, а если точнее, ttf-файла
$text // собственно, текст
);
 
/* Функция imagettfbbox возвращает нам массив из восьми элементов,
содержащий всевозможные координаты минимального прямоугольника,
в который можно вписать данный текст. Индексы массива
удобно обозначить на схеме в виде координат (x,y):
 
(6,7) (4,5)
+---------------+
|Всем привет! :)|
+---------------+
(0,1) (2,3)
 
Число элементов массива может на первый взгляд показаться избыточным,
но не следует забывать о возможности вывода текста под произвольным
углом.
 
По этой схеме легко вычислить ширину и высоту текста:
*/
$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, // координаты (x,y), соответствующие левому нижнему
// углу первого символа
0xFFFFFF, // цвет шрифта
FONT_NAME, // имя ttf-файла
$text
);
 
header('Content-type: image/png');
imagepng($image);
imagedestroy($image);
 
?>

Тренировка прошла успешно - всех поприветствовали. Можно теперь приступать к нашим диаграммам.

<?php
 
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)]
);
 
// Выводим текст:
// .. преобразование в Unicode...
$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. Можно поступить так:

<?php
 
define('SOURCE', 'original.jpg'); // исходный файл
define('TARGET', 'small.jpg'); // имя файла для "превьюшки"
define('NEWX', 100); // ширина "превьюшки"
define('NEWY', 60); // высота "превьюшки"
 
// Определяем размер изображения с помощью функции getimagesize:
$size = getimagesize(SOURCE);
// Функция getimagesize, требуя в качестве своего параметра имя файла,
// возвращает массив, содержащий (помимо прочего, о чем можно прочитать
// в документации), ширину - $size[0] - и высоту - $size[1] -
// указанного изображения. Кстати, для ее использования не требуется наличие
// библиотеки GD, так как она работает непосредственно с заголовками
// графических файлов. В случае, если формат файла не распознан, getimagesize
// возвращает false:
if ($size === false) die ('Bad image file!');
 
// Читаем в память JPEG-файл с помощью функции imagecreatefromjpeg:
$source = imagecreatefromjpeg(SOURCE)
or die('Cannot load original JPEG');
 
// Создаем новое изображение
$target = imagecreatetruecolor(NEWX, NEWY);
 
// Копируем существующее изображение в новое с изменением размера:
imagecopyresampled(
$target, // Идентификатор нового изображения
$source, // Идентификатор исходного изображения
0,0, // Координаты (x,y) верхнего левого угла
// в новом изображении
0,0, // Координаты (x,y) верхнего левого угла копируемого
// блока существующего изображения
NEWX, // Новая ширина копируемого блока
NEWY, // Новая высота копируемого блока
$size[0], // Ширина исходного копируемого блока
$size[1] // Высота исходного копируемого блока
);
 
// Сохраняем результат в JPEG-файле:
// Функции генерации графических файлов, такие как imagejpeg,
// могут выводить результат своей работы не только в броузер,
// но и в файл. Для этого следует указать имя файла в необязательном
// втором параметре.
// Именно функция imagejpeg имеет и третий необязательный параметр -
// качество изображения. Установим максимальное качество - 100.
imagejpeg($target, TARGET, 100);
 
// Как всегда, не забываем:
imagedestroy($target);
imagedestroy($source);
?>

Этот код работает, однако искажения, получаемые при непропорциональном изменении размера, выглядят не особенно симпатично. Более того, код получился не особенно-то универсальным: мало того, что мы можем работать только с JPEG-файлами, у нас еще и жестко заданы имена файлов и размеры получаемого изображения.

Итак, пусть у нас есть файл в любом поддерживаемом GD формате, и мы хотим создать "превьюшку" заданного размера в формате JPEG. Разработаем для этой цели функцию imgResize. Комментариев здесь почти не будет - так как используются уже изученные приемы и обычная арифметика. Постарайтесь разобраться в этом коде самостоятельно.

<?php
/***********************************************************************************
Функция img_resize(): генерация thumbnails
Параметры:
$src - имя исходного файла
$dest - имя генерируемого файла
$width, $height - ширина и высота генерируемого изображения, в пикселях
Необязательные параметры:
$rgb - цвет фона, по умолчанию - белый
$quality - качество генерируемого JPEG, по умолчанию - максимальное (100)
***********************************************************************************/
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;
 
// Определяем исходный формат по MIME-информации, предоставленной
// функцией getimagesize, и выбираем соответствующую формату
// imagecreatefrom-функцию.
$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:

<?php
 
require ('imgresize.php');
if (img_resize('original.jpg', 'small.jpg', 100, 60))
echo 'Image resized OK';
else
echo 'Resize failed!';
 
?>
Ссылки
Реклама