PDA

View Full Version : 4.7.0 - broken QGraphicsItem::ItemClipsChildrenToShape



leetar
25th November 2010, 18:12
Hi,

I use the QGraphicsItem::ItemClipsChildrenToShape flag in a QGraphicsItemGroup to be sure that its children are painted only inside the group and their shape is cut outside the group. Having updated from 4.6.3 to 4.7.1 I can't see any items painted inside the group (when the QGraphicsItem::ItemClipsChildrenToShape flag is enabled).

Found the problem http://bugreports.qt.nokia.com/browse/QTBUG-9024

Any suggestions to take that in ?

tbscope
25th November 2010, 18:24
If you use a git clone, do a pull and rebuild Qt.
Otherwise apply the patch attached to the bug report and rebuild Qt

leetar
25th November 2010, 19:01
The thing is the patch brakes the functionality as it exists in 4.7.0 and prior to 4.7.0 (4.6.3) the functionality worked as intended.

MarekR22
25th November 2010, 20:17
Hi,

I use the QGraphicsItem::ItemClipsChildrenToShape flag in a QGraphicsItemGroup to be sure that its children are painted only inside the group and their shape is cut outside the group. Having updated from 4.6.3 to 4.7.1 I can't see any items painted inside the group (when the QGraphicsItem::ItemClipsChildrenToShape flag is enabled).

Found the problem http://bugreports.qt.nokia.com/browse/QTBUG-9024

Any suggestions to take that in ?
The problem you are describing is different then reported bug you are quoting!
Bug report says about performance and nothing about painting problems!

So show me your code!
what is the shape of parent?

leetar
26th November 2010, 08:41
# inherits QGraphicsItemGroup (boundingRect(), shape(), childrenBoundingRect() are not overridden)
MyGroupItem:MyGroupItem()
: QGraphicsItemGroup()
#

MyGroupItem::test()
{
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
QGraphicsRectItem * item = new QGraphicsRectItem(this);
item->setRect(0,0, 20, 20);
qDebug() << item->parentItem()->boundingRect();
qDebug() << item->parentItem()->childrenBoundingRect();
qDebug() << item->parentItem()->shape();
}

QRectF(0,0 0x0)
QRectF(0,0 0x0)
QPainterPath: Element count=0


MyGroupItem::test()
{
// setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
QGraphicsRectItem * item = new QGraphicsRectItem(this);
item->setRect(0,0, 20, 20);
qDebug() << item->parentItem()->boundingRect();
qDebug() << item->parentItem()->childrenBoundingRect();
qDebug() << item->parentItem()->shape();
}

QRectF(0,0 20x20)
QRectF(0,0 20x20)
QPainterPath: Element count=5
-> MoveTo(x=0, y=0)
-> LineTo(x=20, y=0)
-> LineTo(x=20, y=20)
-> LineTo(x=0, y=20)
-> LineTo(x=0, y=0)

Added after 10 minutes:

Also if you take a look at the actual diff of the bugfix http://bugreports.qt.nokia.com/secure/attachment/14672/qtbug-9024.commit
there is a code block that I'm not sure with and therefore, might cause the bug.

void QGraphicsViewPrivate::setUpdateClip(QGraphicsItem *item)
+{
+ Q_Q(QGraphicsView);
+ // We simply ignore the request if the update mode is either FullViewportUpdate
+ // or NoViewportUpdate; in that case there's no point in clipping anything.
+ if (!item || viewportUpdateMode == QGraphicsView::NoViewportUpdate
+ || viewportUpdateMode == QGraphicsView::FullViewportUpdate) {
+ hasUpdateClip = false;
+ return;
+ }
+
+ // Calculate the clip (item's bounding rect in view coordinates).
+ // Optimized version of:
+ // QRect clip = item->deviceTransform(q->viewportTransform())
+ // .mapRect(item->boundingRect()).toAlignedRect();
+ QRect clip;
+ if (item->d_ptr->itemIsUntransformable()) {
+ QTransform xform = item->deviceTransform(q->viewportTransform());
+ clip = xform.mapRect(item->boundingRect()).toAlignedRect();
+ } else if (item->d_ptr->sceneTransformTranslateOnly && identityMatrix) {
+ QRectF r(item->boundingRect());
+ r.translate(item->d_ptr->sceneTransform.dx() - horizontalScroll(),
+ item->d_ptr->sceneTransform.dy() - verticalScroll());
+ clip = r.toAlignedRect();
+ } else if (!q->isTransformed()) {
+ clip = item->d_ptr->sceneTransform.mapRect(item->boundingRect()).toAlignedRect();
+ } else {
+ QTransform xform = item->d_ptr->sceneTransform;
+ xform *= q->viewportTransform();
+ clip = xform.mapRect(item->boundingRect()).toAlignedRect();
+ }
+
+ if (hasUpdateClip) {
+ // Intersect with old clip.
+ updateClip &= clip;
+ } else {
+ updateClip = clip;
+ hasUpdateClip = true;
+ }
+}

P.S. I use qt 4.7.1 carbon (official lgpl dist, no custom changes) (mac os x 10.6.5)

MarekR22
26th November 2010, 09:37
Ok, this thing you are trying to do with this flag has no sense.

MyGroupItem::shape - inherited from QGrphicsItem::shape and this takes boundingRect of item as a shape
MyGroupItem::boundingRect - inherited from QGraphicsItemGroup::boundingRect - takes QGraphicsItem::childrenBoundingRect - union of boundingRect of children

So you are try clip children to rectangle which covers whole content of all children so effectively no clipping should be done! The thing you are doing has no point!

I can't prove it but I suspect that some optimization of childrenBoundingRect takes into account QGraphicsItem::ItemClipsChildrenToShape flag (it should) so as a result you have circular dependency boundingRect is calculated based on shape and shape is calculated based on boundingRect when this flag is set and that is why you have unexpected outcome.

So I thing you should reimplement shape method so setting of QGraphicsItem::ItemClipsChildrenToShape would have some sense and to break possible circular dependency.

wysota
26th November 2010, 11:21
What is the effect you really want to achieve?

leetar
26th November 2010, 13:05
Re: 4.7.0 - broken QGraphicsItem::ItemClipsChildrenToShape
Ok, this thing you are trying to do with this flag has no sense.

MyGroupItem::shape - inherited from QGrphicsItem::shape and this takes boundingRect of item as a shape
MyGroupItem::boundingRect - inherited from QGraphicsItemGroup::boundingRect - takes QGraphicsItem::childrenBoundingRect - union of boundingRect of children

So you are try clip children to rectangle which covers whole content of all children so effectively no clipping should be done! The thing you are doing has no point!

I can't prove it but I suspect that some optimization of childrenBoundingRect takes into account QGraphicsItem::ItemClipsChildrenToShape flag (it should) so as a result you have circular dependency boundingRect is calculated based on shape and shape is calculated based on boundingRect when this flag is set and that is why you have unexpected outcome.

So I thing you should reimplement shape method so setting of QGraphicsItem::ItemClipsChildrenToShape would have some sense and to break possible circular dependency.

I only provided a simple code demonstrating the issue.
The option describes clearly:

QGraphicsItem::ItemClipsChildrenToShape
The item clips the painting of all its descendants to its own shape. Items that are either direct or indirect children of this item cannot draw outside this item's shape. By default, this flag is disabled; children can draw anywhere. This behavior is enforced by QGraphicsView::drawItems() or QGraphicsScene::drawItems(). This flag was introduced in Qt 4.3.

And I had it working perfectly in 4.6.3, children weren't drawn outside the parent group item so no modifications around shape(), boundingRect(), childrentBoundingRect() were required. I only provided the output info that "tbscope" had requested.


Re: 4.7.0 - broken QGraphicsItem::ItemClipsChildrenToShape
What is the effect you really want to achieve?

The effect I want is exactly what the option is intended to do. I want child items to be drawn only inside the QGraphicsItemGroup and being clipped when their shape is outside the parent. I had it working perfectly in 4.6.3 until I've upgraded to 4.7.1 recently.

wysota
26th November 2010, 13:28
And I had it working perfectly in 4.6.3, children weren't drawn outside the parent group item
Well, in 4.7 many optimizations were introduced, you probably stumbled upon one of them.


so no modifications around shape(), boundingRect(), childrentBoundingRect() were required.
Since the object group surely has the ItemHasNoContents flag set then shape() of the group item is meaningless or even empty (another optimization - calculating bounding rect of all children is expensive) so I'm guessing that clipping its children to this shape would probably result in no drawing at all which would be consistent with what you are getting right now. Removing the flag would probably "fix" the issue at the same time impacting the performance.


The effect I want is exactly what the option is intended to do.
I hate when people answer like that.


I want child items to be drawn only inside the QGraphicsItemGroup and being clipped when their shape is outside the parent. I had it working perfectly in 4.6.3 until I've upgraded to 4.7.1 recently.
Let me put it this way - why do you want to clip children to the shape of the group that contains them? What application functionality does it serve?

leetar
26th November 2010, 13:54
Tried to play with ItemHasNoContents and it didn't help. However, the group didn't have the flag manually enabled.



The effect I want is exactly what the option is intended to do.

I hate when people answer like that.

My apologies but I wanted to make it short and clear as possible :)

As for usage: Simple, I need items to be grouped and be clipped outside the group :) (I have own adjustments over boundingRect() in the actual code so it comes to be valuable but lets skip this part as it doesn't work even with the virgin example shown above).

wysota
26th November 2010, 14:07
Tried to play with ItemHasNoContents and it didn't help.
However, the group didn't have the flag manually enabled.[/quote]
True. The group item draws a selection rectangle so it has a boundingRect and a shape.


As for usage: Simple, I need items to be grouped and be clipped outside the group :) (I have own adjustments over boundingRect() in the actual code so it comes to be valuable but lets skip this part as it doesn't work even with the virgin example shown above).
The definition of the group is that it has a bounding rect of a union of bounding rects of all its children hence clipping child items to the group's bounding rect (as its shape is equivalent to its bounding rect) is by definition a no-op. If you had some clipping occuring before then most probably you were doing something you weren't supposed to be doing.

leetar
26th November 2010, 14:15
The definition of the group is that it has a bounding rect of a union of bounding rects of all its children hence clipping child items to the group's bounding rect (as its shape is equivalent to its bounding rect) is by definition a no-op. If you had some clipping occuring before then most probably you were doing something you weren't supposed to be doing.

True, shape is equivalent to boundingRect. I only have the item group's boundingRect adjusted in my code for own purposes but nothing critical.

Let's return to the simple code:


MyGroupItem::test()
{
setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
QGraphicsRectItem * item = new QGraphicsRectItem(this);
item->setRect(0,0, 20, 20);
qDebug() << item->parentItem()->boundingRect();
qDebug() << item->parentItem()->childrenBoundingRect();
qDebug() << item->parentItem()->shape();
}

QRectF(0,0 0x0)
QRectF(0,0 0x0)
QPainterPath: Element count=0

Is it normal that the group contains children and has the output with empty values ?

wysota
26th November 2010, 14:32
Yes, I believe so. The values might get computed later on. Computing bounding rects is really expensive so if you wanted to add 20 items to the group there is no point in computing the value 20 times. Probably if you run this code while the program is already running you shall get correct values.

MarekR22
26th November 2010, 14:43
Hi,

Read my previews post again there is possible explanation why the outcome is zero.
After reading that you should come to conclusion that the only reasonable shape of the group when you want to have a clipping is something what you will provide.
Default shape should not cause any clipping since boundingRect should always contain whole painted content of that item, so default shape of group will contain everything in the group!
There was a bug in Qt ver<=4.6.3 that childrenBoundingRect returned to big area and clipping to parent shape was not taken into account. This was causing big memory problem when some effects were applied (on Meamo and Symbian it was quite critical).

IMHO definitely you have to reimplement shape method (read my previous replay)! Your code has a bug which was not reviled because of some other bug in Qt.

leetar
26th November 2010, 15:04
So did I :)
Well, not exactly shape() but boundingRect() (which is equally adjusted shape()).

Let's take it from another side then (as I'll solve the rest I believe):

I create a group item with ItemClipsChildrenToShape on.
I add several items as children of the group item.
As the result I have zero boundingRect / childrenBoundingRect of the group item. Wysota mentioned about some post boundingRect calculation of the group item but I haven't noticed any changes. How can I push it manually ?

wysota
26th November 2010, 19:56
I would really like to know what is the ultimate purpose of what you are trying to do. Maybe there is some other easier approach you could take.

leetar
27th November 2010, 11:28
An example:
---
I have some area, 100x100 for instance.
I have bunch of items that should be represented on the area but they are not allowed to be drawn outside but allowed to be clipped.
Those items spread around the area using setPos() so if an item with QRectF(5,5,10,10) is painted then only half of the item should be shown and the rest is clipped.
---

Meantime, I've solved that by inheriting boundingRect() and calculating it by my own (foreach (QGraphicsItem * item, childItems()).
Therefore, I was able to calculate borders and only thing that I happened to trick around was to detect and prevent excessive recalculation so I had the calculated boundingRect cached.

By the way, having looked through qt sources I found this:

qt 4.6.3:



void QGraphicsItemPrivate::childrenBoundingRectHelper(Q Transform *x, QRectF *rect)
{
for (int i = 0; i < children.size(); ++i) {
QGraphicsItem *child = children.at(i);
QGraphicsItemPrivate *childd = child->d_ptr.data();
bool hasPos = !childd->pos.isNull();
if (hasPos || childd->transformData) {
// COMBINE
QTransform matrix = childd->transformToParent();
if (x)
matrix *= *x;
*rect |= matrix.mapRect(child->boundingRect());
if (!childd->children.isEmpty())
childd->childrenBoundingRectHelper(&matrix, rect);
} else {
if (x)
*rect |= x->mapRect(child->boundingRect());
else
*rect |= child->boundingRect();
if (!childd->children.isEmpty())
childd->childrenBoundingRectHelper(x, rect);
}
}
}


qt 4.7.1


void QGraphicsItemPrivate::childrenBoundingRectHelper(Q Transform *x, QRectF *rect, QGraphicsItem *topMostEffectItem)
{
Q_Q(QGraphicsItem);

QRectF childrenRect;
QRectF *result = rect;
rect = &childrenRect;
const bool setTopMostEffectItem = !topMostEffectItem;

for (int i = 0; i < children.size(); ++i) {
QGraphicsItem *child = children.at(i);
QGraphicsItemPrivate *childd = child->d_ptr.data();
if (setTopMostEffectItem)
topMostEffectItem = child;
bool hasPos = !childd->pos.isNull();
if (hasPos || childd->transformData) {
// COMBINE
QTransform matrix = childd->transformToParent();
if (x)
matrix *= *x;
*rect |= matrix.mapRect(child->d_ptr->effectiveBoundingRect(topMostEffectItem));
if (!childd->children.isEmpty())
childd->childrenBoundingRectHelper(&matrix, rect, topMostEffectItem);
} else {
if (x)
*rect |= x->mapRect(child->d_ptr->effectiveBoundingRect(topMostEffectItem));
else
*rect |= child->d_ptr->effectiveBoundingRect(topMostEffectItem);
if (!childd->children.isEmpty())
childd->childrenBoundingRectHelper(x, rect, topMostEffectItem);
}
}

if (flags & QGraphicsItem::ItemClipsChildrenToShape){
if (x)
*rect &= x->mapRect(q->boundingRect());
else
*rect &= q->boundingRect();
}

*result |= *rect;
}

wysota
27th November 2010, 12:37
I have some area, 100x100 for instance.
I have bunch of items that should be represented on the area but they are not allowed to be drawn outside but allowed to be clipped.
Those items spread around the area using setPos() so if an item with QRectF(5,5,10,10) is painted then only half of the item should be shown and the rest is clipped.

Again this doesn't tell why you need this.

Anyway, this works fine for me with 4.7:

#include <QtGui>
#include <cmath>

int main(int argc, char **argv){
QApplication app(argc, argv);
QGraphicsView view;
QGraphicsScene scene(QRect(-150, -150, 300, 300));
QGraphicsRectItem *rect = new QGraphicsRectItem(QRect(-100,-100, 200, 200));
rect->setPen(QPen(Qt::blue));
rect->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
scene.addItem(rect);
for(int i=0;i<12;i++){
QGraphicsEllipseItem *ell = new QGraphicsEllipseItem(QRect(-30, -30, 60, 60), rect);
ell->setBrush(QColor(qrand()%256, qrand()%256, qrand()%256));
qreal x = 100*sin(i*3.14/6.0);
qreal y = 100*cos(i*3.14/6.0);
ell->setPos(x, y);
}
view.setScene(&scene);
view.setRenderHint(QPainter::Antialiasing);
view.show();
return app.exec();
}