Авторы: Jasmin Blanchette & Reginald Stadlbauer
Перевод: Andi Peredri
Пиктограммы стали неотъемлемой частью современного пользовательского графического интерфейса. В связи с появившейся в Qt 3.0 поддержкой коллекций изображений (Qt Designer) и полным переписыванием в Qt 3.1 класса QIconSet настало время обновить наши знания о возможных подходах при создании и хранении изображений.
Неофициальный перевод статьи
Iconography выполнен с любезного разрешения
Trolltech.
QIconSet "из коробки"
Для хранения пиктограмм Qt предлагает использовать класс QIconSet. Он очень прост в использовании. Следующий код загружает пиктограмму из файла:
QIconSet icon( QPixmap( "open.bmp" ) );
А этот код создает пиктограмму из данных XPM:
const char *open_xpm[] = { ... };
QIconSet icon( open_xpm );
Класс QIconSet может хранить до 12 вариантов изображений одной и той же пиктограммы. Каждый вариант характеризуется своим размером (маленький или большой), режимом (нормальный, заблокированный или активный) и состоянием (включенный или отключенный). В приведенных выше двух примерах кода мы предоставляем лишь единственный вариант пиктограммы, соответствующий большому размеру, нормальному режиму и отключенному состоянию. Остальные 11 автоматически создаются в случае необходимости.
В следующей таблице показаны все 12 вариантов изображений одной и той же пиктограммы для кнопки QToolButton:
По умолчанию 'активные' варианты (используются, когда курсор мыши находится над кнопкой) и 'нормальные' варианты идентичны. Также идентичны 'включенные' и 'отключенные' варианты. Отрисовкой возвышенных 3D-границ 'отключенных' кнопок и погруженных 3D-границ 'включенных' кнопок занимается QToolButton.
Многие дизайнеры пиктограмм предлагают четыре варианта изображения каждой пиктограммы в отключенном состоянии: маленькая-нормальная, большая-нормальная, маленькая-заблокированная и большая-заблокированная. Следующий код показывает, как необходимо загружать такие изображения в QIconSet и как их затем использовать в QToolButton:
QIconSet icon;
icon.setPixmap( "open_sm_en.png", QIconSet::Small, QIconSet::Normal, QIconSet::Off );
icon.setPixmap( "open_la_en.png", QIconSet::Large, QIconSet::Normal, QIconSet::Off );
icon.setPixmap( "open_sm_dis.png", QIconSet::Small, QIconSet::Disabled, QIconSet::Off );
icon.setPixmap( "open_la_dis.png", QIconSet::Large, QIconSet::Disabled, QIconSet::Off );
QToolButton *button = new QToolButton( toolbar );
button->setIconSet( icon );
Перед тем, как попросить дизайнера разработать различные варианты пиктограмм, убедитесь в том, что эти варианты будут действительно использоваться. Нет никакого смысла создавать большую-заблокированную-включенную пиктограмму, если в приложении никогда не вызываются QMainWindow ::setUsesBigPixmaps(), QToolButton::setEnabled() или QToolButton::setToggle().
Изысканные варианты
Если вас не устраивают созданные QIconSet варианты пиктограмм, вы можете выбрать один из трех альтернативных подходов:
1. Попросите вашего дизайнера нарисовать необходимые варианты пиктограмм.
QIconSet не может сравниться с талантливым дизайнером в разработке пиктограмм.
2. Создайте необходимые варианты пиктограмм программно и вызовите QIconSet::setPixmap().
Это позволит вам использовать ваши собственные алгоритмы для разработки недостающих вариантов пиктограмм. В следующем примере с помощью двух разработанных функций (shrink() и disable()) создаются три варианта пиктограмм:
QImage large_enabled( "open.png" );
QImage small_enabled = shrink( large_enabled );
QImage large_disabled = disable( large_enabled );
QImage small_disabled = disable( small_enabled );
Теперь для каждого разработанного изображения необходимо вызвать setPixmap().
3. Используйте для создания пиктограмм QIconFactory.
Для разработки недостающих вариантов пиктограмм также могут быть использованы алгоритмы QIconFactory. Они экономят время и память, так как генерируют только лишь необходимые варианты пиктограмм. В следующем примере разработанные алгоритмы используются лишь для генерации 'заблокированного' и 'уменьшенного' вариантов пиктограмм, разработкой оставшихся вариантов занимаются встроенные алгоритмы (возвращаем 0):
class MyIconFactory : public QIconFactory
{
public:
virtual QPixmap *createPixmap( const QIconSet& icon,
QIconSet::Size size, QIconSet::Mode mode,
QIconSet::State state )
{
if ( mode == QIconSet::Disabled ) {
return disable( icon.pixmap( size, QIconSet::Normal, state ) );
} else if ( size == QIconSet::Small ) {
return shrink( icon.pixmap( QIconSet::Large, mode, state ) );
} else {
return 0;
}
}
};
Для использования алгоритмов MyIconFactory необходимо вызвать QIconSet::setIconFactory(). Если все пиктограммы приложения создаются с помощью одних и тех же алгоритмов MyIconFactory, гораздо удобнее воспользоваться QIconFactory::setDefaultFactory().
Вы можете засомневаться в необходимости класса QIconFactory. На первый взгляд, такой же цели можно было бы достичь переопределением функции QIconSet::pixmap(). Однако это не совсем так. Функция QIconSet::pixmap() не виртуальная, но даже если бы она такой была, это не помогло бы, потому что механизм виртуальных функций C++ работает только с указателями и ссылками на объекты, а не с копиями объектов. Поэтому при вызове QToolButton::setIconSet(icon) объект icon типа MySpecialIconSet будет преобразован и сохранен в виде QIconSet, и механизм виртуальных функций не сработает.
Оптимизация
QIconSet может создать до 12 вариантов изображений для каждой пиктограммы. Поэтому использование большого числа пиктограмм может привести к замедлению запуска приложения и перерасходу памяти.
Проблема медленного запуска приложения может возникнуть при загрузке и декодировании, скажем, 500 файлов PNG. QIconSet из Qt 3.1 пытается решить эту проблему отложенной загрузкой файлов изображений. После выполнения следующего фрагмента кода объект QIconSet будет содержать лишь имя файла "open_sm_norm_off.png":
QIconSet icon;
icon.setPixmap( "open_sm_norm_off.png", QIconSet::Small,
QIconSet::Normal, QIconSet::Off );
Непосредственная загрузка файла произойдет при первой необходимости, в данном случае, при использовании маленького-нормального-отключенного варианта пиктограммы.
Проблема перерасхода памяти встречается реже и может быть решена следующим вызовом:
QPixmap::setDefaultOptimization(QPixmap::NoOptim)
В результате обеспечивается оптимизация расхода памяти в ущерб скорости выполнения.
Хранение изображений
Изображения должны храниться совместно с вашим приложением. Есть несколько способов достичь этого. Здесь мы рассмотрим четыре подхода.
1. Хранить изображения в файлах и загружать их во время выполнения
Изображения могут быть загружены конструктором QIconSet или функцией setPixmap():
QIconSet icon;
icon.setPixmap( "open_sm_en.png", QIconSet::Small,
QIconSet::Normal, QIconSet::Off );
Если по каким-либо причинам файлы изображений станут недоступны, приложение сможет работать без них, но это не впечатлит ваших пользователей! Теоретическое преимущество такого подхода в том, что вы сможете локализовать ваше приложение без перекомпиляции. Но на практике пиктограммы обычно не привязаны к национальным особенностям. Исключением являются пиктограммы 'Предыдущий <-' и 'Следующий ->', которые могут быть неверно истолкованы пользователями, использующими языки с левосторонним чтением (арабский и иврит).
2. Включить файлы XPM в исходный код
Так как формат файла XPM совместим с форматом файла исходного кода C++, вы можете его включить непосредственно в ваш код:
#include "open.xpm"
Загрузка одного и того же изображения в нескольких файлах может привести к излишнему перерасходу памяти. Более лучшими альтернативами являются два следующих подхода.
3. Использовать Qt Designer
Если для создания графического интерфейса вы используете Qt Designer, изображения автоматически помещаются в .ui-файлы или в файл изображений проекта. Для добавления новых изображений в коллекцию проекта используйте меню Project|Image Collection... Вы увидите, насколько быстро это работает.
4. Использовать QMimeSourceFactory
Платформы Windows и Macintosh обеспечивают поддержку файлов ресурсов, которые могут быть включены в программу на этапе сборки. Дальнейшее их использование обеспечивается системным API. Однако такой подход хранения изображений не является платформно-независимым. Свой вариант решения этой задачи предлагался в Qt, начиная с версии 2.0, и с выходом Qt 3.1 платформно-независимое хранение изображений стало более удобным.
В Qt 2.0 появился класс QMimeSourceFactory, который обеспечивает прозрачный доступ к двоичным данным любого типа. Например, он используется классом QTextBrowser для загрузки изображений HTML-страниц. Второй частью данного решения является поставляемая вместе с Qt утилита qembed, позволяющая встраивать двоичные данные в C++ файл. Для хранения данных используется векторный массив embed_vec. Инструмент qembed весьма прост в использовании:
qembed --images *.png > images.h
В результате выполнения этой команды все PNG-файлы текущего каталога будут помещены в файл images.h. Теперь для получения отдельного изображения, скажем, fileopen.png, вы должны использовать следующий код:
#include "images.h"
QPixmap pix = qembed_findImage( "fileopen" );
Инструмент qembed не использует расширений. Благодаря этому смена форматов файлов ваших изображений не повлечет за собой других изменений в коде.
После выхода Qt 3.0 стало доступно более гибкое решение: вам достаточно перечислить все используемые изображения в .pro-файле проекта и затем получать их с помощью QMimeSourceFactory. Например, если используемые в программе изображения хранятся в каталоге images, то их необходимо перечислить в файле проекта следующим образом:
IMAGES += images/fileopen.png images/fileclose.png
Если вы используете Qt 3.1, то получить изображения можно следующим образом:
QPixmap pix = QPixmap::fromMimeSource( "fileopen.png" );
Если вы используете Qt 3.0, то код будет слегка запутан:
QPixmap pix;
const QMimeSource *ms =
QMimeSourceFactory::defaultFactory()->data( "fileopen.png" );
QImageDrag::decode( ms, pix );
Все это работает благодаря обеспечиваемой компилятором uic (User Interface Compiler) функциональности qembed. Поиск нужного изображения производится по ключу, в качестве которого выступает имя файла. Даже если вы не используете Qt Designer, вы можете остановиться на таком методе встраивания изображений, используя для этого непосредственно uic.
Главное преимущество механизма QMimeSourceFactory заключается в том, что вы можете использовать одно и то же изображение в различных частях приложения. Например, если в различных диалогах используется одна и та же пиктограмма, в двоичном коде программы будет присутствовать только одна ее копия. Вторым преимуществом такого подхода является возможность использования изображений во всплывающих подсказках, текстовых полях и окнах справки "What's This?". Например, всплывающая подсказка
<img src="fileopen.png"> Use this button to open a file.
будет содержать пиктограмму, потому что механизм рендеринга Qt для получения изображений использует QMimeSourceFactory.
Поставляемая вместе с Qt 3.0 версия Qt Designer поддерживает этот метод встраивания изображений и может быть использована для управления строкой IMAGES файла проекта.
Использование MIME-источников для хранения изображений является простым и эффективным решением. При использовании опции -embed компилятор uic создает файл коллекции изображений qmake_image_collection.cpp. Изображения сохраняются в массивах C и становятся доступными из QMimeSourceFactory сразу после запуска программы. Это достигается автоматическим вызовом глобальной функции qInitImages_projectname(), определенной в файле коллекции изображений, где projectname - это название проекта, указанное в переменной TARGET файла проекта.
К сожалению, некоторые компиляторы не обеспечивают для библиотек корректное создание статических объектов. Если вы столкнулись с этой проблемой или хотите ее избежать, объявите инициализирующую функцию и вызовите ее перед первым использованием изображений, например:
void qInitImages_myproject();
int main( int argc, char **argv )
{
qInitImages_myproject();
QApplication app( argc, argv );
// etc.
}
Механизм MIME-источников Qt является платформно-независимым решением для использования изображений, встроенных в приложения в двоичном виде. В следующей версии Qt Trolltech планирует распространить этот механизм на любые двоичные данные.
QImage & Co.
Для хранения изображений Qt предлагает несколько классов. Здесь представлен их небольшой обзор:
- QImage содержит пиксельные данные в представлении, независимом от устройства. Этот класс поддерживает различные операции, такие как сглаживание, масштабирование и черно-белое конвертирование. Поддерживаются все основные графические форматы, такие как BMP, GIF, JPEG, PNG и XPM. Полный список доступных для текущей конфигурации Qt форматов может быть получен с помощью QImageIO ::inputFormats().
- QPicture содержит векторное изображение. Этот класс поддерживает чтение и запись файлов в формате W3C Scalable Vector Graphic и внутреннем формате Qt.
- QPixmap используется в виджетах. В то время, как QImage поддерживает глубину цвета в 1, 8 и 24 бита, QPixmap имеет ту же глубину цвета, что и виджеты, и поэтому быстр при отрисовке.
Qt также предлагает глобальный в пределах одного приложения кеш изображений QPixmapCache. Он может быть полезен для хранения часто используемых или трудносоздаваемых изображений.