Dimensioning in Qt - qt

I have graphicsView in which different items are drawn in the scene.I want to have an option for dimensioning i.e to dimension.
There are different items like Line, Circle, Ellipse, Arc, Point, Text,
I want whenver the the two points are clicked on any item,or the anywhere on the scene it should tell me its dimension.
The items are drawn with the mouseclicks. Can I get help to proceed?
The items are inherited from QGraphicsItem, LineItem.
Like:http://imgur.com/kBOjfmw

You can use something like this to get the length in inches:
qreal distance(QPointF & p1, QPointF & p2) {
return QLineF(p1, p2).length() / QApplication::screens().at(0)->physicalDotsPerInch();
}
Multiply by 2.54 if you want centimeters. Then create a QGraphicsItem which draws the line between the two points and text with the distance.
You can use void QWidget::mousePress/Release/Event(QMouseEvent * event) and get the click position from the event->pos(), and you should map to the scene coords when you are actually drawing the dimension.
If you don't want the actual line length but the horizontal or vertical length as in that image you posted as example, you can calculate that from the absolute difference between the two points x or y components.

Related

How to properly subtract a shape from a Path in JavaFX

My JavaFX application draws up to 10 different types of shapes. Some of them are complex and must be drawn with Path as a series of lines and arcs. Some of the shapes are negative areas where they are to be subtracted from any shapes they overlap.
I've found that subtracting from basic Javafx shapes such as Circle and Rectangle work as expected but shapes made from Path do not.
The overlapped shape shown:
The result of subtracting:
As you can see the shape is subtracted from the circle correctly but attempting to subtract the ellipse from the "fillet" shape only leaves a gap and the lines are darker/thicker because it is trying to close that gap by retracing back around to the other end of the gap instead of drawing the overlapped area.
Here's the code for drawing the complex shape shown in the image
//Here the v shape is drawn from right to left
Path path = new Path();
path.getElements().add(new MoveTo(firstLineXposition, firstLineYposition));
path.getElements().add(new LineTo(originX, originY));
path.getElements().add(new LineTo(secondLineXPos, secondLineYPos));
//here the arc is drawn from top left point to the top right point
ArcTo arc = new ArcTo();
arc.setX(firstLineXposition);
arc.setY(firstLineYposition);
arc.setRadiusX(radiusPositionX);
arc.setRadiusY(radiusPositionY);
path.getElements().add(arc);
path.getElements().add(new ClosePath());
I'm guessing that there is a missed step in drawing these shapes with Path, it should be treated as one whole shape and appears to be treated as a series of lines instead.
I assume your Path is not filled (has no fill color set). So I guess you are just intersecting with the area that is defined by the outline stroke.

Find center of a fixed-width bounding box

Given a collection of points, I'd like to find the center of a bounding box (fixed-length and width) that maximizes the number of points within said box. I'm at a loss for an efficient way to do this.
Algorithm with complexity O(N^2*logN) (I hope that better one exists):
Edit: article exploiting interval trees claims O(NlogN) complexity
Sort data array A by X coordinate.
Scan A with sweep line left to right.
For every point in A get LeftX = A[k].X - left coordinate of vertical band, find the rightmost coordinate of vertical band RightX = LeftX + Width.
Copy points inside the band to another array B.
Sort B by Y-coordinate.
Scan B width sweep line top to down.
For every point B[i] get TopY = B[i].Y - top coordinate of rectangle, calculate BottomY = TopY + Height.
Use binary search in B:
B[j] is the last bottom point in B with B[j].Y <= BottomY.
Find number of points in the current rectangle:
Number of points is N(k, i) = j - i + 1
Check whether N(k, i) is maximum among others
This seems like a difficult problem, here is my idea:
Hold a graph, each node holds a rectangle and a subset of points. the rectangle defines the area where placing the bounding box in would overlap all the points in the subset.
To build the graph:
Start with a root node holding the empty set and the rect [top:-inf, bottom:inf, left:-inf, right:inf]
For each point in the tree call this recursive function with the root node (pseudo code):
function addPoint(node, point)
// check that you didn't already try to add this point to this node
// node.tested can be a hash set
if(node.tested contains point)
return
node.tested.add(point)
newRect = node.rect.intersectWith(boundingBoxAround(point))
// if the bounding box around the point does not intersect the rectangle, return
if(newRect is invalid) // rect is invalid if right<left or bottom<top
return
node.addChild(new node(newRect, node.pointSet U {point})
for each child of node
addPoint(child, point)
Now you just pick the node with the largest subset, you can keep track of that when building the graph so you don't need to run through the graph again.
I hope my idea is clear, let me know if I can explain it better.

Scaling a rotated item based on top left moves the item

I would like to be able to rotate a QGraphicsItem based on its center, and scale it based on the top left corner.
When I try to combine rotation and scaling, the item also apparently moves...
#include <QApplication>
#include <QGraphicsView>
#include <QGraphicsTextItem>
void testTransformations(QGraphicsScene* s)
{
qreal angle = 30, scaleX = 2, scaleY = 1;
// Reference rotated not scaled
QGraphicsTextItem* ref = new QGraphicsTextItem("bye world");
ref->setFont(QFont("Arial", 20));
ref->setDefaultTextColor(Qt::green);
s->addItem(ref);
qreal center0X = ref->boundingRect().center().x();
qreal center0Y = ref->boundingRect().center().y();
QTransform t0;
t0.translate(center0X, center0Y);
t0.rotate(angle);
t0.translate(-center0X, -center0Y);
ref->setTransform(t0);
// Reference scaled not rotated
QGraphicsTextItem* ref1 = new QGraphicsTextItem("bye world");
ref1->setFont(QFont("Arial", 20));
ref1->setDefaultTextColor(Qt::yellow);
s->addItem(ref1);
QTransform t;
t.scale(scaleX, scaleY);
ref1->setTransform(t);
// Rotate around center of resized item
QGraphicsTextItem* yyy = new QGraphicsTextItem("bye world");
yyy->setDefaultTextColor(Qt::red);
yyy->setFont(QFont("Arial", 20));
s->addItem(yyy);
qreal center1X = yyy->boundingRect().center().x() * scaleX;
qreal center1Y = yyy->boundingRect().center().y() * scaleY;
// in my code I store the item size, either before or after the resize, and use it to determine the center - which is virtually the same thing as this for a single operation
QTransform t1;
t1.translate(center1X, center1Y);
t1.rotate(angle);
t1.translate(-center1X, -center1Y);
t1.scale(scaleX, scaleY);
yyy->setTransform(t1);
// rotated around center of bounding rectangle
QGraphicsTextItem* xxx = new QGraphicsTextItem("bye world");
xxx->setDefaultTextColor(Qt::blue);
xxx->setFont(QFont("Arial", 20));
s->addItem(xxx);
qreal center2X = xxx->boundingRect().center().x();
qreal center2Y = xxx->boundingRect().center().y();
QTransform t2;
t2.translate(center2X, center2Y);
t2.rotate(angle);
t2.translate(-center2X, -center2Y);
t2.scale(scaleX, scaleY);
xxx->setTransform(t2);
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QGraphicsScene s;
QGraphicsView view(&s);
s.setSceneRect(-20, -20, 500, 500);
view.show();
testTransformations(&s);
return app.exec();
}
Result:
green is rotated, not scaled (or scaled a different amount)
yellow is scaled, not rotated
blue is scaled, and rotated by the center of bounding rectangle (which is not resized)
red is scaled and rotated around center
It is evident now to me that the transformations operate correctly - if I resize and rotate an item, I get first the yellow then the red item.
Yet, what I need, is if an item is already rotated (green) then scaled, to behave like the blue - stretch in the same direction, without jumping, while if an item is first scaled, then rotated, to behave like the red... Even more complicated, the original item (green) may have had scaling applied, so my simple solution of using the bounding rectangle wouldn't work.
I have tried to calculate the change... Always with weird results.
Is it possible to scale a rotated item, based on top left, without also moving it, while also rotating it around its center ?
It may require incremental transformations, and would be odd to get different results based on the order they are applied.
Edit: I have been experimenting with position adjustments, since the transformations have failed, but I have not been able to get a formula for a transform function that will give me smooth visual transition of the type:
1) rotate item (pinned to center)
2) scale item (pinned to top left) without jumping
3) rotate item (pinned to center)
where step 2 would also include an offset in position. I just don't know how to do it.
The way I see it, in the fragment for the "red" transform, I would have o add a mapToScene(somePoint) before and after the transform, and perform a correction (moveBy) based on result.
This would not be a great fix, but still... If only I knew how to adjust the position of the item after resize so it doesn't jump, it would still be a fix...
OK, sorry for the late edit, it seems like it is impossible to directly achieve that in Qt.
You can however use simple trigonometry to calculate the offset from the center you get when rotating around the 0,0 origin and manually move the item to compensate for the displacement.
Just imagine a circle with center 0,0 and radius a line from the center of the circle to the center of the item's bounding box. As you rotate the item around the 0,0 origin, the item's bounding box center will always sit on the circle, so you can calculate the offset for a given angle of rotation and then rotate and adjust the position of the item to match the center of its previous state.
Here is a little illustration, as you can see, after rotation the item is moved by the offset in order to make it appear as if it rotated around its center and not around the top left. Naturally, since the origin is still 0,0 it will scale as you intended.
EDIT: Even easier, no trig, just using Qt functionality, this method will rotate the item around its center while the transform origin is top left:
static void offsetRotation(qreal angle, QGraphicsItem * i) {
QPointF c = i->mapToScene(i->boundingRect().center());
i->setRotation(angle);
QPointF cNew = i->mapToScene((i->boundingRect()).center());
QPointF offset = c - cNew;
i->moveBy(offset.x(), offset.y());
}
However, I noticed something odd - when the origin is set to top left, scaling doesn't really keep the top left corner in the same place as I expected based on my long experience with graphics design software. Instead the item will slowly drift away as its scale increases. The origin point does seem to have some effect on the scaling, but I would certainly not call it adequate by any measure. So depending on what exactly you want to achieve, you may have to use the same offset adjustment trick for scaling as well. As an item scales, it will keep on reporting the same position, but if you map that to the scene, you will realize it actually changes, so you can track that change and compensate in order to produce adequate behavior with a respective offsetScale method.
All this comes at a cost thou, your coordinates will end up being all over the place mess for the sake of keeping the visual output as expected. This may prove to be a complication later on. One solution would be to create your own "public" coordinates for your items, and internally manage all that mess to normalize the end result.
Hopefully someone else may offer a cleaner solution, from my experience with Qt it seems like the people who worked on the graphics classes have had little to no experience with graphics workflow, no doubt they were excellent programmers, but the end result is Qt's graphics classes are often counter-intuitive or even totally incapable of working in the manner people have come to expect from professional graphics software. Perhaps a more pragmatic mind may offer a better remedy for this problem.
This is my attempt to solve the problem - it works but it is not a great solution:
on any transformation, I store m31() and m32().
If transformation is scaling, I offset by change between old and new m31() and m32()

How to detect if my mouse position is within a rectangle c#?

I am trying to write an application to draw schematic diagrams which contain rectangles, lines and circles. Now I want to add another functionality to drag a rectangle to different position. The problem I am facing is to detect whether I have clicked within a rectangle or not. I know there is a function like Rectangle.Contains(Point). To use such method I need to use a for loop to check against each rectangle. If I have a large number of rectangles present, then its not wise to use this method. Is there any other way to do this task.
You need a computer graphics textbook, this and similar problems are often discussed.
If memory serves me, make sure the point is below the top edge of the rectangle, above the bottom edge, left of the right edge and right of the left edge.
Regarding testing a bunch of rectangles in a loop. Consider having a circle that each rectangle fits in, a bounding circle. First test to see if the point is farther from the origin of the circle than the radius of the circle. If so there is no need to test the rectangle, its a miss. OK, that was a very theoretical answer. In reality calculating the distance from the point to the origin can be a very expensive calculation, it involves a square root, it may be faster to do the four comparisons of the point in rectangle check. Again if memory servers me, we don't really care what the distance from the origin is only if it is greater than the radius. So only partially perform the distance calculation, omitting the final square root, and compare against the square of the radius. Of course you still need to experiment and profile to make sure this bounding circle check is faster than just doing the regular point in rectangle check and you need to make sure you will have sufficient misses to offset the hits where you will end up doing both the bounding circle and rectangle checks.
You need to use a spatial index to find quickly in which rectangle the mouse is. I suggest a R-tree, here is the theorical part:
http://en.wikipedia.org/wiki/R-tree
And the c#,implementation:
http://sourceforge.net/projects/cspatialindexrt/
Create an rtee, add your rectangles then call the rtree.nearest method with the mouse coordinate to know the rectangles containing the mouse cursor. You can play with the distance parameter.
Hope it helps,
Anben Panglose.
I would go about dividing the display region into a quadrant.
Then place the rectangles into top-left, top-right, bottom-left, bottom-right grids.
Placing them means, creating a list for every quadrant and placing the rectangles in it.
Once the point is clicked, determine which quarter it belongs to and search in those rectangles only. This approach reduces your linear search by 4 times.
Remember that you need to also take care of overlapping where the point can belong to many rectangles. Here the z-order of your rectangles matter. So while the list is maintained for a quadrant, it should be sorted with it's z-order as a key.
Hope this helps.
May be something like this?
public bool isRectangelContainPoint(RectangleF rec, PointF pt)
{
if (pt.X >= rec.Left && pt.X <= rec.Right && pt.Y <= rec.Bottom && pt.Y >= rec.Top)
return true;
else
return false;
}

Use window/viewport to flip QPainter y-axis

I'm using Qt 4.7 QPainter to draw some polygons, etc into a widget. I am hoping to alter the coordinate system so that (0,0) is at the center of my widget, and the x/y axis behave in a standard "Cartesian" way (ie. y increases going "up" and decreases going "down"). In other words, I want the coordinates to be "math"-like not "computer graphics"-like, if you know what I mean. :-)
I'm trying to do this using setViewport() and setWindow() rather than do the math myself, as it would be nice to be able to just call the draw methods directly with my coordinates.
Here's what I've got so far:
// Setup coordinates
double screenWidth = width();
double screenHeight = height();
double windowWidth = 100.0;
double windowHeight = (screenHeight / screenWidth) * windowWidth;
painter.setViewport(0, 0, screenWidth, screenHeight);
painter.setWindow(-(windowWidth / 2.0), -(windowHeight / 2.0), windowWidth, windowHeight);
// Draw stuff
painter.setPen(Qt::NoPen);
painter.setBrush(Qt::blue);
painter.drawRect(-10, -10, 20, 20);
Now this works just fine, in that it draws a nice blue square in the middle of the screen. The problem is, I have to say that the upper left corner is (-10, -10). I'd like to be able to make it (-10, 10), as that is what it would be in Cartesian coords.
I tried messing with setWindow/setViewport to get this "y-axis flip", but to no avail. This seems like a really easy/basic thing to do, but after scouring the Qt docs and the web, I can't figure it out!
Thanks,
Chris
Use class QMatrix. It specifies 2D transformations. QMatrix is set to QPainter.
But remember, in your case, if you convert your widget's coords to Cartesian coords, you will have to put first point at (-10,-10) (not at (-10,10) as you did mentioned) to draw a rect, which has center at (0,0), because Y-axis now grows up and X-Axis now grows right.
All you need is to transform your coord system this way:
translate origin from (0,0) to the middle of the widget:
scale Y-axis by -1 factor:
Here is the code, typed in paintEvent() function of a widget:
QPainter pn( this );
int w_2 = width() / 2;
int h_2 = height() / 2;
{ // X- and Y-Axis drawing
pn.setPen( Qt::blue );
pn.drawLine( 0, h_2, width(), h_2); // X-Axis
pn.drawLine( w_2, 0 , w_2, height() ); // Y-Axis
}
QMatrix m;
m.translate( w_2, h_2 );
m.scale( 1, -1 );
pn.setMatrix( m );
pn.setPen( Qt::NoPen );
pn.setBrush( QBrush( Qt::blue, Qt::Dense4Pattern ) );
pn.drawRect( -10, -10, 20, 20 );
result:
update apr 07, 2014
This question was asked a long time ago and many things have changed since. For those asking themselves the same question today (beginnings of 2014) then my personal answer is that since Qt 4.3 it is possible to solve problem with text flipping more easier.
You are right. Text also gets filpped because it is drawn with the same painter. You can draw text at the end, when all flipped drawings are done, if it is possible. This method is not convinient because of new calculations of texts position. Also you will need to drop settings for painter.
Now I would recommend you to use QGraphicsView, because of huge support of 2D painting. Also for each QGraphicsItem ItemIgnoresTransformations flag can be set, which allows it to ignore 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 graphics view is transformed
The above answer will also flip text, "p" will be "b". To avoid that you have to flip back the y-axis before text is drawn, and you have to change sign on y-coord for the text position when you draw it. This is a little bit ugly I think, or is there a better way?
As stated above, drawing text also appears flipped upside down. There is an easy solution to it, see below. We will temporary disable the world transform for the text drawing. Note that text is not scaled anymore.
in your painting code we want to draw text on coordinate QPointF P;
Painter pn( this );
// calculate the point with the transform
QPointF p = pm.transform().map(P);
// Disable Transform temporary
pn.setWorldMatrixEnabled(false);
// draw it ordinary, no scaling etc
pn.drawText(p, QString("HI FRIENDS!"));
// Enable the transform again
pn.setWorldMatrixEnabled(true);
I needed to flip the y-axis in order to paint lines and polygons using Qt from points defined in Java coordinates. I imagine others will need to do this in porting from Java to Qt coordinate systems. The discussion above was helpful. My solution was:
painter.translate(0,height());
painter.scale(1.0, -1.0);
and then proceed to draw the lines and polygons.

Resources