Qt Quick Image Viewer Tutorial
Smooth Image Flicking in Qt Quick, Video
Introduction
This short article covers how to create a smoothly flickable image browser in Qt Quick. We will use a ListView and the FolderListModel to create an image viewer in 25 lines of code. While it may seem a trivial problem, it requires several concepts to be applied right. Step by step we will show that a ListView can indeed serve us well as a smoothly scrolling image browser.
To follow this tutorial you need the qmlviewer tool and folderlistmodel labs component. If you are running a recent version of Linux, simply install the qt4-qmlviewer, libqt4-declarative-folderlistmodel and libqt4-dev packages.
The minimum required Qt Quick version is 1.0, which has been shipping with Qt since version 4.7.1.
Bootstrapping the application
Create an image folder, place some test images inside and lets get started with the following code:
import QtQuick 1.0
import Qt.labs.folderlistmodel 1.0
ListView {
id: view
width: 640
height: 360
model: FolderListModel {
nameFilters: [ "'''.JPG", "'''.jpg" ]
folder: "images"
sortField: FolderListModel.Name
}
delegate: Image {
source: filePath
width: view.width
height: view.height
smooth: true
}
orientation: ListView.Horizontal
snapMode: ListView.SnapToItem
}
Save the file as FlickScroller.qml in the same directory . Our approach is trivial: Create a horizontally oriented ListView, use a FolderListModel to retrieve the image names and use an Image component to show the images. While this seems to be the obvious way to tackle the problem, you will see several issues here. Run the code in qmlviewer to see what's wrong. First of all, the images are deformed. Secondly, the performance is ugly at best - far from being smooth flick scrolling, although ListView usually supports that nicely. So, what is happening here? Basically ListView is generating on-demand Image delegates when they are needed for displaying items, i.e. when the items enter the viewport. Which is fine so far because we couldn't preload an entire image folder. Usually you would have hundreds of images on your device. We can improve the loading speed by setting the Image.sourceSize attribute. Internally it will use QImageReader and load the amount of pixels needed, which will require less memory and will load faster. Add the following line to the image attributes and try it out:
sourceSize.width: width
Another thing you need to know here: If you specify only one dimension for the source size the other dimension will be chosen in property to the original image size. If you specify both it will inevitably break the aspect ratio. Which brings us the the next problem: the aspect ratio is already broken. You have two choices here to fix this:
fillMode: Image.PreserveAspectCrop // or: Image.PreserveAspectFit
Add this parameter to the Image delegate and see what happens. Image.PreserveAspectCrop makes the image fill the entire width and height without breaking aspect ratio although parts may stick out on the sides. Image.PreserveAspectFit on the other hand scales the image to fit into the given width and height, possibly leaving parts blank.
Using more cores
Qt Quick allows you to use more than one thread at a time, but it is up to you to decide where you need it. First you have to know, that images are always loaded synchronously. This means an Image element halts the declarative engine until it has successfully loaded the image needed for display. It may sound worse, but should it just show a blank rectangle instead until the image is loaded? You can try this by adding the following line to the Image delegate:
asynchronous: true
As you may have noticed, the flick scrolling is now much smoother than before, but with the trade-off of flickers, because it may take some 100ms until the image is actually loaded, meanwhile showing a blank rectangle. Lucky for us, we can do better! To achieve completely fluent and flicker-free scrolling we will push the asynchronous loading of images out-of-view using the cache buffer of a ListView. ListView provides us with a cacheBuffer attribute, which defines how many delegates to retain below and above the visible area. The cacheBuffer is measured in number of pixels and defines the size of the range for which item delegates should be cached. If the items fall out of range, their delegates are destroyed. If they enter the cache range, they are created on-demand. By default the cacheBuffer is zero and therefore we can see it when the delegates are created. So let us set the cacheBuffer to a sufficiant size:
ListView {
…
cacheBuffer: width * 4
…
}
In our example this will trigger image loading for 4 images in advance in front and behind the visible area. When the user flicks the view it will fly in the chosen direction and there will be up to 4 images ready in memory to generate this smooth animation, while others are getting loaded in the background.
Fine tuning
Once we've got the basic image flicking working, we might engage onto more advanced features we might expect from an image viewer, but our goal here is achieved: proving the power of a ListView in Qt Quick. If you run it on your system, you might still see some jitters. For a fully perfect solution you still have to consider your graphics drivers and ensure it works in sync with the monitor. Usually invoking the qmlviewer with the -opengl parameter should do.