Load multiple pages (pixmaps) into a QScrollArea: how to gain efficiency?



  • Hello,

    I am using python-poppler-qt4 to create a PDF viewer. I've looked at the "QT Quarterly":http://doc.qt.nokia.com/qq/qq27-poppler.html's example, though it seems the viewer shows one page at a time (I can't follow C).

    I am currently loading an entire document, each pixmap rendered from pages onto QLabel, all labels stacked vertically on a QFrame. It works OK, but when I implement scaling, I need to regenerate the entire document's pixmaps, which takes more than a second for about 16 pages. That's, efficiently speaking, not acceptable.

    So I am now assuming the right thing to do is to load the pixmaps I need only, i.e. the current page's, the pervious page's and the next page's. It looks good in my head, but I have no idea as to how I should proceed programmatically. How to determine when the next page + 1 should be loaded and the previous page - 1 should be erased? Could you provide some guidance? Thanks.



  • I assume you have a QScrollBar somewhere; you could use it to find out which part of the document you are currently viewing. Based on that you could decide which pages you want to have loaded.

    But I think using QFrame and QLabel for this is not optimal. I recommend using a QGraphicsScene, with a QGraphicsPixmapItem for each page. This approach should be much more flexible, though probably a bit more tricky to set up. It is also easier to implement zooming, as you can use QGraphicsView::scale to see the whole scene larger. You'd still have to re-render the pages that are actually visible, but the QGraphicsView makes it easy to grab all the items that are currently visible. And you would not have to worry about enlarging the pixmaps that you do not want to re-render (if you did not enlarge these in your QLabels, then the scrollbar space would not correspond to pages in a linear fashion anymore).



  • Right. So if I understand the QGraphicsScene replaces my QFrame and the QGraphicsView the QScrollArea? In that case, how do I set the layout of my pages (i.e. aligned vertically). And what is the qreal I am supposed to pass into the QGraphicsView.Scale() (can't make out from the doc)?

    About your last note on the scollbar space: I currently resize the blank QLabels so as to keep up with the scaled size. So the scrollbar space not an issue.



  • I'm sorry to answer so late.
    The QGraphicsScene is not a widget, it is just a management class for the items (and it can have more than one QGraphicsView associated to it). So maybe it replaces your current QFrame in some regards, but in terms of actual widgets the QGraphcisView is all you would have.

    The argument to QGraphicsView::scale is the scaling factor: 1 means no change at all, a value greater than one means zooming in, and a value smaller than one means zooming out.

    For the layout of the pages you have two options: you could do it manualy, using QGraphicsItem::setPos. That is rather strait forward, but you have to re-position the items when they change their size, and it is your responsibility that they don't overlap. The second option is to use a root item with a QGraphicsLinearLayout (see http://doc.qt.nokia.com/4.7-snapshot/qgraphicslinearlayout.html#details for a simple example). The problem with this solution is that the QGraphicsPixmapItem does not inherit QGraphicsLayoutItem, which it has to do in order to be used in a layout. So for that to work you would have to inherit from both of these classes, and implement functions like sizeHint and setGeometry (http://doc.qt.nokia.com/4.7-snapshot/qgraphicslayoutitem.html gives a few hints what you should do).

    I hope this helps a little. If you have another question I will try to answer more quickly next time.



  • Thanks mkuettler. You have no obligation, as far as I am concerned, to answer promptly. Moreover your lagging made me work harder on my own solution. For now I stuck to the QFrame in a QScrollArea and I can find out on each moveEvent() or resizeEvent() whether a child page of the QFrame has become visible but checking their visibleRegion().isEmpty(). If those happen to be visible, I load the pixmaps into them.

    It works pretty well, it's not super smooth, but that's probably because I load Pixmaps at a 400dpi and allow them to stretch on scaling pages so I don't have to worry about them once they're loaded. I should probably be able to make the process a lot smoother by passing the rendering into a thread.

    The QGraphicsView remains a interesting alternative though.

    Thanks.



  • It's good to hear that you have a working solution.
    I think it might be a good idea if you would not only render the images that are visible, but also the next and previous. That way you could avoid lags when you scroll through the document slowly. But that would only make any sense if rendering was done in a separate thread. Otherwise scrolling to a new page always makes the user wait for a new page to be rendered, and there would be no gain by having the actually visible page rendered already.



  • Hi mkuettler,

    I think I've reached the end point of my let's-do-it-with-qscrollarea-anyway option. Loading images still is somehow delaying scrolling and zomming is really ugly.

    [quote author="mkuettler" date="1337351188"]
    The argument to QGraphicsView::scale is the scaling factor: 1 means no change at all, a value greater than one means zooming in, and a value smaller than one means zooming out.[/quote]

    That sounds appealing now, since there seems to be little to do about zooming in QScrollArea.

    [quote author="mkuettler" date="1337351188"]
    For the layout of the pages you have two options ... the second option is to use a root item with a QGraphicsLinearLayout (see http://doc.qt.nokia.com/4.7-snapshot/qgraphicslinearlayout.html#details for a simple example). The problem with this solution is that the QGraphicsPixmapItem does not inherit QGraphicsLayoutItem, which it has to do in order to be used in a layout. So for that to work you would have to inherit from both of these classes, and implement functions like sizeHint and setGeometry (http://doc.qt.nokia.com/4.7-snapshot/qgraphicslayoutitem.html gives a few hints what you should do).[/quote]

    Could I also put my QPixmap into a QLabel? QLabel would have the QLayoutItem's functions right?

    Also you mentioned
    [quote author="mkuettler" date="1337419605"] ... you could avoid lags when you scroll through the document slowly ... if rendering was done in a separate thread. Otherwise scrolling to a new page always makes the user wait for a new page to be rendered...
    [/quote]

    Indeed. However I tried to implement the loading of the PDF image into the Document widget through a thread, but that worked very poorly. First Qt does not seem to like pixmap handling outside of the main thread. Second I've manage to limit the thread's activity to QImage = Poppler.Page.renderToImage() which is the most time-consumming step of the image rendering, but that didn't go too well either: pages were rendered erroneously, with characters overlapping one another, returning error messages as if the method could not read the source image, or simply crashing the application.

    So I am going to look into the QGraphicsScene and QGraphicsView, and I hope rendering a zoom effect will look smooth and proper this time. And if you have any suggestion on the thread part, they'll be more than welcome. Thanks.



  • Hi jesuisbenjamin.

    [quote author="jesuisbenjamin" date="1337928915"]
    Could I also put my QPixmap into a QLabel? QLabel would have the QLayoutItem's functions right?
    [/quote]

    In order to use normal QWidgets in the GraphicsScene you have to use a QGraphicsProxyWidget (because every item in the scene must be a QGraphicsItem). Using that you can insert QLabels, and they should be correctly managed by layouts.

    But using QGraphicsPixmapItems with manual placement would be very easy as well, because in your application you would not have to reposition them at all. The images do not have to change their size (only their resolution), because zooming can be done independently from the scene.

    I'm sorry to hear that the thread did not worked properly. From what you wrote don't know what might have been the problem, but I would gladly look at the code. I'm not very experienced with threads, but this should be a simple usage, and I would very much like to get it working (if only to prove myself that I do know something about threads).



  • Thanks! Implementing the QGraphicsView.scale() worked perfectly! I need to readjust the code and then look into loading images with a thread. I'll definitely get back to you on this.



  • [quote author="mkuettler" date="1337937262"]
    In order to use normal QWidgets in the GraphicsScene you have to use a QGraphicsProxyWidget (because every item in the scene must be a QGraphicsItem). Using that you can insert QLabels, and they should be correctly managed by layouts.
    [/quote]

    Hi there mkuettler,

    I've followed your advice, using the QGraphicsProxyWidgets in pair with QLabels, as containers for the QPixmaps. The display works well and the zoom is smooth.

    I encounter a problem though as I want to load images progressively. In QScrollArea I looked for visible pages by checking for each page whether its visbileRegion().isEmpty() or not. In the case of QGraphicsView I do not seem to be able to use this. All QLabels' visibleRegion() have the same QRect and are visible according to what the function returns. That is, however, not true, since only page 0 is in fact visible in the View. So what does it say about how the QGraphicsProxyWidget works?

    Also in your first reply, you said:

    [quote]
    ... QGraphicsView makes it easy to grab all the items that are currently visible.
    [/quote]

    I trust you know what you were saying :) Yet I could not find any relevant method in the QGraphicsView's member list. How to see if the QGraphicsProxyWidget or its content, QLabel or QPixmap are indeed visible in the viewer?

    Thanks,
    Benjamin



  • Hi Banjamin,

    [quote author="jesuisbenjamin" date="1338110804"]
    All QLabels' visibleRegion() have the same QRect and are visible according to what the function returns. [/quote]

    I am not sure what causes this, but I could imagine that the QGraphicsProxyWidget considers everything visible, because it can not know (or does not check) whether there is a QGraphicsView seeing it. The QLabels just see the space provided to them by the QGraphicsProxyWidget, so they all return the same rect, because every rect is relative to its own QGraphicsProxyWidget.

    I did not try this, but I thought about something like the following (with view denoting your QGraphicsView*):
    @
    view->items( QRect( QPoint(0,0), view->viewport()->size() ), Qt::IntersectsItemBoundingRect );
    @

    This returns a QList<QGraphicsItem*> containing all the items that intersect with the viewport rect of the QGraphicsView. The last argument is probably not necessary (the default is Qt::IntersectsItemShape), but it might improve speed (and all your items are rectangular anyway).

    Please let me know whether this works,
    Martin



  • I hAve a related question. When you zoom, are you leaving the scroll position alone? I am working on a similar app. When zoom happens, I scale up the visible pixmaps and then regenerate them at higher resolution. This works fine.

    I'm now attempting to adjust the scroll position to be approximately the same as it was before the zoom, relatively speaking. But I still get a moment where I see the zoomed image at its not-final position, after I've done the scaling but before I've moved the scroll position.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.