Обработка изображений средствами 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'); // устанавливаем тип документа - "изображение в формате 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);

// Устанавливаем тип документа - "изображение в формате 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, о необходимости наличия которой сказано в главе "Сборка".

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

// Назовем этот файл data.php - он нам еще понадобится.

$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'); // Вот и понадобился 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, то нам пригодится вот такая функция для соответствующего преобразования:

// Разместим этот код в файле 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(), которая поможет нам вычислить высоту и ширину выводимого шрифтом текста.

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

  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);

?>

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

  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. Можно поступить так:

  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. Комментариев здесь почти не будет - так как используются уже изученные приемы и обычная арифметика. Постарайтесь разобраться в этом коде самостоятельно.

/***********************************************************************************
Функция 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:

  require ('imgresize.php');
  if (img_resize('original.jpg', 'small.jpg', 100, 60))
    echo 'Image resized OK';
  else
    echo 'Resize failed!';

?>

 


Страница сайта http://silicontaiga.ru
Оригинал находится по адресу http://silicontaiga.ru/home.asp?artId=6166