Writing a Qt Image Plugin

Материал из Wiki.crossplatform.ru

Перейти к: навигация, поиск
Image:qt-logo_new.png Image:qq-title-article.png
Qt Quarterly | Выпуск 17 | Документация


by Kent Hansen
The Qt image I/O API provides a high-level API for reading and writingimages in popular formats supported by Qt, including JPEG, PNG and BMP.But what if your application needs to read or write images in a formatnot currently supported by Qt?

Содержание

This article addresses this intriguing question. The first part givesan overview of the Qt image I/O framework, revealing what goes on behindthe scenes; specifically, we show how Qt enables support for a multitude ofimage formats in an extensible fashion.The second part goes on to show -- with an example -- how you can tap intothe power of that framework, and provide seamless integration with your ownimage formats.

[Download source code]

[править] Basic I/O

At the application level, you typically read an image into a QImageby passing a filename to the QImage constructor:

QImage myimage(fileName);
if (myimage.isNull()) {
    // Handle load error
}

You can write an image by calling its save() function:

// Save the modified image to file
if (!myimage.save(fileName, format)) {
    // Handle save error
}

In order to gain an understanding of how we can achieve reading and writingof images in our custom image format in the same high-level way, we will spenda few moments to consider what goes on behind the scenes of the above code.

The QImage functions rely on the classes QImageReader and QImageWriter to provide image reading and writing, respectively. The main job of these two classes is to delegate the actual image I/O to the proper image I/O handler.

An image I/O handler encapsulates the details of low-level image I/Ofor a particular image format. The handler is a subclass of the interface class QImageIOHandler, and implements thevirtual functions that QImageReader and QImageWriter will use to delegate image I/Oto the handler.

A Qt image I/O plugin makes a QImageIOHandler available to Qt atrun-time. The plugin, which is a subclass of QImageIOPlugin, providestwo basic services. First, the plugin can be queried to determine whether it has an image I/O handler that can perform the requested I/O. Second, the plugin acts as a factory for the image I/O handler itself, allowing Qt to obtain an instance of the handler.

center

The above figure shows the basic control flow when an application loads animage from a file.

The application simply constructs a QImage, handingthe QImage constructor the filename. In response,the QImage instructsa QImageReader to read the image.The primary constructor of QImageReadertakes a QIODevice as argument, which meansthat any subclass of QIODevice can be used,from QFile to QTcpSocket, and even custom devices.(In the case illustrated by the above figure, a QFile would be constructed from the givenfilename.)

Optionally, a format string can be passed to the QImage constructor; like the filename, thisstring is passed along to the QImageReader.In the absence of a format string, an image I/O plugin is expected to"peek" at the contents of the given QIODevice(e.g. check the image header) and auto-detect the format.

The QImageReader queries the readingcapabilities of each plugin in turn. If no suitable handler can be providedby any plugin, the reader falls back to a default handler, if one exists forthe given format (not shown in the figure).Finally, the QImageReader callsread() on the established I/O handler, which does the real imagereading.

[править] Writing an Image

Writing an image with the help of QImageWriter is similar to reading;the main difference is that passing the image format string is now mandatory, because auto-detecting the format from the I/O device is no longer apossibility. Additionally, you can set various I/O handler options if thehandler supports them, such as the compression level that is to be used.

[править] Providing a Custom Image I/O Handler

By subclassing QImageIOPlugin and QImageIOHandler, you cangive Qt access to your own image I/O code, effectively allowing Qtapplications to access your image formats just like any other supportedimage format (e.g., by passing a filename to the QImage constructor). In this section, we look atwhat this entails.

Let us consider an example image I/O plugin for a very simple "raw"(uncompressed) ARGB image format. The format itself has been invented just forthe sake of this example. In this ingenious format, pixels are represented as32-bit unsigned integers, with 8 bits allocated for each of the red, green,blue, and alpha channels, and they are preceded by a small (12-byte) headercontaining the "magic" number, 0xCAFE1234, followed by the width andheight of the image (each 32-bit unsigned integers).

The ArgbPlugin class is an QImageIOPlugin subclass, and exposesthe image handler to Qt via a set of standard functions:

class ArgbPlugin : public QImageIOPlugin
{
public:
    ArgbPlugin();
     ArgbPlugin();
 
    QStringList keys() const;
    Capabilities capabilities(QIODevice *device,
                 const QByteArray &format) const;
    QImageIOHandler *create(QIODevice *device,
                            const QByteArray &format = QByteArray()) const;
};

There are three functions that must be reimplemented in our subclass.The first function, keys(), returns a list of the format stringsrecognized by the plugin. We've chosen to use the rather generic .rawextension for the image format, so keys() looks like this:

    QStringList ArgbPlugin::keys() const
    {
        return QStringList() << "raw";
    }

This function is used byQImageReader::supportedImageFormats() andQImageWriter::supportedImageFormats() to build the list of imageformat strings that Qt can provide handlers for.

The capabilities() function determines the read/writecapabilities of the plugin (or rather, of the handler it provides)based on a given I/O device or format string:

QImageIOPlugin::Capabilities ArgbPlugin::capabilities(
    QIODevice *device, const QByteArray &amp;format) const
{
    if (format == "raw")
        return Capabilities(CanRead | CanWrite);
    if (!(format.isEmpty() &amp;&amp; device->isOpen()))
        return 0;
 
    Capabilities cap;
    if (device->isReadable() &amp;&amp; ArgbHandler::canRead(device))
        cap |= CanRead;
    if (device->isWritable())
        cap |= CanWrite;
    return cap;
}

Note that when a format string is not given, the plugin calls the staticfunction canRead() of ArgbHandler (see below) to determine if thedevice's contents indicate the presence of a raw ARGB image.

The third and final function of ArgbPlugin creates an instance of the image I/O handler itself, ArgbHandler:

QImageIOHandler *ArgbPlugin::create(
    QIODevice *device, const QByteArray &amp;format) const
{
    QImageIOHandler *handler = new ArgbHandler;
    handler->setDevice(device);
    handler->setFormat(format);
    return handler;
}

That's it for ArgbPlugin. As we can see, subclassing QImageIOPlugin is a straightforwardprocess. Even for complex image formats, the code probably doesn't have to domuch more than in this example because the image format-specific processingis in the image I/O handler.

The QImageIOHandler subclass,ArgbHandler, performs I/O specific to the raw ARGB image format.

class ArgbHandler : public QImageIOHandler
{
public:
    ArgbHandler();
    ~ArgbHandler();
 
    bool canRead() const;
    bool read(QImage *image);
    bool write(const QImage &amp;image);
 
    QByteArray name() const;
 
    static bool canRead(QIODevice *device);
 
    QVariant option(ImageOption option) const;
    void setOption(ImageOption option, const QVariant &amp;value);
    bool supportsOption(ImageOption option) const;
};

The first function of interest isthe static function, canRead(), which checks whether a raw ARGB imagecan be read from a given device. It simply checks that the input datastarts with the magic number, 0xCAFE1234:

bool ArgbHandler::canRead(QIODevice *device)
{
    return device->peek(4) == "\xCA\xFE\x12\x34";
}

Here, we rely on QIODevice::peek() to lookat the contents of the device without side effects; unlikeQIODevice::read(), peek() does notconsume the data, leaving the device in the same state as it was prior to theinvocation of peek().

The handler processes the image files in its read() function:

bool ArgbHandler::read(QImage *image)
{
    QDataStream input(device());
    quint32 magic, width, height;
    input >> magic >> width >> height;
    if (input.status() != QDataStream::Ok || magic != 0xCAFE1234)
        return false;
 
    QImage result(width, height, QImage::Format_ARGB32);
    for (quint32 y = 0; y < height; ++y) {
        QRgb *scanLine = (QRgb *)result.scanLine(y);
        for (quint32 x = 0; x < width; ++x)
            input >> scanLine[x];
    }
    if (input.status() == QDataStream::Ok)
        *image = result;
    return input.status() == QDataStream::Ok;
}

We use QDataStream to unpack the multi-bytevalues for each pixel from the QIODevice. Afterreading the image header and verifying the magic number, we construct a QImage of the proper size and format,then read the pixels one at a time, scanline by scanline.

Finally, we check that the input data stream is correct (that there wereno input errors while reading the pixels). If so, we store theresulting image and report success by returning a true value; otherwise,we indicate failure with a false value.

The reimplementation ofQImageIOHandler::write() performs the"inverse" process to that implemented in the read() function:

bool ArgbHandler::write(const QImage &amp;image)
{
    QImage result = image.convertToFormat(QImage::Format_ARGB32);
    QDataStream output(device());
    quint32 magic = 0xCAFE1234;
    quint32 width = result.width();
    quint32 height = result.height();
    output << magic << width << height;
    for (quint32 y = 0; y < height; ++y) {
        QRgb *scanLine = (QRgb *)result.scanLine(y);
        for (quint32 x = 0; x < width; ++x)
            output << scanLine[x];
    }
    return output.status() == QDataStream::Ok;
}

In addition to reading and writing raw ARGB images, ArgbHandlersupports theQImageIOHandler::Sizeoption, so that the size of an ARGB image can be queried without actuallyhaving to read the entire image. To achieve this, we reimplementsupportsOption() and option() as follows:

bool ArgbHandler::supportsOption(ImageOption option) const
{
    return option == Size;
}
 
QVariant ArgbHandler::option(ImageOption option) const
{
    if (option == Size) {
        QByteArray bytes = device()->peek(12);
        QDataStream input(bytes);
        quint32 magic, width, height;
        input >> magic >> width >> height;
        if (input.status() == QDataStream::Ok &amp;&amp; magic == 0xCAFE1234)
            return QSize(width, height);
    }
    return QVariant();
}

Basically, instead of creating a QDataStreamthat operates on the QIODevice itself (like wedid in ArgbHandler::read()), we only peek at the 12-byte header andcreate a QDataStream that operates on theresulting QByteArray. This way, we can parsethe header and extract the image size without disturbing the state of the QIODevice.

[править] Qt Project File

The project (.pro) file for the raw ARGB plugin looks as follows:

TARGET  = argb
TEMPLATE = lib
CONFIG = qt plugin
VERSION = 1.0.0
 
HEADERS = argbhandler.h
SOURCES = argbplugin.cpp argbhandler.cpp
 
target.path += $$[QT_INSTALL_PLUGINS]/imageformats
INSTALLS += target

Note that we use the lib template (the plugin is compiled as a library),and that the CONFIG definition contains plugin to ensure that a Qtplugin is built.

[править] Using the Plugin

With the project file shown above, make install will install the plugin inthe Qt installation's plugin directory, making the plugin available to all Qtapplications.

If the plugin resides in a different directory (e.g., if the plugin isbundled with your application's source distribution), you have to change thetarget path in the project file, and your application needs to callQCoreApplication::addLibraryPath()with the path of your plugin directory.

For example,if the ARGB plugin is located in some_path/plugins/imageformats, an application that wants to use it has to include the following line:

QCoreApplication::addLibraryPath("some_path/plugins");

With the plugin in place and the library path correctly set, an application cannow read and write raw ARGB image files with the usual QImage functions, which is what we set out toachieve.

[править] Wrapping Up

While the example we have looked at is very basic, it nicelyillustrates some issues that most Qt image I/O handlers need to deal with:

  • Attempting to detect the image format by verifying the presence offormat-specific header information.
  • Creating a proper QImage based on imageattributes found in the header (e.g., size and format).
  • Handling multi-byte data in a platform-independent manner.
  • Pixel and scanline-level image manipulation.
  • Returning image attributes (e.g., size) without actuallyreading the whole image.

For reference implementations of non-trivial image plugins, take a look atthe plugins that are part of the Qt source distribution. These can befound in the src/plugins/imageformats directory. Those of particularinterest in today's jungle of high-complexity image formats are the JPEGand MNG plugins, which demonstrate how an image plugin can wrap existingthird party libraries: libjpeg and libmng respectively.


Copyright © 2006 Trolltech Trademarks