Обработка изображений средствами PHP и GD
Материал из PhpWiki.
Содержание |
Сборка
Для успешного выполнения примеров из этой статьи необходимо, чтобы 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". ?>
Приступим теперь непосредственно к генерации картинки.
Арендуйте бокс для хранения вещей аренда бокса для хранения вещей сао. chemicals-el.ru
Создание изображения
Для создания изображения, в нашем распоряжении две функции:
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!'; ?>
original.jpg
small.jpg