PDA

View Full Version : Artifacts with custom QGraphicsItem



ThomasS0
26th February 2015, 12:02
Hi,
when using my custom QGraphicsItemi get artifacts in the GraphicsView.
The artifacts appear when i "zoom out" by caling graphicsView->scale(0.2,0,2). Without scaling i don't see the artifacts.

I have a 5x5 grid of my GraphicsItem objects, and the upper left is the parent of the other ones.
I draw the bounding rects and they look good.


Here is my paint method:


void Tile::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
Q_UNUSED(widget);

painter->save();

if(!pixels.isNull()) {

painter->drawPixmap(QRect(0,0,w,h), pixels, pixels.rect());

}

painter->drawRect(boundingRect());

painter->restore();
}


and my bounding rect method:



QRectF Tile::boundingRect() const {

if(childItems().empty()) {
return QRectF(-50,-50,w+100,h+100);
} else {
return QRectF(-100,-100,5*w+200,5*h+200);
}
}




I searched this forum and the web for solutions, but i couldn't solve it yet. Any ideads?

Any idea?

Cheers,
Thomas

wysota
26th February 2015, 12:14
Where are you calling prepareGeometryChange()?

ThomasS0
26th February 2015, 12:23
i called prepareGeometryChange() in the constructor of my QGraphicsItemSubclass, but i removed since it didn't change anything.
Is it neccessary to call prepareGeometryChange() even if the bounding rect never changes?

Now i call prepareGeometryChange() in the beginning of my paint() method, and the artifacts disappeared. Is this a suitable place to call prepareGeometryChange() or sould it be called only once and from somewhere else?

Thanks!

wysota
26th February 2015, 13:24
i called prepareGeometryChange() in the constructor of my QGraphicsItemSubclass,
That doesn't make sense.


Is it neccessary to call prepareGeometryChange() even if the bounding rect never changes?
Your boundingRect does change depending on whether your item has children or not.


Is this a suitable place to call prepareGeometryChange()
No. You should call it when boundingRect() changes.

ThomasS0
26th February 2015, 14:48
thanks for you explanations.

This is what i added now:


QVariant Tile::itemChange(GraphicsItemChange change, const QVariant &value) {

//if(change == ItemChildAddedChange) {
prepareGeometryChange();
//}

return QGraphicsItem::itemChange(change, value);
}


prepareGeometryChange() gets called this way, but the artifacts still appear.

The only way to get rid of the artifacts that i found yet is to call prepareGeometryChange() in paint(), which leads to repeated redrawing and high cpu usage.
The Tile class does not add its children itself, they are added from outside the class.
Is my assumption right, that the boundingRect also needs to include the items children?

wysota
26th February 2015, 16:46
Maybe you should just state what you are trying to achieve?

ThomasS0
26th February 2015, 17:08
I want to draw different QPixmaps to a GraphicsScene, depending on the zoom level. I use option->levelOfDetailFromTransform(painter->worldTransform()) in paint() to get the level of detail, and then draw the right pixmap.
This is why i don't use QGraphicsPixmapItem insteat of my Tile class.
For debugging, i removed the level of detail code, and the problem still appears.

When creating the QGraphicsScene, all Tile objects are created. There is one parent Tile and multiple child Tiles.
The number of children per Tile does not change at a later time. The parent Tile is added to the QGraphicsScene only after all children have been set.

I drawed my boundingRects, and they look good.

Now if i never call prepareGeometryChange() i get artifacts. I was able to get rid of the artifacts by calling prepareGeometryChange() at the beginning of the paint method, but this is not a good solution because it leads to an endlessly repeated painting().

d_stranz
26th February 2015, 17:25
What are these "artifacts"?

wysota
26th February 2015, 17:32
I want to draw different QPixmaps to a GraphicsScene, depending on the zoom level. I use option->levelOfDetailFromTransform(painter->worldTransform()) in paint() to get the level of detail, and then draw the right pixmap.
This is why i don't use QGraphicsPixmapItem insteat of my Tile class.
For debugging, i removed the level of detail code, and the problem still appears.
This doesn't explain why the bounding rect of the item depends on whether it has children or not.


When creating the QGraphicsScene, all Tile objects are created. There is one parent Tile and multiple child Tiles.
The number of children per Tile does not change at a later time. The parent Tile is added to the QGraphicsScene only after all children have been set.
This still doesn't explain why bounding rect depends on whether the item has children or not.


I drawed my boundingRects, and they look good.
Because you are calling the boundingRect() function. Graphics View is not (until you call prepareGeometryChange), that's the issue.


Now if i never call prepareGeometryChange() i get artifacts. I was able to get rid of the artifacts by calling prepareGeometryChange() at the beginning of the paint method, but this is not a good solution because it leads to an endlessly repeated painting().
I think the problem is that you are trying to paint something "on behalf of your children" which you shouldn't be doing.

ThomasS0
26th February 2015, 17:58
d_stranz, i attached a picture. left is what i want, right are the "artifacts". 10970



This still doesn't explain why bounding rect depends on whether the item has children or not.

As stated above, i assumed that the bounding rect should include all children. Am i wrong?
Nevertheless, i set the bounding rect to a fixed size now, independently of the children, and it didn't change anything.



QRectF Tile::boundingRect() const {
return QRectF(-50,-50,w+100,h+100);
}
w and h are const.



Because you are calling the boundingRect() function. Graphics View is not (until you call prepareGeometryChange), that's the issue.

Yes, i see. But what i don't know is why i have to call prepareGeometryChange(), even if the bounding rect is fixed, and where whould be a better place to call prepareGeometryChange() instead of in paint()?



I think the problem is that you are trying to paint something "on behalf of your children" which you shouldn't be doing.

Now every Tile has a fixed bounding rect, and draws only its own pixmap and its own bounding rect. The problem remains.

wysota
26th February 2015, 18:16
The item should never care about is children, it reports is own size. PrepareGeometryChange is only required if you modify the bounding rect. The artifacts on the picture are in my opinion related to incorrectly drawing the pixmap. Does calling prepareGeometryChange still help for it? What if you call update() instead?

ThomasS0
26th February 2015, 18:30
Does calling prepareGeometryChange still help for it? What if you call update() instead?

update() and prepareGeometryChange() in the beginning of paint() each solve the drawing problem, but both lead to endlessly repeated paint() calls.

wysota
26th February 2015, 18:45
If update() solves the problem then it has nothing to do with the bounding rect. prepareGeometryChange() calls update(), that's why it helps. I would take a closer look at your drawPixmap call, make sure it is valid.

ThomasS0
27th February 2015, 11:20
I have created a minumal example reproducing the problem, attached.
The drawing happens in src/Tile.cpp.


My image is quite big, 8192x8192 pixels. If i use a smaller picture, everything looks good.
The problem is: when i zoom out several times, it does not display the images correctly.

I would be happy if you could take a look.
10973



Thanks,
Thomas

wysota
27th February 2015, 18:26
I'm not sure what effect exactly you wanted to achieve. The loop where you create tiles seems a bit strange.

ThomasS0
2nd March 2015, 11:53
Hey wysota, thanks for your time.

I attached a more simplified example, the problem still occures: 10978.

I want to display a Pixmap, using my own QGraphicsItem subclass.

When i scale the scene to zoom out, the drawing of the pixmap goes wront, as you can see on the attached screenshots:

10979109801098110982

wysota
2nd March 2015, 14:15
I still think what I wrote a couple of posts ago, that your drawPixmap call is wrong.


painter->drawPixmap(QRect(0,0,w,h), pixels, pixels.rect());

ThomasS0
2nd March 2015, 15:27
I still think what I wrote a couple of posts ago, that your drawPixmap call is wrong.


painter->drawPixmap(QRect(0,0,w,h), pixels, pixels.rect());
I checked the documentation again and i don't see the problem. Please explain whats wrong with this line.
What i am trying to is to draw the complete pixmap at position (0,0), scaled to size (w.h).

wysota
3rd March 2015, 23:12
I think the effect you observe comes from rounding errors and effectively the pixmap gets "pulled" to one side of the item. I don't understand why you have a tile of size 1900 and draw a pixmap of size 8192 onto it.