Tile with QGraphicsScene and QGraphicsView

I am creating an image renderer that opens large images (2gb +) in Qt. I do this by breaking a large image into several 512X512 fragments. Then I load the QGraphicsScene of the original image size and use addPixmap to add each tile in the QGraphic Scene. Thus, in the end, it looks like a huge image for the end user, when in fact it is a continuous array of smaller images glued to the scene. First is a good approach?

Trying to load all the fragments onto the scene takes up a lot of memory. Therefore, I only think about loading the plates that are visible in the view. I have already managed to subclass QGraphicsScene and override its drag event, which allowed me to find out which tiles need to be loaded further depending on the movement. My problem is tracking the scrollbars. Is there a way to create an event that fires every time the scroll bar moves. Subclassing QGraphicsView is not an option.

+7
source share
3 answers

QGraphicsScene is smart enough not to display what is not visible, so here is what you need to do:

Instead of loading and adding pixmaps, add classes that wrap pixmaps, and only load them when they are first displayed. (Computer scientists like to call this a "proxy template.") Then you can unload the pixmap based on the timer. (They will transparently reload if they are unloaded too soon.) You can even notify this proxy channel of the current zoom level so that it downloads lower resolution images when they are reduced.


Edit: here is the code to get you started. Note that all that QGraphicsScene draws is QGraphicsItem (if you call ::addPixmap , it will convert to ...GraphicsItem backstage), so you want subclasses:

(I didn’t even compile this, therefore it is a “caveat lector”, but it does the right thing;)

 class MyPixmap: public QGraphicsItem{ public: // make sure to set `item` to NULL in the constructor MyPixmap() : QGraphicsItem(...), item(NULL){ } // you will need to add a destructor // (and probably a copy constructor and assignment operator) QRectF boundingRect() const{ // return the size return QRectF( ... ); } void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget){ if(NULL == item){ // load item: item = new QGraphicsPixmapItem( ... ); } item->paint(painter, option, widget); } private: // you'll probably want to store information about where you're // going to load the pixmap from, too QGraphicsPixmapItem *item; }; 

then you can add your pixmaps to QGraphicsScene using QGraphicsScene::addItem(...)

+7
source

Although the answer has already been selected, I would like to express my opinion.

I do not like the selected answer, especially due to the use of timers. Pixmaps unload timer? Say that the user really wants to take a good look at the image, and after a couple of seconds - bam, the image will be uploaded, he will have to do something to make the image appear again. Or maybe you put another timer that loads pixmaps in a couple of seconds? Or will you check among your thousands of items if they are visible? This is not very annoying and not so, but it means that your program will constantly use resources. Say that the user minimizes your program and plays in the movies, he wonders why on earth my film freezes every couple of seconds ...

Well, if I misunderstood the suggested idea of ​​using timers, try me.

In fact, the idea suggested by Muttz is better. It reminded me of Mandelbrot’s example . Take a look at this. Instead of deciding what to do, you can rewrite this part to download the part of the image that you need to show.

In conclusion, I propose another solution using QGraphicsView is much simpler:

1) check image size without loading image (use QImageReader)

2) make your scene size equal to image size

3) instead of using pixmap elements, override the DrawBackground () function. One of the parameters will give you a new open rectangle - this means that if the user scrolls a bit, you will download and draw only this new part (to load only part of the image, use setClipRect (), and then the read () methods of the QImageReader class). If there are some transformations, you can get them from another parameter (which is a QPainter) and apply them to the image before drawing it.

In my opinion, the best solution would be to combine my solution with the flow shown in the Mandelbrot example .

The only problem I can think of right now is that the user is scaled with a large scale factor. Then you will need a lot of resources for some time to load and scale a huge image. Well, now I see that there is some QImageReader function that I have not tried yet - setScaledSize (), which, perhaps, does exactly what we need - if you set the scale size and then load the image, it may not load first all image - try. Another way is to limit the scale factor - the thing you should do anyway if you stick with the pixmap method.

Hope this helps.

+4
source

If you absolutely do not need the view to be a QGraphicsView (for example, because you place other objects on top of a large background pixmap), I would really recommend simply subclassing QAbstractScrollArea and overriding scrollContentsBy() and paintEvent() .

Add pixmaps to the LRU cache (see QPixmapCache for inspiration, although it's global) and make paintEvent() pull the used pixmaps in front and set.

If this sounds like more work than QGraphicsItem , believe me, it is not :)

+3
source

All Articles