Using QtConcurrent to load a Pixmap and paint it

2019-04-14 10:42发布

问题:

I'm trying to create a Tile rendering program. Heres some basic code.

Header

class Tile: public QGraphicsItem
{
public:
Tile(void);
~Tile(void);
QGraphicsPixmapItem *tileItem;
void update(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
 protected:
QRectF boundingRect() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
};

CPP:

.Constructor etc
.
.

void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    if(tileItem==NULL)
    {
        qDebug()<<"Loading Pixmap";
        QPixmap p("c:\\qt\\tile\\tile0-0.png");
        tileItem=new QGraphicsPixmapItem;
        tileItem->setPixmap(p); 
    }
    tileItem->paint(painter,option,widget);
}

I'm trying to make a application that will paste tiles of a big image onto a QGraphicsScene. But loading all the tiles at once is time consuming and takes up a lot of memory. So I'm subclassing QGraphicsItem and overriding paint. The paint method in the QGraphicsItem class is only called when a it comes into view inside the QGraphicsView. So by loading up my tile inside paint, I can basically create an application that loads tiles only when they come into view. This much is working so far.

To make the user experience better I'm use QtConcurrent to try an load the tiles up in a seperate thread. SO here's the changes I've made.

CPP

connect(&watcher,SIGNAL(finished()),this,SLOT(updateSceneSlot()));

void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
    if(tileItem==NULL)
    {   
        TilePainter=painter;
        TileOption=option;
        TileWidget=widget;
        qDebug()<<"Paint Thread id "<< QThread::currentThread();

        future=QtConcurrent::run(LoadTilePixmap,this);
        watcher.setFuture(future);
    }
    else
        tileItem->paint(painter, option, widget);

}    

LoadTilePixmap function:

void LoadTilePixmap(Tile *temp,QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
qDebug()<<"Loading Pixmap";
QPixmap p("c:\\qt\\tile\\tile0-0.png");
temp->tileItem=new QGraphicsPixmapItem;
temp->tileItem->setPixmap(p);
qDebug()<<"Loaded Pixmap";
}


void Tile::updateSceneSlot()
{
    qDebug()<<"updateSceneSlot Thread id "<< QThread::currentThread();
    tileItem->paint(TilePainter, TileOption, TileWidget);
}

This code should work but it keeps crashing at runtime as soon as paint gets called. After adding breakpoints I narrowed the problem down to temp->tileItem->paint(painter,option,widget); which causes the crash.

The output that I get is

Loading Pixmap 
Almost Loaded Pixmap 
First-chance exception at 0x6526174a (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0xc88bffe1.
Unhandled exception at 0x6526174a (QtGuid4.dll) in Visualizer.exe: 0xC0000005: Access violation reading location 0xc88bffe1.

Could anyone help me and let me know why the lastline/paint method crashes. How can I fix it?

EDITED CODE TO UPDATE CHANGES

回答1:

Only the main(also called GUI) thread can draw on the screen. The following line from the LoadTilePixmap() function, which you run in a separate thread, I believe, tries to paint the content of your pixmap item on the screen.

temp->tileItem->paint(painter,option,widget);

In the thread you should just load and prepare the image and when the thread is done, signal to the main thread that the image is ready and do the drawing from the main thread.



回答2:

It's not clear from the code you posted, but are you initializing tileItem to be NULL in the constructor of Tile? If not, that would be a possible explanation for the crash you are seeing.



回答3:

I solved the issue by avoiding paint and instead using the update function. From whatever documentation I've read update indirectly schedules a paint call. So in the end my code looks like this

void LoadTilePixmap(Tile *temp)
{
        QPixmap p("c:\\qt\\tile\\tile0-0.png");
        temp->tileItem=new QGraphicsPixmapItem;
        temp->tileItem->setPixmap(p);
        temp->update(0,0,511,511);
}

This will cause my overloaded paint function to get called for a second time but this time the if condition is false and it goes into else which paints. Not exactly an optimum solution but it works for now.