Навигация
Автор: Ильдар Шайморданов © 2006
Интернет позволяет пользователям публиковать собственные графические изображения, например, созданные с помощью компьютера, или фотографии, выполненные цифровыми камерами. Чаще всего эти работы выполнены на высоком художественном уровне и такие файлы значительны по размеру (как геометрически, так и в объеме хранимой информации). Современные серверные технологии позволяют создать уменьшенные копии изображений для их предварительного просмотра.
В англоязычной литературе для обозначения уменьшенных копий оригиналов существуют два термина - preview и thumbnail. Ни одно их не имеет адекватного перевода в русскоязычной литературе кроме более близкого по смыслу термина - миниатюра. В дальнейшем, в данной статье будет использоваться именно это слово и производные от него (с целью исключения повторов в тексте) либо близкие по смыслу выражения.
Все способы создания миниатюр предполагают выполнение предварительных арифметических операций для вычисления начальной точки копирования фрагмента оригинального изображения, размеров копируемой области. На протяжении данной работы будут использоваться следующие обозначения (выраженные в пикселях):
Основное место использования миниатюр - тематически структурированные коллекции и фото-галереи, где на главных страницах разделов выводятся уменьшенные копии изображений со ссылкой на оригинал. Для более подробного ознакомления с работой пользователь может посмотреть полную версию оригинала.
Размер миниатюры устанавливается администратором фото-галереи и имеет фиксированный размер, соответствующий общему дизайну. В настоящее время существует два наиболее распространенных способа создания миниатюр:
Каждый из способов имеет свои достоинства и недостатки. Например, миниатюры, созданные по первому способу позволяют увидеть миниатюрную копию цельного изображения, но могут испортить общий дизайн вследствие различной ориентации изображений (книжная или альбомная). А второй способ не дает полного представления о представленном изображении.
Однако можно выделить еще один, третий способ создания миниатюр, который можно представить как комбинацию первых двух. В данной статье будут рассмотрены все возможные способы.
Дополнительно, в противовес традиционным методам создания миниатюрных копий фиксированного размера, выраженного в пикселях, будет рассмотрено пропорциональное сжатие, выраженное в процентах от геометрического размера оригинального изображения. Будет показано, что этот способ не может рассматриваться как полноценный метод создания миниатюр, но имеет право на существование в определенных случаях.
По-видимому, не существует терминов, обозначающих способы создания миниатюр (по крайней мере, автор статьи их не обнаружил, кроме существующих терминов - масштабирование и обрезка), поэтому в статье будет использовать собственные. Эта терминология не претендует быть общепринятой, однако если это случится - автор будет рад.
Используемый термин метод максимального сжатия означает сжатие большей стороны оригинала, так чтобы он был вписан полностью в размеры предлагаемой миниатюры. Для этого необходимо:
На псевдокоде это будет описано так:
if W > H
then
height = H * width / W
else
width = W * height / H
end if
Таким образом, будут определены размеры будущей копии. Это необходимо, для создания уменьшенной копии, которая будет вписана в заданные рамки миниатюры без искажений. Дальнейшие действия заключаются в использовании соответствующих графических средств на стороне сервера по преобразованию изображений. Следующий пример иллюстрирует преобразование, реализованное на PHP (в предположении, что исходное изображение уже загружено в программу - переменная $srcImage, и его размеры получены в $width и $height):
Описание данного метода завершим рисунком, иллюстрирующим данный подход.
Рисунок 1. Метод максимального сжатия
Вырезать фрагмент технически достаточно простой способ - все-таки вся работа заключается лишь в копирования части изображения. Однако эстетическая сторона явно не соблюдена. В лучшем случае, если программистом заложена возможность - позволить пользователям выбрать подходящий фрагмент для показа на миниатюре. В худшем - будет создана копия, например, центральной части. Тем не менее техническую сторону мы рассмотрим. Так же рассмотрим вариант выбора фрагмента.
Следующий код на PHP иллюстрирует данный подход:
При всем многообразии начальных координат источника копирования автор считает, что источник копии может иметь только девять фиксированных позиций:
Соответственно, можно говорить о вертикальном и горизонтальном выравнивании. Алгоритм вычисления координат источника описан на псевдокоде:
if horizontal align = left
then
X = 0
else if horizontal align = right
X = W - width
else
X = (W - width) / 2
end if
if vertical align = top
then
Y = 0
else if vertical align = bottom
Y = H - height
else
Y = (H - height) / 2
end if
Рисунок 2. Фрагмент целого
Рассмотрим вертикально расположенное изображение, которое необходимо вписать в квадрат. Если метод максимального сжатия, предполагает, что необходимо сжать большую сторону на столько, чтобы она полностью была вписана в соответствующую сторону миниатюры, то для реализации этого метода необходимо сжать меньшую, чтобы добиться требуемого результата.
Рисунок 3. Метод минимального сжатия
В общем случае, одна из сторон большого прямоугольника должна быть вписана в соответствующую сторону малого. При этом коэффициент сжатия (масштабирования) должен быть минимален. Этого легко добиться сравнивая соотношения соответствующих сторон - большее соотношение определяет сторону, которое будет обрезано при масштабировании.
Ww = W / width
Hh = H / height
if Ww > Hh
then
W = width * Hh
X = ...
else
H = height * Ww
Y = ...
end if
Здесь используется алгоритм вычисления начальных координат источника фрагмента, идентичный описанному ранее алгоритму.
Так как одна из сторон большего прямоугольника будет полностью вписана в соответствующую сторону меньшего, а другая - обрезана, то здесь тоже можно говорить о выравнивании - или вертикальном, или горизонтальном.
Последний способ создания миниатюр сложно отнести к действительно полноценному методу, однако он тоже требует своего рассмотрения. Причина кроется в том что, это фактически метод максимального сжатия и метод вырезания фрагмента целого, выраженные в пропорциональном уменьшении геометрических размеров изображения, но выраженное в процентах. Этот способ логично использовать в двух случаях:
При этом следует иметь в виду, что пропорции относятся к соответствующим сторонам исходного изображения и результирующего.
В этом разделе будет подробно рассмотрен реализованный на PHP в рамках объектно-ориентированного похода модуль Thumbnail на основе описанного алгоритма.
Основным публичным методом является метод output(). В своей работе он использует результат работы метода render(), определяет приемник для вывода изображения (браузер или файл на сервере):
Обязательный аргумент функции - имя файла исходного изображения. По умолчанию, метод выводит результирующее изображение в браузер. Если будет указан второй аргумент - имя выходного файла, будет создан файл. Третий, аргумент метода, ассоциативный массив, содержит необязательные параметры обработки изображений. По умолчанию, используются собственные установки - методом максимального сжатия создается миниатюра в формате PNG размером 150х150 пикселей.
Логика класса по созданию миниатюр реализована в методе render(), который загружает файл изображения в память, определяет размеры исходного изображения, рассчитывает размеры будущей миниатюрной копии, создает новое изображение и возвращает ссылку на него в случае успешного выполнения. При этом данный метод использует вспомогательные методы для загрузки объекта и расчета необходимых переменных:
Данный метод учитывает особенности работы PHP с графическими изображениями - при отсутствии функций библиотеки GD для работы с изображениями высокого качества imagecreatetruecolor и imagecopyresampled используются их менее качественные аналоги imagecreate и imagecopyresized.
При необходимости, можно указать аргумент $options (ассоциативный массив параметров - третий аргумент метода output() и второй - метода render()) для реализации собственной обработки изображений. Для этого необходимо лишь указать требуемые опции
Никто не застрахован от ошибок, поэтому модуль реализует минимальную обработку типовых ошибок (неверный формат входных данных, невозможность инициализации графического формата) посредством генерации пользовательских сообщений встроенной функцией user_error.
Ошибки передачи некорректных опций отсутствуют. По мнению автора, эти ошибки должны обрабатываться и корректироваться на уровне приложения до передачи их в модуль.
Эти методы необходимы для загрузки изображения и расчета координат и размеров области копирования исходного и результирующего изображений.
Методы загрузки изображения из файла - основное их назначение определить источник изображения (файл, строка или образ изображения в памяти)
Метод расчета координат и размеров области копирования исходного и результирующего изображений
Вся логика класса реализована в методе render(), который загружает файл изображения в память, определяет размеры исходного изображения, рассчитывает размеры будущей миниатюрной копии и возвращает ссылку на новое изображение в случае успешного выполнения. При этом данный метод использует вспомогательные методы для загрузки объекта и расчета необходимых переменных:
В модуле предопределено несколько констант для управления методом создания миниатюр и выравнивания изображений
Пример 1. Простое создание миниатюры в браузер
include_once 'includes/Thumbnail.php'; $filename = 'files/image.jpg'; Thumbnail::output($filename);
Пример 2. Простое создание миниатюры в файл
include_once 'includes/Thumbnail.php'; $input_file = 'files/image.jpg'; $output_file = 'files/thumb.jpg'; Thumbnail::output($input_file, $output_file);
Пример 3. Использование собственных параметров (JPEG, 200х200 пикселей)
include_once 'includes/Thumbnail.php';
$filename = 'files/image.jpg';
$options = array(
'width' => 200,
'height' => 200,
'type' => IMAGETYPE_JPEG,
);
Thumbnail::output($filename, null, $options);
По умолчанию, из любого изображения создается маленькое изображение в формате PNG. Указывая явно тип можно изменить формат графического потока (в примере - JPEG). Для вывода в браузер второй аргумент установлен в значение null.
Пример 4. Каскадное создание миниатюры из очень большого изображения
Последний пример стоит разобрать более подробно.
Вначале из исходного изображения вырезается центральный прямоугольник, стороны которого составляют ровно половину оригинала (50%). Затем, за второй проход из полученного промежуточного изображения создается обычная уменьшенная копия (стандартные размеры 150х150 пикселей). После этого полученное окончательное изображение отображается в браузере.
Хотя задача создания миниатюр не самая сложная задача, решаемая разработчиками интернет-приложений, тем не менее, она не всегда решается на должном уровне и в полном объеме. В настоящей статье были разобраны все варианты создания миниатюр, кратко рассмотрены особенности работы с изображениями в PHP.
Методы загрузки и создания реализуют работу с изображениями только в форматах PNG и JPEG. Однако модуль расширяем, и добавить отсутствующую функциональность будет достаточно легко.
Как ни странно, но в богатой коллекции PEAR нет полноценных классов или методов для работы с миниатюрами, хотя существуют пакеты, реализующие отдельные методы обработки изображений (обрезки, масштабирования, поворота и т.д.) Однако описанный модуль может быть с легкостью интегрирован.
Исходные коды и демонстрационные файлы представлены в архиве на googlecode. На странице sourceforge старая (слегка подправленная) копия статьи со ссылками на примеры.
Автор искренне признателен Илье Лебедеву за реальную помощь в публикации данной статьи.
Дискуссия
очень плохое качество сжатия.
как исправить?
Возможно Ваша версия PHP не поддерживает функцию imagecopyresampled. Возможно надо производить сжатие поэтапно. Возможно Вы пытаетесь сжать очень большое изображение.
Уточните, пожалуйста - Что значит плохое качество?
2 метод на мой взгляд не очень хорош. На практике столкнулся с тем, что пользователи зачастую грузят изображения с размерами порядка 3000 на 4000 px. А превью нам надо получить 50х50. В итоге на изображении вообще не поймёшь ничего.
Я использую третий метод с небольшим (на мой взгляд) улучшением - для вертикально ориентированных изображений я помещаю вырезаемую область чуть выше (так, чтобы сверху оставалось 1/3 удаляемого, а снизу - 2/3) - обычно значимая часть изображения находится ближе к верху.
Другой способ вычисления размеров изображения - вырезание квадрата (или прямоугольника) из случайного места. Но чтобы было, откуда вырезать, изображение уменьшяется до размеров больших, чем превью, раза в три.
Например, есть картинка 1000 на 10000 px, нужно случайное превью 50х50 px. Уменьшаем исходное изображение так, чтобы меньшая сторона его стала 50*3=150px, а большая соответственно 1500. И из этой области уже случайно вырезаем нужный нам квадратик. Так мы будем уверены, что на нём будет что-то осмысленное.
Хорошее замечание. Согласен с Вами, что очень большие изображения дадут трудноразличимые мелкие картинки. Попробуйте каскадный метод:
Хотя примерно тоже самое делаете и Вы.
Вообще существует черновик второй версии, как статьи так и библиотеки (маленький класс вырос до целой библиотеки, только GD). Но необходимо время на шлифовку. Веб-разработка в настоящее время - хобби. А на хобби времени не хватает.
Возник вопрос. В моем случае мне необходимо в обязательном порядке получить миниатюру строго заданного размера, поскольку на экран она выдается еще и с указанными в html параметрами width и height. Таким образом, если размер полученной миниатюры отличается от заданных, на экране она все равно изуродуется, даже если само изображение будет пропорциональным. Для себя ввел следующие принципы: 1.Произвожу первичное сравнение размеров изображения с заданными. 2.Вычисляю Ww и Hh. 3. Нахожу «коэффициент ужаса» -параметр, равный отношению max(Ww, Hh)/min(Ww, Hh). На мой взгляд этот параметр определяет степень несоответствия исходного и заданного изображений. Если этот коэффициент превышает 1.25 ,считаю, что без обрезки на экране в моем случае начнется ужас. 4. В этом случае перехожу к предварительной обрезке исходного изображения. Для этого умножаю заданные размеры на min(Ww, Hh). То есть фактически определяю рамку, одна из сторон которой надежно впишется в исходное изображение. Теперь эта рамка должна встать по центру и обрезать исходное изображение. 5.Обрезанный вариант уменьшаю обычным путем и сохраняю в файл. Так, на мой взгляд, должно получиться более-менее приемлемое решение проблемы.
Теперь вопрос: как можно с помощью библиотеки произвести предварительную обрезку исходного изображения по заданной рамке, центрировав ее относительно исходного изображения. Пожалуйста, если можно-код. В общем смысле я и сам понимаю:) Спасибо! Извините за туповатый вопрос.
Очень огорчило что ссылка http://phpmrtgadmin.sourceforge.net/download/demo/thumbnail/ не работает.
Видимо на sourceforge запретили просмотр каталога. Изменил заключение статьи - добавил правильную ссылку на рабочие примеры и ссылку на архив.
Всё равно ссылка не работает…
поправил еще раз
Спасибо большое! Скачалось :)
Мне пришлось заняться созданием миниатюрной галлереи. Начал с того, что открыл справочник функций, попытался реализовать всё сам. Когда не вышло, нашёл вашу библиотеку и попытался воспользоваться написанным вами объектом. В обоих случаях происходит одно и то же. Картинка попадает на экран, при некоторых изменениях, но почему-то в виде текста побайтно (видна служебная информация файла jpg, а далее видимо коды пикселей, перекодированных в символы). В первозданном виде ошибка вылетает на 185й строке, не проходя условия функции !headers_sent(). Подскажите пожалуйста, как решить эту проблемму и с чего начинать. Видимо код модуля нельзя считать недействующим, поэтому проблемма в чём-то другом (возможно настройки сервера или совместимость версий).
Библиотека корректно обработала отправку данных клиенту. Скорее всего Ваш скрипт уже отправил некие данные. Проверьте свои скрипты - возможно где-то присутствует пустая строка или пробел в начале. Удалите их - они препятствуют нормальному выводу картинки. Убедитесь, что Ваш скрипт не производит редирект, например
header('Location: …');.В фотопечати есть такой термин - кадрирование. Это как раз то, чем мы тут занимаемся. Есть два варианта кадрирования - «кадр целиком» и «кадр в обрез». В первом случае (мы говорим о горизонтальных изображениях) это Ваш «метод максимального сжатия», во втором - некое подобие «фрагмент целого». В фотопечати в первом случае берется высота фотографии, пропорционально сжимается относительно этой высоты и засовывается в изображение необходимого формата (10х15 и тд). Во втором случае - за основу берется ширина фотографии и уменьшается пропорционально - соответственно при превышении необходимых на эскизе размеров фотография обрезается снизу и сверху.
В связи с актуальностью материала перенес архив на гулокод http://code.google.com/p/php-funs/downloads/list. Демоверсия на sourceforge http://phpmrtgadmin.sourceforge.net/download/demo/thumbnail/theory.html.