Qt Display Table View with large number image
-
Hi,
I need to display large amount of images in table View sometimes more than 10000, and loading altogether in to the model causes my application to use lot of RAM, and I found that I need to implement a custom model and display images only when the user scroll to that area, like unload top images when scroll down and vise-versa.
Is it possible to do with QTableView and custom model?
I already read the Qt model/view documentation, but couldn't understand much from there to implement it. -
Yeah, holding 10000 images in ram is definitely not a good idea. It's basically a caching problem.
There are couple of strategies you could take. Out of top of my head two come to mind (but there are many others I'm sure):-
Use a QCache. The cost of the item can be for example image size. You can set a maximum size allowed. Then in your model override
data()
member and return your image loading it from the cache, and if it's not already there load it from disk into the cache first. -
Create your own cache, for example a
QMap<QPoint, QImage>
, where QPoint part is the cell index in the table . Connect to thevalueChanged
signal of the scrollbars. In the handler load images that are within the visible area into the map and unload any that are not. You can keep in the map a little around the visible area to prevent images popping on the borders.
In both cases you can load the images in a separate thread and emit
dataChanged
signal from the model when they are in, to avoid gui freezes when scrolling. -
-
Yeah, holding 10000 images in ram is definitely not a good idea. It's basically a caching problem.
There are couple of strategies you could take. Out of top of my head two come to mind (but there are many others I'm sure):-
Use a QCache. The cost of the item can be for example image size. You can set a maximum size allowed. Then in your model override
data()
member and return your image loading it from the cache, and if it's not already there load it from disk into the cache first. -
Create your own cache, for example a
QMap<QPoint, QImage>
, where QPoint part is the cell index in the table . Connect to thevalueChanged
signal of the scrollbars. In the handler load images that are within the visible area into the map and unload any that are not. You can keep in the map a little around the visible area to prevent images popping on the borders.
In both cases you can load the images in a separate thread and emit
dataChanged
signal from the model when they are in, to avoid gui freezes when scrolling.@Chris-Kawa
hi
Say he makes a custom model like
http://www.informit.com/articles/article.aspx?p=1405547&seqNum=3Will it then automatically make only the visible items loaded?
I understand it will ask via data() but Im wondering how
I can know when to unload the images
that has been scrolled past. ? -
-
If the image is loaded in the
data()
member then yes, it will be loaded only when needed (becomes visible). It does not handle unloading in any way as there is no model method called for cells that go out of view.You could of course subclass the view and implement something like this. On scroll see which cells went out of view and call
data()
with some custom role likeUnloadRole
to let model know it should unload the data. It's a slight variation of the second method I proposed. -
Ok. so a custom model will help
but if u scroll all the way down to the end, it will still
have loaded all images ?So for a perfect "sliding frame" of loaded images, he will need a custom view too ? (using your UnloadRole method)
Sorry for asking so much but I find this task interesting.
-
but if u scroll all the way down to the end, it will still have loaded all images ?
Yes, but at the same time it will unload the ones you scrolled past so there's only gonna be a small amount of images loaded at any given time.
Of course, if you scroll really fast you will kill this model and your ui will be choppy as hell. That's why I suggested to offload the main thread for this. Also, another optimization could be to suspend any loading while the scrollbar is being dragged, but that's further down the road.Sorry for asking so much but I find this task interesting.
Sure, no problem. It's one of those topics that seem simple and obvious at first glance but are getting really complicated really fast when you start to inspect the details.
-
ok so custom model is the way to go.
Depending on his scrolling requirements, it might just work.It is indeed easy sounding but many details.
Also as far as I know, he want to scale the images on load so your idea with offload the main thread soon
might be needed. Not so much to allow for very fast scrolling but to allow for just normal smooth scrolling :)Thank you
-
but if u scroll all the way down to the end, it will still have loaded all images ?
Yes, but at the same time it will unload the ones you scrolled past so there's only gonna be a small amount of images loaded at any given time.
Of course, if you scroll really fast you will kill this model and your ui will be choppy as hell. That's why I suggested to offload the main thread for this. Also, another optimization could be to suspend any loading while the scrollbar is being dragged, but that's further down the road.Sorry for asking so much but I find this task interesting.
Sure, no problem. It's one of those topics that seem simple and obvious at first glance but are getting really complicated really fast when you start to inspect the details.
@Chris-Kawa, @mrjj, @haris123
Hello guys,
Your discussion piqued my interest as well, so maybe I could ask some questions as well ...?
While I do support handing the loading to a separate thread, it might not be able to keep up when scrolling. What about showing a generic image from the model and when/if the image is loaded then show the correct one? As I imagine it, when scrolling down there could a lot of requests to load an image, but most of them quickly become moot, as the user would have scrolled past beyond the visible field. So possibly one could schedule the loading requests to the worker thread and if the user has scrolled past the item just mark them as invalid, and then the thread would have wasted some time, but the UI should stay responsive. In the end when the user pauses scrolling the images would be loaded and the view updated when the correct images are loaded (this doesn't exclude using caching though). How about this?Kind regards.
-
@Chris-Kawa, @mrjj, @haris123
Hello guys,
Your discussion piqued my interest as well, so maybe I could ask some questions as well ...?
While I do support handing the loading to a separate thread, it might not be able to keep up when scrolling. What about showing a generic image from the model and when/if the image is loaded then show the correct one? As I imagine it, when scrolling down there could a lot of requests to load an image, but most of them quickly become moot, as the user would have scrolled past beyond the visible field. So possibly one could schedule the loading requests to the worker thread and if the user has scrolled past the item just mark them as invalid, and then the thread would have wasted some time, but the UI should stay responsive. In the end when the user pauses scrolling the images would be loaded and the view updated when the correct images are loaded (this doesn't exclude using caching though). How about this?Kind regards.
@kshegunov
That would probably works pretty well and also
address the cost for scaling
the image since only the true visible would be scaled or at least
only some of them as other would be "invalid" as scrolled by. -
@kshegunov Yeah, hanks for pointing that out. The thread should indeed be sort of a queue, that you can add request to and remove before they get executed if needed. Usually it has the nice effect that when scrolling at a reasonable pace you see items loading every few rows apart, so you don't have a choppy ui but you also don't scroll a wall of empty items.
-
@kshegunov Yeah, hanks for pointing that out. The thread should indeed be sort of a queue, that you can add request to and remove before they get executed if needed. Usually it has the nice effect that when scrolling at a reasonable pace you see items loading every few rows apart, so you don't have a choppy ui but you also don't scroll a wall of empty items.
the image since only the true visible would be scaled or at least
only some of them as other would be "invalid" as scrolled by.It'd be a good idea to pre-scale the images in the worker, so the main thread doesn't waste time for doing that, but only for painting them. :)
@Chris-Kawa
It's basically what you suggested for caching, but with a somewhat custom tuned caching operation. So you were of course right in the first place, I'm just throwing in my 2 cents. :) -
Hi guys,
I read all above discussion, and I have started working to implement it,
So as a first step created a custom Delegate for table view,
tableviewdelegate.h
class TableViewDelegate : public QStyledItemDelegate { public: enum datarole {ImagePathRole}; TableViewDelegate(); ~TableViewDelegate(); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index ) const; static QSize iconSize; };
tableviewdelegate.cpp
#include "tableviewdelegate.h" QSize TableViewDelegate::iconSize = QSize(20, 20); TableViewDelegate::TableViewDelegate() { } TableViewDelegate::~TableViewDelegate() { } QSize TableViewDelegate::sizeHint(const QStyleOptionViewItem & option , const QModelIndex & index) const { if(!index.isValid()) return QSize(); QSize size(option.rect.width(),option.rect.height()); /* Keep the minimum height needed in mind. */ if(size.height()<iconSize.height()) size.setHeight(iconSize.height()); return size; } void TableViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if(!index.isValid()) return; painter->save(); if (option.state & QStyle::State_Selected) painter->fillRect(option.rect, option.palette.highlight()); QString imagePath = index.data(ImagePathRole).toString(); QPixmap pix(imagePath); painter->drawPixmap(option.rect,pix); painter->restore(); }
And the mainWindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); QFuture<void> f2 = QtConcurrent::run(this,&MainWindow::loadTable); } void MainWindow::loadTable(){ QStandardItemModel *model; model = new QStandardItemModel(); ui->tableView->horizontalHeader()->setDefaultSectionSize(128); ui->tableView->verticalHeader()->setDefaultSectionSize(128); ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); model=new QStandardItemModel(); for(int i=0;i<15;i++){ //15 column for now model->setHorizontalHeaderItem( i, new QStandardItem( QString::number(i+1) ) ); } TableViewDelegate *tabledelegate; tabledelegate = new TableViewDelegate(); ui->tableView->setItemDelegate(tabledelegate); ui->tableView->setModel(model); QString inpath ="C:/Users/user/Documents/allBio/"; QStringList ImagePathList=getImageList(inpath); int row=0,col=0; for(int i=0;i<ImagePathList.size();i++){ QStandardItem *item = new QStandardItem(); QString imagePath = inpath +"/"+ ImagePathList.at(i); item->setData(imagePath,TableViewDelegate::ImagePathRole); model->setItem(row, col, item); col++; if(col==15){ row++; col=0; } } } QStringList MainWindow::getImageList(QString inpath){ QStringList ImagePathList; QStringList filters; filters << "*.jpeg" << "*.jpg"; QDir dir(inpath); dir.setNameFilters(filters); dir.setSorting(QDir::Time); ImagePathList = dir.entryList(); return ImagePathList; }
Note: in the above code, anything that discussed above is not implemented, I just created custom delegate and applied to table view. I just want to make sure I am on right direction. Please let me know your valuable feedback
Also using the above code, the paint event get called every time on some user interaction on mainwindow , even after all the images loaded, and this cause the UI to freeze sometimes while scrolling.