Loading Large Images
When loading graphics, the input file can sometimes be larger than needed. Thankfully, the Qt image loading infrastructure takes this into account. Before we can look at how you can use this, we digress and have a look at the mechanisms involved when loading images using Qt.
Background to Loading Images
Every time you write QPixmap("picture.png") or QImage("picture.jpeg"), you ask Qt to load an image from disk. Qt supports a number of image formats which can be enabled or disabled when configuring Qt. You can also extend Qt with your own loading (and saving) classes for your own file formats. As these functions are implemented as plug-ins, you can add them without having to rebuild or configure Qt.
The file formats are handed by the QImageReader and QImageWriter classes. They, in turn, rely on QImageIOPlugin classes which acts as a factory for classed derived from QImageIOHandler. Each image i/o handler can support a number of features. The flags, ImageOptions, enumerate the available options. Taking full advantage of these features, you can improve the performance as well as the size of the image files that your application can handle.
Using QImageReader
The first step to taking control of your image loading is to avoid using the QImage::load or QPixmap::load. Instead, you need to invoke a QImageReader class directly. This allows you to take more direct control of the process, thus making it possible to fine-tune the settings used.
Creating and using an image reader is basically as easy as loading an image using the methods mentioned above. By default, the image reader automatically tries to identify the image format and handes the details involved in finding the correct QImageIOHandler object.
QImageReader reader("large.jpeg");
If you want to control the file format directly, you can use the QImageReader::setFormat function. Another useful format detection flag is the decideFormatFromContents property. By setting this flag, all image reader plugins are loaded and the actual contents of the image data is used to determine the file format (as opposed to just looking at the file extension). This can be useful if the image data is collected via a socket or retrieved from a database BLOB.
When you have setup your reader for the right data source and format, you can query it for some basic information. For instance, looking at the size of the image. The image i/o handlers can perform this operation without loading the full image. However, they are not forced to. You can read more about this in the Ensuring Support section below.
qDebug() << "Image size:" << reader.size();
The issue when loading a large file is usually that we cannot, or don't want to, keep the entire image in memory at once. Using the QImageReader::setClipRect function, you can clip the image while it is being loaded. This can be used to load the interesting parts of a given image, or to load an image in chunks.
reader.setClipRect(myRect);
QImage image = reader.read();
Loading only part of an image is on scenario. Another common function is to show a smaller version of the image. Loading a very large image and then scaling it down to the approriate size can be a very memory consuming operation. Again, this can be solved using the appopriate QImageReader function. By calling the QImageReader::setScaledSize function, you can set the size that you want your resulting image to be. This lets the image i/o handler load and scale the image in one operation, reducing the needed memory to a minimum.
reader.setScaledSize(mySize);
QImage image = reader.read();
Please notice that this scaling operation does not support the Qt::AspectRatioMode flags. Instead, you must take the necessary steps to handle the aspect ratio of the scaled result yourself.
To both clip and scale an image, you can set the scaledClipRect property, which clips the image after it has been scaled.
Ensuring Support
On paper, using any of the features described above, can increase the performance of your image reading, as well as reducing the needed memory. This is something that is very important in embedded settings, where resources are limited, but should be taken into account for desktop operations as well. Reducing the memory need makes caches operate more efficiently and avoids having to spin up harddrives for swapping, so writing efficient code can both increase performance and battery time - and happy users give happy developers.
However, the operations described can be handled either by the image i/o handler or by the QImageReader. The latter uses a naive implementation, i.e. loading the full image, and then clipping and scaling it. This does not improve your application in any way. So, how do you determine what your image i/o handler does and what the QImageReader does? Enter the QImageIOHandler::ImageOptions flags.
By querying the QImageReader for specific options using the QImageReader::supportsOption function, you can learn what is supported and what is not. In the examples above, we used the following features:
- QImageIOHandler::Size, the image i/o handler can read the size of the image from the image metadata. This means that querying for the image size does not load the full image, only the required header data.
- QImageIOHandler::ClipRect, the image i/o handler supports clipping and loading the image in one operation, potentially reducing memory impact and increasing performance
- QImageIOHandler::ScaledSize, the image i/o handler supports loading and scaling the image in one operation, potentially reducing memory impact and increasing performance
- QImageIOHandler::ScaledClipRect, the image i/o handler supports loading, scaling and clipping in one operation, potentially reducing memory impace and increasing performance
More Information
The QImageReader class provides more information about images than just the pixels making the image. Information such as metadata, subtypes of image formats, the support for incremental reading and more can be retreived. For instance, the QImageReader::text function returns a QString containing the metadata of the image, if the image i/o handler and image file format supports it. Please notice that this string can be encoded to hold key-value pairs of data, so it might need parsing.
Something that this text has not convered at all is the QImageWriter, which provides the same level of control for writing images. Using it, you can tune settings such as compression ratios, quality settings, endianess and more when writing image data to files. It can also be used to write image data to other devices such as sockets and database BLOBs.