Статья описывает методику сложного форматирования изображений на PHP под нужный формат независимо от их начальных размеров и ориентации, но всегда выдаёт изображения строго определённого размера. В скрипте используются функции обработки изображений библиотеки GD. Скрипт будет полезен для новостных сайтов и фотогалерей.
Работая над новостным сайтом, я столкнулся с проблемой хранения картинок к статьям в двух форматах – мелкий (90 на 90 для заголовочных блоков) и крупный (218 на сколько есть). Новости хранились в базе и добавлялись очень просто. С картинками выходило сложнее. Исходные изображения с цифровой фотокамеры (или цифрового макета бумажной газеты) загружались в PhotoShop и обрабатывались дизайнером под нужный формат. Уже сам факт ручной работы с графикой заставил задуматься об автоматизации. Кроме того, приходилось локально сохранять две картинки, а потом закачивать их через форму по отдельности, да чтоб ещё и не перепутать, где какая! Возникает избыточность, а размер занимаемого места становился всё больше и больше.
С самого начала работы над проектом я знал, что существует такая вещь, как ImageMagick для Perl. Запустить этот чудо-модуль мне так и не удалось. Потом я перешёл на PHP, и понял, что Perl не совсем приспособлен для таких вещей. На PHP тоже можно формировать картинки с помощью модуля GD. Он хоть и попроще будет, но функции масштабирования в нём есть, что и требовалось.
Постановка задачи – написать скрипт, получающий путь исходного изображения и выводил бы данные image/jpeg – картинку нужного формата (см. описание протокола HTTP).
Входные параметры:
$f – имя файла в виде path/file.jpg. Можно было придумать более защищённый способ адресации, но суть алгоритма не меняется. Например, предавать только номер статьи из базы, а скрипт сам бы определил местонахождение картинки;
$q – качество сжатия JPEG;
$type – формат картинки (в смысле линейные размеры) где в зависимости от значения$type производятся следующие действия:
$type = 0 – из исходной картинки вырезается квадратная серединка и приводится к формату 90x90 пикселей;
$type = 1 – исходная картинка масштабируется пропорционально и приводится к ширине 218 пикселей (опционально наносится надпись);
$type = 2 – исходная картинка выводится без изменений.
Посмотрим начало скрипта:
resize.php
<?php // f - имя файла // type - способ масштабирования // q - качество сжатия // src - исходное изображение // dest - результирующее изображение // w - ширниа изображения // ratio - коэффициент пропорциональности // str - текстовая строка // тип преобразования, если не указаны размеры if ($type == 0) $w = 70; // квадратная 70x70 if ($type == 1) $w = 90; // квадратная 90x90 if ($type == 2) $w = 218; // пропорциональная шириной 218 // качество jpeg по умолчанию if (!isset($q)) $q = 100;
Здесь как обычно проверяются входные параметры. Вы, конечно, можете задать более жёсткие условия проверки. Смотрим дальше.
// создаём исходное изображение на основе // исходного файла и опеределяем его размеры $src = imagecreatefromjpeg($f); $w_src = imagesx($src); $h_src = imagesy($src); header("Content-type: image/jpeg");
В этой части программы мы загружаем исходное изображение в переменную $src. Функцииimagesx и imagesy определяют размеры исходной картинки и записывают их в соответствующие переменные. Они нам понадобятся для вычисления коэффициента пропорциональности. Здесь же с помощью функции header передаём заголовок Content-type:image/jpeg в браузер пользователя. После этого он ожидает, что следующий поток данных будет jpeg-файлом.
Следующее условие отвечает за размер выводимой картинки и непосредственно за вывод. Возможно, что исходная картинка уже нужного размера. Если это не так, обработаем её. Для этого поставим следующее условие:
// если размер исходного изображения // отличается от требуемого размера if ($w_src != $w) {
Следующая часть программы будет подгонять картинку под ширину 218 пикселей (пропорциональное уменьшение) для случая $type=1.
// операции для получения прямоугольного файла if ($type==2) { // вычисление пропорций $ratio = $w_src/$w; $w_dest = round($w_src/$ratio); $h_dest = round($h_src/$ratio); // создаём пустую картинку // важно именно truecolor!, иначе будем иметь 8-битный результат $dest = imagecreatetruecolor($w_dest,$h_dest); $str = "foxweb.net.ru"; imagecopyresized($dest, $src, 0, 0, 0, 0, $w_dest, $h_dest, $w_src, $h_src);
Вначале вычисляем коэффициент $ratio, чтобы сразу вычислить размеры результирующей картинки $w_dest и $h_dest. Потом создаём новую картинку $dest с таким же размерами, это заготовка для масштабированной картинки. Самая сложная часть алгоритма – функцияimagecopyresized. В ней я указал результирующую и исходную картинку, координаты левого верхнего угла для обоих картинок (0, 0, 0, 0) и координаты правого нижнего угла для обоих картинок ($w_dest, $h_dest, $w_src, $h_src). Вот официальная справка на эту функцию:
imagecopyresized - копирует и изменяет размеры части изображения
int imagecopyresized ( resource dst_im, resource src_im, int dstX, int dstY, int srcX, int srcY, int dstW, int dstH, int srcW, int srcH)
imagecopyresized() копирует прямоугольную часть одного изображения в другое изображение. Dst_im - результирующее изображение, src_im - исходное изображение. Если исходные и координаты расположения и ширины и высот отличаются, будет выполнено растяжение или сжатие фрагмента изображения. Начало координат - верхний левый угол. Эта функция может быть использована, чтобы копировать области в пределах того же изображения (если dst_im и src_im совпадают), но если области перекрывают результаты быть непредсказуемым.
Вместо неё можно использовать imagecopyresampled() - ресемплирование со сглаживанием. Картинка получится более качественной. Подробнее о параметрах функции можно узнать в документации на сайте http://php.net/imagecopyresized/ .
Следующая часть кода совершенно необязательна. Она печатает произвольную надпись на готовой картинке. Причём, контрастным цветом: на тёмном фоне надпись будет белая, на светлом – чёрная. Сначала определим координаты вывода текста (в нашем примере это внизу справа) и три цвета – $white, $black и $gray с помощью функции imagecolorallocate. Чтобы определить, каким цветом печатать надпись, сравним цвет точки (функция imagecolorat), где будет надпись, со «средним» цветом $gray. Если цвет точки окажется светлее серого, то надпись будет чёрного цвета ($color = $black). Если цвет точки окажется темнее серого, то надпись будет белого цвета ($color = $white). Наконец определяем строку $str и наносим её функцией imagestring, указав размер шрифта, координаты, строку и цвет. Помните, что эта функция выводит только латинские символы одним шрифтом. Хотя никто не запрещает вам пользоваться TrueType шрифтами, благо такие функции есть.
// определяем координаты вывода текста $size = 2; // размер шрифта $x_text = $w_dest-imagefontwidth($size)*strlen($str)-3; $y_text = $h_dest-imagefontheight($size)-3; // определяем каким цветом на каком фоне выводить текст $white = imagecolorallocate($dest, 255, 255, 255); $black = imagecolorallocate($dest, 0, 0, 0); $gray = imagecolorallocate($dest, 127, 127, 127); if (imagecolorat($dest,$x_text,$y_text)>$gray) $color = $black; if (imagecolorat($dest,$x_text,$y_text)<$gray) $color = $white; // выводим текст imagestring($dest, $size, $x_text-1, $y_text-1, $str,$white-$color); imagestring($dest, $size, $x_text+1, $y_text+1, $str,$white-$color); imagestring($dest, $size, $x_text+1, $y_text-1, $str,$white-$color); imagestring($dest, $size, $x_text-1, $y_text+1, $str,$white-$color); imagestring($dest, $size, $x_text-1, $y_text, $str,$white-$color); imagestring($dest, $size, $x_text+1, $y_text, $str,$white-$color); imagestring($dest, $size, $x_text, $y_text-1, $str,$white-$color); imagestring($dest, $size, $x_text, $y_text+1, $str,$white-$color); imagestring($dest, $size, $x_text, $y_text, $str,$color); }
Несколько сложнее выглядит часть кода для получения квадратного фрагмента.
// операции для получения квадратного файла if (($type==0)||($type==1)) { // создаём пустую квадратную картинку // важно именно truecolor!, иначе будем иметь 8-битный результат $dest = imagecreatetruecolor($w,$w); // вырезаем квадратную серединку по x, если фото горизонтальное if ($w_src>$h_src) imagecopyresized($dest, $src, 0, 0, round((max($w_src,$h_src)-min($w_src,$h_src))/2), 0, $w, $w, min($w_src,$h_src), min($w_src,$h_src)); // вырезаем квадратную верхушку по y, // если фото вертикальное (хотя можно тоже серединку) if ($w_src<$h_src) imagecopyresized($dest, $src, 0, 0, 0, 0, $w, $w, min($w_src,$h_src), min($w_src,$h_src)); // квадратная картинка масштабируется без вырезок if ($w_src==$h_src) imagecopyresized($dest, $src, 0, 0, 0, 0, $w, $w, $w_src, $w_src); } // вывод картинки и очистка памяти imagejpeg($dest,'',$q); imagedestroy($dest); imagedestroy($src); }
Мы не определяем коэффициент масштабирования, поскольку картинка будет квадратной заранее известного размера. По размеру картинка может быть трёх типов – горизонтальная, вертикальная и квадратная. В первых двух случаях нам придётся вырезать и масштабировать квадратную область из прямоугольной картинки. В третьем – просто масштабировать.
Немного поясню конструкцию: round((max($w_src, $h_src) – min($w_src, $h_src)) / 2). Это выражение вычисляет координату x или y верхнего левого угла квадратного блока (без разницы для горизонтальной или вертикальной картинки). Это видно на рисунках.
Горизонтальное изображение
Вертикальное изображение
Обычно в горизонтальных картинках (лица, пейзажи, объекты) наиболее информативна центральная часть, а в вертикальных (чаще всего это стоящие люди) - наиболее важна верхняя часть. Чтобы изменить вырезаемый фрагмент вертикальной картинке, строку:
imagecopyresized($dest, $src, 0, 0, 0, 0, $w, $w, min($w_src,$h_src), min($w_src,$h_src));
можно заменить на
imagecopyresized($dest, $src, 0, 0, 0, round((max($w_src,$h_src)-min($w_src,$h_src))/2), $w, $w, min($w_src,$h_src), min($w_src,$h_src));
Тогда в вертикальной картинке будет браться не верхняя часть, а средняя (это зависит от расположения объектов на фотографии). Точно так же можно брать нижнюю часть или правую/левую часть горизонтальной картинки – поэкспериментируйте с координатами. Особенно этот метод пригодится, если нужно создать квадратные «превьюшки» (thumbnails) в одной таблице.
В конце выводим картинку imagejpeg() и очищаем память imagedestroy().
Чтобы вам не приходилось лихорадочно менять входные переменные в процессе отладки на локальном сервере, предлагаю демо-страничку, которая загружает четыре картинки разных форматов. Тестовые исходные картинки должны храниться в текущей директории под именамиimage1.jpg и image2.jpg. Здесь же должен находиться файл resize.php, который на выводе выдаёт jpeg-изображение.
resize.htm
<html> <head> <title></title> </head> <body> <h3 align="center">Демонстрационный пример масштабирования картинок</h3> <table align="center"> <tr align="center" valign="top"> <td>90*90<br><img src="resize1.php?f=av.jpg&t=1&w=90&s=0" border=0></td> <td>90*90<br><img src="resize.php?f=news114.jpg&type=1" border=0></td> </tr> <tr align="center" valign="top"> <td>218*x<br><img src="resize.php?f=av.jpg&type=2" border=0></td> <td>218*x<br><img src="resize.php?f=news114.jpg&type=2" border=0></td> </tr> </table> </body> </html>
Этот скрипт достаточно прост, он всего лишь формирует страницу, ссылки, осуществляет всю навигацию по каталогу картинок. Вся работа по загрузке картинок производится черезresize.php. Его можно взять за основу простого веб-альбома.
preview.php
<html> <head> <title></title> </head> <body> <?php // читаем список jpg-файлов в массив list $list = array(); if ($dir = opendir('.')) // успешное открытие текущей директории { // перебираем именя файлов, полученные readdir while (false !== ($file = readdir($dir))) { // отбираем только .jpg, отсекаем ссылки '.' и '..' if ($file != "." && $file != ".." && preg_match("/.jpg/i", $file)) { $list[] = $file; // пишем имя файла в массив $i++; // счётчик файлов, нужен для определения границ массива } } closedir($dir); // закрываем директорию } // переменные по умолчанию, полученные через GET if ($f == '') $f = $list[0]; // если файл не указан, берём первый из списка if ($num == '') $num = 0; // номер запрошенного файла if ($type == '') $type = 1; // тип по умолчанию "средний" // заголовочек - имя файла echo "<p>$list[$num]</p>\n"; // определяем ссылку с картинки анализируя type // щёлкая по картинке файл будет перезагружаться с новым type if ($type == 0) $link = "href=\"?type=1&num=$num\" title=\"показать 90\""; if ($type == 1) $link = "href=\"?type=2&num=$num\" title=\"показать 218\""; if ($type == 2) $link = "href=\"?type=full&num=$num\" title=\"показать весь\""; if ($type == "full") $link = "href=\"?type=0&num=$num\" title=\"показать 70\""; echo "<a $link><img src=\"resize.php?f=$list[$num]&type=$type\" border=0></a>"; // определяем, крайние ли элементы списка $num1 = $num-1; if ($num1<0) $num1 = $i-1; // если num вышел за 0, переводим его на 0 $num2 = $num+1; if ($num2>$i-1) $num2 = 0; // если num вышел за i, переводим его на i-1 // ссылки навигации echo "<br><a href=\"?type=$type&num=$num1\">пред</a> <a href=\"?type=$type&num=$num2\">след</a>"; ?> </body> </html>
Думаю, что скрипт достаточно хорошо комментирован, потому объясню лишь суть. Вначале происходит загрузка имён jpg-файлов в текущем каталоге в массив $list. Потом загружаем картинку с нужными параметрами из скрипта resize.php, причём на картинке «висит» ссылка на её увеличенную копию (изменяется параметр $type). В конце формируем ссылки на следующую и предыдущую картинку. Вот и всё. В качестве тестирования накидайте в каталог скрипта два десятка фотографий и вы получите простенькую «смотрелку». Навигацию, конечно, можно усложнить, но тогда скрипт уже будет «сложненьким».
Я отлаживаю скрипты в Windows, поэтому объясню решение проблемы именно для этой ОС. Скорее всего, причина неработоспособности скрипта в отсутствии модуля php_gd.dll (или более новый php_gd2.dll). Откройте директорию с интерпретатором PHP, создайте директорию extensions, если её нет. Найдите вышеуказанный модуль – он есть в полных дистрибутивах PHP. Пропишите в php. ini (лежит в c:\ windows) строчку extension = php_gd2.dll (а заодно иextension_dir = "c:\php\extensions\", если такой нет). Пути могут быть другими. После этого всё должно заработать. Не «смешивайте» модули php_gd.dll и php_gd2.dll – интерпретатор будет ругаться на дублирование функций в библиотеках.
Вот такие интересные вещи можно сделать при помощи пары графических функций – зато какая экономия на изготовлении и хранении дублирующих картинок. Не PhotoShop конечно, но для массового применения это самое то. Картинок на сайте очень много, качественно их выводить необязательно, главное чтобы быстро и просто. Скрипт получился не очень универсальный, но с моей задачей он справляется прекрасно. Следует отметить тонкую разницу между функциями imagecopyresized и imagecopyresampled. Первая просто изменяет размер картинки без сглаживания, она работает быстро, но результат – большие искажения. Вторая функция – ресемплирование, изменение размеров со сглаживанием. Работает она намного медленнее, но картинка получается чистой, как в профессиональных графических пакетах. О ресемплировании можно почитать здесь. Выбирать способ следует исходя из ваших потребностей – быстрее или качественнее. Возможно, вы реализуете другие идеи: задавать произвольный размер x и y, наличие или отсутствие надписи, формат выходного файла, рисование рамочки, водяных знаков, подписи на картинке шрифтами TrueType, вывод картинки-заглушки в случае ошибки.
Если кто заметил какие-то глупые или смешные места в программах, жду ваших комментариев.
Сайт, для которого это всё изначально разрабатывалось – http://58region.ru/. Правда там используется усложнённый вариант – resize.php работает с тремя типами файлов, выводит надпись по желанию и выводит картинку-заглушку в случае отсутствия исходного файла.