Trolltech | Documentation | Qt Quarterly | « Generating XML

Iconography 101
by Jasmin Blanchette & Reginald Stadlbauer
Icons stand out as one of the most characteristic features of modern graphical user interfaces (GUIs). With the advent of Qt Designer's image collection support in Qt 3.0 and the complete rewrite of the QIconSet class in Qt 3.1, it is time to refresh our knowledge about the different approaches to generating and storing images.

QIconSet Out of the Box

Qt provides the QIconSet class to store icons. It is usually very easy to use. This code loads an icon from a file:

QIconSet icon( QPixmap( "open.bmp" ) );

And this code constructs an icon from XPM data:

const char *open_xpm[] = { ... };
QIconSet icon( open_xpm );

A QIconSet can store up to 12 versions of the same icon. A version is characterized by its size (small or large), its mode (normal, disabled, or active), and its state (on or off). In the two code snippets above, we provide only one version of the icon, which defaults to large-normal-off; the other 11 versions are generated automatically as they are needed.

The following table shows all 12 versions of the icon as they would appear on a QToolButton:

Icons

By default, the 'active' versions (used when the mouse hovers over the button) are identical to the 'normal' versions. Likewise, the 'on' versions are identical to the 'off' versions. The raised 3D borders on the 'on' buttons above, and the sunken 3D borders on the 'off' buttons, are provided by QToolButton.

Many icon designers provide four versions of each icon: small-enabled-, large-enabled-, small-disabled-, and large-disabled-off. The following code shows how to load the images into a QIconSet and how to set it on a 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 );

Before asking a designer to draw different versions of a tool-button icon, it's worth making sure that these versions will actually be used. There is no use in providing a large-disabled-on icon if the application never calls QMainWindow:: setUsesBigPixmaps(), QToolButton::setEnabled(), or QToolBut- ton::setToggle().

Elegant Variation

If you're not satisfied with the icon versions generated by QIconSet, you can choose from three alternative approaches:

  1. Ask your icon designer to draw the needed icon variants.

    QIconSet cannot compare with a talented human when it comes to designing good-looking icons.

  2. Derive the needed icon versions programmatically and call QIconSet::setPixmap().

    This allows you to use your own algorithms for generating missing icon variants. The following example uses two custom functions (shrink() and disable()) to generate three icon variants:

    QImage large_enabled( "open.png" );
    QImage small_enabled = shrink( large_enabled );
    QImage large_disabled = disable( large_enabled );
    QImage small_disabled = disable( small_enabled );
    

    Now simply call setPixmap() for each image.

  3. Derive the icon versions programmatically inside an icon factory.

    Icon factories can also be used to apply custom algorithms, and they save time and memory, since they only generate icon variants when required. Here's an example that performs the disabling and the shrinking, but leaves the rest to the default algorithm (by returning 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;
            }
        }
    };
    

A particular QIconSet can be made to use an icon factory by calling QIconSet::setIconFactory(). If all the QIconSets in one application are to use the same factory, it is usually more convenient to call QIconFactory::setDefaultFactory() instead.

At this point, you might wonder why the QIconFactory class exists. After all, the same could be achieved by reimplementing QIconSet::pixmap(), couldn't it? Actually no. QIconSet::pixmap() isn't virtual, and even if it were that wouldn't help, as the C++ virtual magic only works with pointers and references to objects, not with copies of the object. When you call QToolButton::setIconSet(icon), the object icon of type {MySpecial}{IconSet} is converted into a plain QIconSet and stored as such -- and the magic is lost.

Optimizations

With QIconSet, each icon can take up to 12 pixmaps. If your application contains a lot of icons, you may experience slow start-up or run out of pixmap memory.

The slow start-up problem occurs because it takes some time to read and decode, say, 2{|0.5s}000 PNG files. In Qt 3.1, QIconSet attempts to solve this by loading image files lazily; that is, when the following line is executed, only the string "open_sm_norm_off.png" is stored in the QIconSet:

QIconSet icon;
icon.setPixmap( "open_sm_norm_off.png", QIconSet::Small,
                QIconSet::Normal, QIconSet::Off );

The file itself is only loaded if it's actually used; in this example, only if a small-normal-off variant of the icon is required.

The memory problem is rare and can often be solved by calling QPixmap::setDefaultOptimization(QPixmap::NoOptim) to instruct QPixmap to optimize for space instead of speed.

Image Storage

The image data must be stored somewhere along with your application. There are many ways of doing this; here we review four approaches.

  1. Store images in files and load them at run-time

    The images can be loaded by QIconSet's constructor or by setPixmap():

    QIconSet icon;
    icon.setPixmap( "open_sm_en.png", QIconSet::Small,
                    QIconSet::Normal, QIconSet::Off );
    

    If for some reason the icon files are missing, the application will run without them -- this won't impress your users! A theoretical benefit of this technique is that you can localize your application without recompiling. In practice, icons are not culture-specific, so this is rarely needed. One exception is for right-to-left languages like Arabic and Hebrew, where a '<-' icon for 'Previous' will be misinterpreted as 'Next'.

  2. Include XPM data in the source code

    Since a legal XPM file is also a legal C++ source file, you can include it directly in your source code:

    #include "open.xpm"
    

    This can lead to wasted space, e.g. loading the same image into multiple files. The next two techniques are better alternatives.

  3. Use Qt Designer

    If you use Qt Designer to create your application's user interface, the image data is automatically stored in the .ui files or in a project-wide image collection file. You can click Project|Image Collection... to add images to the image collection. (We'll see how this works shortly.)

  4. Use QMimeSourceFactory

    The Windows and Macintosh platforms support resource files that can be linked into an application and accessed using a system API. But there is no platform-independent approach to storing binary image data. Qt has edged towards providing this functionality since version 2.0, and with the introduction of Qt 3.1, binary image data can now be used in a simple platform-independent way.

    Qt 2.0 introduced QMimeSourceFactory, a rarely noticed class that provides an abstraction for retrieving any kind of binary data from a data source. It is used by QTextBrowser to load images for HTML pages. Another part of the solution is qembed, a utility provided with Qt for embedding binary data into a C++ file. This tool generates code that puts all the data into a vector called embed_vec, and it is very easy to use:

    qembed --images *.png > images.h
    

    This command line will embed all the PNG images in the current directory into the images.h file. To retrieve a particular image, say, fileopen.png, you would use the following code:

    #include "images.h"
    

    QPixmap pix = qembed_findImage( "fileopen" );
    

    qembed chops off the extension. This way, you can change the file format of your images without needing to change all occurrences of the file name.

    Since Qt 3.0, a more versatile solution has been available: list the images you want to access in the project's .pro file and retrieve them using QMimeSourceFactory. For example, if you had some images in your project's images subdirectory, you would add the images to the project's .pro file like this:

    IMAGES += images/fileopen.png images/fileclose.png
    

    and retrieve the images like this (if you're using Qt 3.1):

    QPixmap pix = QPixmap::fromMimeSource( "fileopen.png" );
    

    For Qt 3.0 users, the code is slightly more involved:

    QPixmap pix;
    const QMimeSource *ms =
        QMimeSourceFactory::defaultFactory()->
            data( "fileopen.png" );
    QImageDrag::decode( ms, pix );
    

    All this works because uic (User Interface Compiler) now includes qembed's functionality and generates code that puts the images into a MIME-source factory using their filenames as keys. Even if you don't use Qt Designer, you can still use this approach, using uic purely to handle the image embedding.

    A major benefit of using the MIME-source technique is that a single image can be used by any part of the application. For example, if several forms all have some toolbar images in common, only one copy of each image is stored in the application's binary. Another benefit is that you can use your images wherever you use rich text formatting, such as tooltips, labels, and "What's This?" help text. For example, a tooltip text of

    <img src="fileopen.png"> Use this button to open a file.
    

    would show the image in the tooltip because Qt's rich text engine uses the QMimeSourceFactory to retrieve images.

    The version of Qt Designer that appeared with Qt 3.0 supported project files for the first time. When project files are used, Qt Designer is able to use the MIME-source approach for storing images, and maintains the IMAGES line in the .pro file.

    Using images in a MIME source provides an easy-to-use and efficient solution for image storage. It works because the uic creates the image collection file, qmake_image_collection.cpp, using its -embed option. The generated code stores the images in C arrays, and makes them accessible from the QMimeSourceFactory at load time. It achieves this by including a global function in the image collection file, qInitImages_projectname(), where projectname is the name specified by TARGET in the .pro file, and by creating a static object that calls this function from its constructor. This ensures that the qInitImages_projectname() function is automatically called when the program loads and the images are available to fromMimeSource().

    Unfortunately, a few compilers do not construct static objects properly at load time, especially for projects which are loaded as libraries. If you encounter this problem, or want to be defensive, declare the initialization function and call it from a function that will be called before any images are needed, for example:

    void qInitImages_myproject();
    

    int main( int argc, char **argv ) 
    {
        qInitImages_myproject();
        QApplication app( argc, argv );
        // etc.
    }
    

Qt's MIME-source approach provides a platform-independent way of using images as binary resources compiled into applications without needing platform-specific features like resource files. Trolltech plans to extend this mechanism in a future release, to make it possible to embed not only images, but any binary data.

QImage & Co.
Qt provides many classes to store images. Here is a review of the different kinds and their uses:
  • QImage stores pixel data in a device-independent way. It supports many operations such as smooth scaling and grayscale conversion. Images can be read from common file formats such as BMP, GIF, JPEG, PNG, and XPM; the actual list of formats varies according to how Qt is configured and is available from QImageIO::inputFormats().
  • QPicture stores a vector image. It can read and write files in the W3C Scalable Vector Graphic format and in a Qt-specific binary format.
  • QPixmap is basically an off-screen widget. Whereas QImages can be 1, 8, or 24 bits deep, QPixmaps have the same depth as widgets, and are always fast to show.
Qt also provides QPixmapCache, an application-wide pixmap cache. It is especially useful for images that are used frequently or that are expensive to generate.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2003 Trolltech. Trademarks Qt Quarterly