Wiki

Иконография

Авторы: 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:

Icons

По умолчанию 'активные' варианты (используются, когда курсор мыши находится над кнопкой) и 'нормальные' варианты идентичны. Также идентичны 'включенные' и 'отключенные' варианты. Отрисовкой возвышенных 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. Он может быть полезен для хранения часто используемых или трудносоздаваемых изображений.