Qt Quick Image Viewer Tutorial: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Developing_with_Qt::Qt Quick]]


[toc align_right="yes" depth="3"]
[toc align_right="yes" depth="3"]


'''English''' [[Qt_Quick_Image_Viewer_Bulgarian|Български]]
'''English''' [[Qt_Quick_Image_Viewer_Bulgarian|Български]]
Line 11: Line 11:
== Introduction ==
== Introduction ==


This short article covers how to create a smoothly flickable image browser in Qt Quick. We will use a "ListView":http://developer.qt.nokia.com/doc/qt-4.7/qml-listview.html and the "FolderListModel":http://developer.qt.nokia.com/doc/qt-4.7/src-imports-folderlistmodel.html 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.
This short article covers how to create a smoothly flickable image browser in Qt Quick. We will use a "ListView":http://developer.qt.nokia.com/doc/qt-4.7/qml-listview.html and the "FolderListModel":http://developer.qt.nokia.com/doc/qt-4.7/src-imports-folderlistmodel.html 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. All files in this tutorial are available from "gitorious":https://www.gitorious.org/qt-training/qml-demos/trees/master/flickscroller.
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. All files in this tutorial are available from "gitorious":https://www.gitorious.org/qt-training/qml-demos/trees/master/flickscroller.


The minimum required Qt Quick version is 1.0, which has been shipping with Qt since version 4.7.1.
The minimum required Qt Quick version is 1.0, which has been shipping with Qt since version 4.7.1.
Line 19: Line 19:
== Bootstrapping the application ==
== Bootstrapping the application ==


Create an ''image'' folder, place some test images inside and lets get started with the following code:<br /><code><br />import QtQuick 1.0<br />import Qt.labs.folderlistmodel 1.0
Create an ''image'' folder, place some test images inside and lets get started with the following code:
<code>
import QtQuick 1.0
import Qt.labs.folderlistmodel 1.0


ListView {<br /> id: view<br /> width: 640<br /> height: 360<br /> model: FolderListModel {<br /> nameFilters: [ &quot;'''.JPG&amp;quot;, &quot;'''.jpg&amp;quot; ]<br /> folder: &quot;images&amp;quot;<br /> sortField: FolderListModel.Name<br /> }<br /> delegate: Image {<br /> source: filePath<br /> width: view.width<br /> height: view.height<br /> smooth: true<br /> }<br /> orientation: ListView.Horizontal<br /> snapMode: ListView.SnapToItem<br />}<br /></code>
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
}
</code>


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.<br />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 &quot;QImageReader&amp;quot;:http://developer.qt.nokia.com/doc/qt-4.7/qimagereader.html 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:<br /><code><br />sourceSize.width: width<br /></code>
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":http://developer.qt.nokia.com/doc/qt-4.7/qimagereader.html 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:
<code>
sourceSize.width: width
</code>


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:<br /><code><br />fillMode: Image.PreserveAspectCrop // or: Image.PreserveAspectFit<br /></code>
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:
<code>
fillMode: Image.PreserveAspectCrop // or: Image.PreserveAspectFit
</code>


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.
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.
Line 31: Line 59:
== Using more cores ==
== 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:<br /><code><br />asynchronous: true<br /></code>
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:
<code>
asynchronous: true
</code>


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!<br />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:<br /><code><br />ListView {<br /> <br /> cacheBuffer: width * 4<br /> <br />}<br /></code>
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:
<code>
ListView {
cacheBuffer: width * 4
}
</code>


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.
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.

Revision as of 10:11, 25 February 2015


[toc align_right="yes" depth="3"]

English Български

Smooth Image Flicking in Qt Quick

[YouTubeID:zg6GxFpQeWU]

Introduction

This short article covers how to create a smoothly flickable image browser in Qt Quick. We will use a "ListView":http://developer.qt.nokia.com/doc/qt-4.7/qml-listview.html and the "FolderListModel":http://developer.qt.nokia.com/doc/qt-4.7/src-imports-folderlistmodel.html 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. All files in this tutorial are available from "gitorious":https://www.gitorious.org/qt-training/qml-demos/trees/master/flickscroller.

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":http://developer.qt.nokia.com/doc/qt-4.7/qimagereader.html 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.