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.
Related
I have an orthographic camera in an osg scene. There are flat objects (planes) which will be displayed in that scene, and I want to be able to have the camera automatically point at them to as to view them head-on, and also to be zoomed to show as much of the plane as possible without chopping off any edges.
So far I've oriented myself correctly to look at the plane:
osg::Vec3d eye; // dummy
osg::Vec3d center; // we only really care about this here
osg::Vec3d up; // dummy
cameraManipulator->getViewMatrixAsLookAt(eye, center, up);
osg::Vec3d desiredVec(velocityX, velocityY, velocityZ); // velocity of the plane I want to look at
desiredVec.normalize();
desiredVec = -desiredVec; // invert
desiredVec += center; // align
cameraManipulator->setViewMatrixAsLookAt(desiredVec, center, osg::Vec3d(0, 0, -1));
This positions me so that I'm viewing the plane head-on, but it's zoomed in way too far, so I think what I need to do is offset my camera back a bit along the velocity vector by some value, and I don't know how to calculate that value. Something like this:
osg::Vec3d dir = desiredVec - center; // the direction (vector) in which we want to move
dir.normalize();
double scaleFactor = (width * height) / 2; // test
desiredVec += (dir * scaleFactor); // add it to desiredVec to move back in that direction (by scaleFactor)
The scaleFactor "test" stuff seems to work ok, it produces a view of the plane with a margin around the edge. I imagine this is what needs to change.
Basically, how do I calculate the distance I need to move the camera back by in order to view all of the current plane? I have information about the plane's velocity, size etc.
When using an orthographic camera, the size of the rendered objects (zoom effect) is not achieved by moving the camera, but by the size of the projection frustum you set on the camera.
None of the OSG camera manipulators implements a zooming effect for an ortho camera.
See this forum topic as a reference: http://forum.openscenegraph.org/viewtopic.php?t=10763&view=next
I'm trying to draw an icon(.png) inside a QWidget with QPainter::drawPixmap()
:
QPixmap _source = "/.../.png";
painter.setRenderHint(QPainter::HighQualityAntialiasing);
painter.drawPixmap(rect(), _source);
but in comparing to QLabel (for example) and in lower size (19*19 in my case) the result isn't perfect.
What can I do?
****Edit****
QLabel with pixmap # size 19*19:
My painting # size 19*19 via SmoothPixmapTransform render type:
You are setting the wrong render hint, you need QPainter::SmoothPixmapTransform to get smooth resizing. By default the nearest neighbor method is used, which is fast but has very low quality and pixelates the result.
QPainter::HighQualityAntialiasing is for when drawing lines and filling paths and such, i.e. when rasterizing geometry, it has no effect on drawing raster graphics.
EDIT: It seems there is only so much SmoothPixmapTransform can do, and when the end result is so tiny, it isn't much:
QPainter p(this);
QPixmap img("e://img.png");
p.drawPixmap(QRect(50, 0, 50, 50), img);
p.setRenderHint(QPainter::SmoothPixmapTransform);
p.drawPixmap(QRect(0, 0, 50, 50), img);
img = img.scaled(50, 50, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
p.drawPixmap(100, 0, img);
This code produces the following result:
There is barely any difference between the second and third image, manually scaling the source image to the desired dimensions and drawing it produces the best result. This is certainly not right, it is expected from SmoothTransformation to produce the same result, but for some reason its scaling is inferior to the scale() method of QPixmap.
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()
I have a mesh that has origin point at the bottom. I want to move it by -132 on Z axis. If I change the position of the Mesh it is on the correct position. But If I translate it on the Z axis by -132, the mesh is off by 20. Why am I not getting the same result?
The way I am translating the matrix:
matrix = new THREE.Matrix4().makeTranslation( 0, 0, -132 )
mesh.geometry.applyMatrix( matrix );
Here is the image of the mesh:
And here is image after the translation by 132. It's off by 20.
Some more info:
Position of the mesh is at:
419, -830, 500
and Rotation is:
0, -0.52, 0
So the Z coordinate is at 500; But I have to move it down by -132. If it move it by moving the position down 132 it is on correct position. But I want to translate the matrix to get the origin point down by 132.
Here is also the matrix:
"matrix": [0.8660253882408142,0,0.5,0,0,1,0,0,-0.5,0,0.8660253882408142,0,419,-830,500,1]
update after further clarifications and chat
The whole proint is that 3D transformatins are not commutative. This means that translating and then rotating is different that rotating and then translating (will produce different results). In some special cases these can coincide (e.g origins are at 0,0,0 and so on..), but in general they have different results.
Furthermore there is the issue with relative coordinates and nesting 3D objects inside other 3D objects. The final (world) transform of an object is affected whether it is inside other object or not.
Finally, the actual mesh position (local transform) versus vertices position plays a role in the way the mesh (and the geometry) will be (eventualy) projected onto 2D, so the projection angle changes (see above).
As clarified, the question is about shifting a mesh in such a way as to shift the origin, so further transformations (e.g rotations) can be done with respect to this shifted origin. A standard way to achieve this behaviour, in 3D programing, is to include pivots or wrappers around the mesh and position the mesh relatively inside with respect to the wrapper. Then apply any further transformations (e.g rotations) on the wrapper itself. This gives the effect that the mesh is rotating with respect to another axis (instead of the center).
The way this happens is that the wrapper indeed rotates around its own origin (i.e at 0,0,0) but the mesh inside is shifted so it appears as rotating with respect to another axis. A common example of this is modelling a 3D car wheel, which can rotate around its own axis (i.e spinning) but also it translates with the rest of the car. So one adds a wrapper around the wheel, where the wrapper is indeed translated with the rest of the car, but the wheel is rotated inside the wrapper as if no translation is present (kind of reverse situation of what you need here, but same difference).
You may optionaly want to check the MOD3 pivot modifier which creates custom pivots as origin points/axes (ps i'm author of the port). A wheel modifier is also included in MOD3 which solves what is described above as the wheel problem in 3D.
To use a wrapper 3D Object in your code do something like this:
// create the pivot wrapper
var pivot = new THREE.Object3D();
pivot.name = "PIVOT";
pivot.add( mesh );
scene.add( pivot );
// shift the mesh inside pivot
mesh.position.set(0,0,-132);
// position wrapper in the scene,
// position in the place where mesh would be
pivot.position.set(419, -830, 500);
pivot.rotation.set(0, -0.52, 0);
// now mesh appears rotated around the z = -132 axis instead of the center
// because the wrapper is actually rotated around its center,
// but its center coincides with the shifted mesh by z = -132
a related answer here
Well, i have my issues with gluCylinder(), it's not letting me do the things. What i'm trying to do is to give no-top to the cylinder and fill it inside.
void GLWindow::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1,0,0);
glBegin(GL_POLYGON);
GLUquadricObj *obj = gluNewQuadric();
gluCylinder(obj, 1.0, 1, 3, 30, 30);
glEnd();
}
So the question is, how do i give the Cylinder no-top (Means, it doesn't have top), i've been trying to modify the "top" parameter but had no success on it and how do i fill it inside?
So the question is, how do i give the Cylinder no-top (Means, it doesn't have top),
GLU doesn't support this. BTW: GLU is not part of OpenGL, its a companion library and has been phased out ages ago. Just implement your own cylinder drawing code.
how do i fill it inside?
You mean like a solid? With OpenGL you can't. Heck OpenGL doesn't even know cylinders or objects at all. All it sees are vertices it interprets as the attributes to draw single points, lines or triangles. There's no scene, there are no geometric objects.