Point-in-poly for QPainterPath with holes - c++

I'm trying to use a QGraphicsPathItem to represent a polygon with a hole in it (think doughnut). I can get it to draw correctly, and the center is transparent. However, with item selection or movement turned on, I am able to interact with my object when clicking in the hole. I'd rather treat the hole as not part of the polygon.
I've done some testing, and it appears that QPainterPath::contains() will return true when I check a point within the hole. Will I need to subclass QGraphicsPathItem to implement a more specific contains() function, or is there something else I'm missing?

If you change the filling rule from default value Qt::OddEvenFill to Qt::WindingFill, can you still see the hole? I guess you'll not be able to see the hole. So the hole is actually not a "physical" hole in your path. If you are going to represent a polygon with a hole, you may need to subclass the QGraphicsPathItem, explicitly define outer most path and holes, and maintain the relationship among paths in different role.

You'll need to subclass QGraphicsPathItem and reimplement the shape method. Here's what I did to solve the similar problem with an unfilled versus filled rectangle. This particular case is subclassed from QGraphicsRectItem. This also has some adjustment to the path width to make it easier for the user to click on, essentially a buffer zone around the path.
In the case of a filled rectangle, the returned shape is simply a rectangle, but for an unfilled rectangle, it's a stroked path with an empty interior. Then the object only moves when the user drags the edge, but not the middle. Figuring out the QPainterPathStroker wasn't intuitive from the documentation, but it was actually pretty simple to use.
QPainterPath MyRectItem::shape (void) const
{
if (this->brush().style() != Qt::NoBrush)
{
return QGraphicsRectItem::shape();
}
// The rectangle is unfilled. Create a path outlining the rectangle.
QPainterPath path;
QRectF rect = this->rect();
path.moveTo (rect.topLeft());
path.lineTo (rect.topRight());
path.lineTo (rect.bottomRight());
path.lineTo (rect.bottomLeft());
path.lineTo (rect.topLeft());
QPainterPathStroker stroker;
if (this->pen().style() != Qt::NoPen)
{
// For easier selection, increase the pen width to provide a buffer zone
// around the line where the user can click and still select.
stroker.setWidth (this->pen().width() + 20);
stroker.setCapStyle (this->pen().capStyle());
stroker.setJoinStyle (this->pen().joinStyle());
}
return stroker.createStroke (path);
}

Related

Osmdroid - Marker - increase selection area to get the InfoWindow

When selecting a Marker an InfoWindow pops up.
Sometimes the selection of a Marker is difficult. Especially when the map is rotating in the direction of navigation.
How can I increase the 'touch circle' so that selection is easier?
Update: I have to change the hitTest() for the Marker by subclassing.
I would like to check whether the 'hit' (or touch) was within a circle of X pixels around the point of the Marker. The icon will rotate while I navigate, so I guess I don't use the icon.
How can I do that?
public boolean hitTest(final MotionEvent event, final MapView mapView){
final Projection pj = mapView.getProjection();
pj.toPixels(mPosition, mPositionPixels);
// Does mPositionPixels contains the x, y of the Marker?
// Should I draw a Rect around this point, or could it be a circle?
// How can I check whether the event.getX(), event.getY() is a hit?
return hit;
}
Method proposed by spy is feasible.
You can also create your icon bitmap with an area of transparent pixels around. This is a very simple way to increase its touch area.
I believe #Mker would say, extend the Marker class and override the method hitTest. That is used for the Marker itself. I'm not sure if you can change this for the InfoWindow itself.

Color the area defined by Items intersection in Qt

I have some custom QGraphicsItems in a QGraphicsView of a QGraphicsScene. With items(QPoint(x, y)) method I retrieve all the items at given scene point.
Once these items are drawn they will not be moved, rotated or scaled, so their shapes will not change.
I would to know if there is a way to change the color of the overlapping area only (if I have at least two items, of course).
A different way to write my question is: given a starting point, color the scene until some borders are found.
I have not enough reputation to post an image, so I uploaded three examples of desired results here.
Edit 1: solution from Nejat works if I select a point that actually is within two Items' shapes, but it doesn't work if the point belongs to only one Item or to no Items (I uploaded an example of this case here).
Maybe should I use a different approach? Once painted, I don't need to change an item, so I will be interested also in a "flat/static" pixel-oriented solution. Could I use QImage class?
Edit 2: Nejat's answer was right for the original question. Btw, for my purpose I used a QImage, drawing on it all the shapes and finally using a "flood fill" algorithm to fill the area I wanted.
You can use the QGraphicsItem::shape () which returns a QPainterPath to retrieve the shape of an item. For taking the intersection path this can be used :
QPainterPath QPainterPath::intersected ( const QPainterPath & p ) const;
So you can get the intersection path of two items like:
QPainterPath intersectedPath = item1->shape()->intersected(item2->shape());
Now you can fill the intersected area by :
painter->setBrush(QColor(122, 163, 39));
painter->drawPath(intersectedPath);

Why could a custom QGraphicsItem cause the scene or view to move unless boundingRect is empty?

My Qt 4.8.1 C++ class to draw a crosshair subclasses QGraphicsItem and implements its paint() and boundingRect() methods.
The paint() method consists of two drawLine() and one drawText() calls. The origin is centered.
boundingRect() also respects these coordinates (-,-,+,+) and also the half-pen-width in each direction.
When creating, moving (or positioning) and then adding the object to the (visible) scene, the view shifts a few pixels so that the scene contents move slightly to the bottom right.
If the boundingRect() returns an empty QRectF(), this shift does not happen.
additional elements do not cause further movement of the scene.
a prior update() of the scene does not change the behaviour
a larger boundingRect seems to increase movement
changing the view's transform does not change the behaviour
itemChange() is called numerous times but does not seem to provide a clue (in my eyes):
Crosshair::Crosshair being created, id= "1" at QPointF(63.6, 88.487) scenePos= QPointF(33.6, 58.487)
Crosshair::Crosshair position QPointF(63.6, 88.487)
Crosshair::itemChange ItemFlagsChange QVariant(uint, 1)
Crosshair::itemChange ItemFlagsHaveChanged QVariant(uint, 1)
Crosshair::itemChange ItemFlagsChange QVariant(uint, 33)
Crosshair::itemChange ItemFlagsHaveChanged QVariant(uint, 33)
Crosshair::itemChange ItemFlagsChange QVariant(uint, 2081)
Crosshair::itemChange ItemFlagsHaveChanged QVariant(uint, 2081)
[...]
Crosshair::itemChange ItemPositionChange QVariant(QPointF, QPointF(63.6, 88.487) )
Crosshair::itemChange ItemPositionHasChanged QVariant(QPointF, QPointF(63.6, 88.487) )
Crosshair::itemChange ItemSceneChange QVariant(QGraphicsScene*, )
Crosshair::itemChange ItemSceneHasChanged QVariant(QGraphicsScene*, )
[...]
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
Crosshair::itemChange ItemVisibleChange QVariant(bool, true)
Crosshair::itemChange ItemVisibleHasChanged QVariant(bool, true)
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
Crosshair::boundingRect 20 3 QRectF(-11.5,-11.5 21.5x21.5)
...
I might be able to create a minimal example tomorrow, but maybe someone is able to guess my mistake even from this general description of the problem.
Alternatively, hints towards further debugging are of course very welcome!
Edit
Thanks Riateche for the advice.
Some further debug outputs reveal that the sceneRect() indeed is changed, it expands exactly the size of the Crosshair's bounding rect, e.g. from QRectF(0,0 1680x636) to QRectF(-11.5,-11.5 1691.5x647.5). The scene's itemsBoundingRect stays the same.
Now I failed to mention previously that the view has been scaled/transformed with fitInView(scene()->itemsBoundingRect(), Qt::KeepAspectRatio); and noticed that the shift indeed does not occur when the scene is viewed at 100% (e.g. resetTransform()).
But I still do not understand how/why the sceneRect() changes when I am adding an element within the boundingRect(). It must have to do with my custom implementation, because adding an ellipse does not cause a shift of the scene/view.
Ok, here we go: I also failed to mention that I use setFlag(QGraphicsItem::ItemIgnoresTransformations); so that the crosshair stays the same size regardless of scale. This apparently causes the boundingrect to "stay" at the scene's origin when added, and ignores the setPos().
To be continued ...
A scene has a bounding rectangle which is by default the smallest rect containing all items. If you add something to a scene outside of this rectangle, the bounding rect will be expanded.
A view respects the scene's rect. If the scene's rect is smaller than the view's viewport size, the view's scrollbars get hidden and the scene contents appear in the center of the viewport. So, if you add an item, scene's bounding rect changes and all contents get shifted. If the viewport is smaller than bounding rect then scrollbars appear.
If you want to avoid this behavior, you can use QGraphicsView::setSceneRect or QGraphicsScene::setSceneRect to fix the rect position. Note that everything outside of the set rectangle will be unavailable and invisible in the view.
Also consider switching to QGraphicsPathItem (and QGraphicsScene::addPath). It can be used to draw crosshairs.
I was trying whether the problem could be worked around with
QRectF save = scene->sceneRect();
scene->addItem( myGraphicsItem );
scene->setSceneRect( save );
as Riateche suggests on a different note, and it works.
However, I unfortunately still don't fully understand the cause of this.
Here's what the documentation has to say about this flag:
QGraphicsItem::ItemIgnoresTransformations
0x20
The item ignores
inherited transformations (i.e., its position is still anchored to its
parent, but the parent or view rotation, zoom or shear transformations
are ignored). This flag is useful for keeping text label items
horizontal and unscaled, so they will still be readable if the view is
transformed. When set, the item's view geometry and scene geometry
will be maintained separately. You must call deviceTransform() to map
coordinates and detect collisions in the view. By default, this flag
is disabled. This flag was introduced in Qt 4.3. Note: With this flag
set you can still scale the item itself, and that scale transformation
will influence the item's children.
Here is some possibly related information:
https://bugreports.qt-project.org/browse/QTBUG-19039
Issue with fitInView of QGraphicsView when ItemIgnoresTransformations is on

QGraphicsPolygonItem drawing a open(not closed) polygon

I am using a QGraphicsPolygonItem and I have noticed that it always connects the end-point with the start-point.
I know that the polygon terms means exactly that, and what I am looking for is "polyline" or "polygonal chain". I didnt found nothing like that in QGraphicsItem subclasses.
How do I draw a polygonal chain in QGraphics Framework? Is there a property of QGraphicsPolygonItem or a class that does that?
I had a similar problem, and I solved it by using the QGraphicsPathItem class. In the following code, polygon is a non-closed QPolygonF object (i.e. a QPolygonF which start-point is different from its end-point):
QPainterPath path = new QPainterPath();
path.addPolygon(polygon);
QGraphicsPathItem contour = new QGraphicsPathItem(path);
contour.setPen(new QPen(QColor.black));
When displaying this QGraphicsPathItem object, the start-point is (in theory) disconnected from its end-point.
I'm sorry this example code is in Java; but the mechanisms should be the same as in C++.
You can use QPainterPath and use lineTo method to input yors polyline points, then just use QGraphicsPathItem to make it graphics item.
Alternatively you also might think about combining several QGraphicsLineItem into one QGraphicsItemGroup, but that's more difficult as you need to pay attention to aligning lines together.
Is this what you are looking for?
EDIT:
QPainterPath is apparently closing paths, then you are left with group of lines only.
EDIT2:
Sorry for confusing you, but HostileFork seem to be right - you just use QPainterPath and call pathItem->setBrush(QBrush(Qt::transparent)); to keep your path unfilled.

QGraphicsItem::setTransformOriginPoint bug when trying to scale image

I have created my own class by extending QGraphicsItem and I want to make it so that when someone does a wheel even while over this item, it scales.
This way, i can have multiple items in a scene, and scale each of them in and out as I please.
The problem is, I want the item to scale under the mouse cursor, much like google maps does. That is, a move forward will keep panning the image and scaling it, so taht the area in the vicinity around my mouse pointer is always in view.
void ImagePixmapItem::wheelEvent ( QGraphicsSceneWheelEvent * event ){
update();
qreal factor = 1.2;
if (event->delta() < 0)
factor = 1.0 / factor;
scale(factor, factor);
scaleFactor *=factor;
this->scene()->setSceneRect(0,0,this->boundingRect().width(), this->boundingRect().height());
}
This is the code I am using to do the scale. The problem is, it always seems to be scaling from the top left corner. Well, this is undesirable, beacuse if I scale in or out too much, eventually my area of interest around the mouse pointer has moved off the screen, and I have to either scroll manually or pan to the location, zoom, pan, etc, until i get to the desired level.
I tried to use the QGraphicsItem::setTransformOriginPoint, but no matter what values I put in there, it still seems to scale and such from the top left.
What can I add to that code I posted to get the desired effect?
I have achieved similar functionality in my Image-Manipulation application (scaling the view and having the area under my mouse stay in place and visible) but I scale the whole graphics-view not a specific item, so I don't know if this code can solve your problem.
Anyway, this is what I do in the constructor of my subclassed QGraphicsView (after I set the scene):
setTransformationAnchor(AnchorUnderMouse);
setResizeAnchor(AnchorViewCenter);
I also use the above functions, after each call to:
MyGraphicsView->setTransform(transform);
I'm not sure if this can help you, but I hope so.