QWT moving canvas - qt

I'm using QWT library for my widget, there are some curves on the canvas, like this:
void Plot::addCurve1( double x, double y, const char *CurveName,
const char *CurveColor,const char *CurveType )
{
...
*points1 << QPointF(x, y);
curve1->setSamples( *points1 );
curve1->attach( this );
...
}
So, all my curves have the same coordinate system. I'm trying to build navigation interface, so I could put step into TextEdit (for example) and moving by using this step, or I could go the end/start of my defined curve.
I've found method in QwtPlotPanner class, that gives me such opportunity:
double QWT_widget::move_XLeft()
{
//getting step from TextEdit
QString xValStr = _XNavDiscrepancies->toPlainText();
double xVal = xVal.toDouble();
// moveCanvas(int dx, int dy) - the method of QwtPlotPanner
plot->panner->moveCanvas(xVal,0);
x_storage = x_storage - xVal;
return x_storage;
}
So it works ok, but displacement in pixels and I need to stick it to my defined curve and it's coordinate system.
Qwt User's Guide tells, that:
Adjust the enabled axes according to dx/dy
Parameters
dx Pixel offset in x direction
dy Pixel offset in y direction
And this is the only information I've found. How can I convert pixels step into my coordinat system step? I need to go to the end of my curve, so I should return the last QPointF(x,y) of my curve and convert it to pixel-step? Or maybe I'm using wrong class/method?
Thank you very much :)

Thanks to #Pavel Gridin:
(https://ru.stackoverflow.com/a/876184/251026)
"For conversion from pixels to coordinates and back there are two
methods: QwtPlot::transform and QwtPlot::invTransform"

Related

Lines thickness changes spontaneously when scaling QGraphicsView

I am drawing lines in Qt using Graphics View framework. Since i want my picture to take the same portion of space when the window is resized, I override MainWindow::resizeEvent, so that graphics view is rescaled according to the resize event:
void MainWindow::resizeEvent(QResizeEvent *event) {
int w = event->size().width(), h = event->size().height();
int prev_w = event->oldSize().width(), prev_h = event->oldSize().height();
if (prev_w != -1) {
int s1 = std::min(prev_w, prev_h), s2 = std::min(w, h);
qreal k = (qreal)s2 / s1;
std::cerr << k << std::endl;
ui->graphicsView->scale(k, k);
}
}
However, doing so, my lines (that should have thickness of 1 pixel) sometimes have different thickness after resize. As I understand, it happens because coordinates of the objects after transforming to the GraphicsView are real, so are sometimes drawn with different number of pixels. That is unacceptable! I want lines to have same 1-pixel thickness all the time.
So, my question is: what is the usual solution for this problem? For now (based on my assumption above) I can only think of deleting all objects and creating new with integer coordinates, but rescaled (manually).
You need to set your line drawing to "cosmetic" in the QPen. This makes the lines non-scalable. Otherwise, Qt scales the line widths along with the scaling of the view. Look up QPen::setCosmetic. By default, drawing lines is not cosmetic.

Perspective Projection effect correction

I was trying to plot 8 points in a 3D space from the 8 vertices of the above 3D sphare.
I used the following code:
#include "Coordinates2d.h"
#include "Point3d.h"
const double zoom = 500;
int main()
{
Coordinates2d::ShowWindow("3D Primitives!");
std::vector<Point3d> points;
points.push_back(Point3d(0,0,20));
points.push_back(Point3d(0,100,20));
points.push_back(Point3d(120,100,20));
points.push_back(Point3d(120,0,20));
points.push_back(Point3d(0,0,120));
points.push_back(Point3d(0,100,120));
points.push_back(Point3d(120,100,120));
points.push_back(Point3d(120,0,120));
for(int i=0 ; i<points.size() ; i++)
{
Coordinates2d::Draw(points[i], zoom);
}
Coordinates2d::Wait();
}
Where, the Point3D is like the following:
#ifndef _POINT_3D_
#define _POINT_3D_
#include "graphics.h"
#include "Matrix.h"
#include "Point2d.h"
#include <cmath>
#include <iostream>
struct Point3d
{
double x;
double y;
double z;
public:
Point3d();
Point3d(double x, double y, double z);
Point3d(Point3d const & point);
Point3d & operator=(Point3d const & point);
Point3d & operator+(int scalar);
bool operator==(Point3d const & point);
bool operator!=(Point3d const & point);
Point3d Round()
{
return Point3d(floor(this->x + 0.5), floor(this->y + 0.5), floor(this->z + 0.5));
}
void Show()
{
std::cout<<"("<<x<<", "<<y<<", "<<z<<")";
}
bool IsValid();
double Distance(Point3d & point);
void SetMatrix(const Matrix & mat);
Matrix GetMatrix() const;
Point2d ConvertTo2d(double zoom)
{
return Point2d(x*zoom/(zoom-z), y*zoom/(zoom-z));
}
};
#endif
#ifndef _COORDINATES_2D_
#define _COORDINATES_2D_
#include "graphics.h"
#include "Point2d.h"
#include "Point3d.h"
#include "Line3d.h"
class Coordinates2d
{
private:
static Point2d origin;
public:
static void Wait();
static void ShowWindow(char str[]);
private:
static void Draw(Point2d & pt);
public:
static void Draw(Point3d & pt, double zoom)
{
Coordinates2d::Draw(pt.ConvertTo2d(zoom));
}
};
#endif
I was expecting the output to be the following:
But the output became like the following:
I am actually interested to move my viewing camera.
How can I achieve my desired result?
I see from the comments that you achieved your desired result with a clever formula. If you're interested in doing it the 'standard' graphics way, using matrices, I hope this post will help you.
I found an excellent page written explaining projection matrices for OpenGL, which also extends to the general mathematics of projection.
If you want to go in depth, here is the very well written article, explains it's steps in detail, and is just overall highly commendable.
The below image shows the first part of what you're trying to do.
So the image on the left is the 'viewing volume' that you want your camera to see. You can see that in this case, the Center of Projection (basically the focal point of the camera) is at the origin.
But wait, you say, I don't WANT the center of projection to be at the origin! I know, we'll cover that later.
What we're doing here is taking the strangely shaped volume on the left, and converting it to what we call 'normalized coordinate' on the right. So we're mapping out viewing volume onto the range of -1 to 1 in each direction. Basically, we mathmatically stretch the irregularly shaped viewing volume into this 2x2x2 cube centered at the origin.
This operation is accomplished through the following matrix, again, from the excellent article I linked above.
So note you have six variables.
t = top
b = bottom
l = left
r = right
n = near
f = far
Those six variables define you viewing volume. Far is not labeled on the above image, but it is the distance of the furthest plane from the origin in the image.
The above image shows the projection matrix that puts out viewing volume into normalized coordinates. Once coordinates are in this form, you can make it flat by simply ignoring the z coordinate, which is similar to some of the work you have done (nice work!).
So we're all set with that for viewing things from the origin. But let's say we don't want to view from the origin, and would prefer to view from, say somewhere behind and to the side.
Well we can do that! but instead of moving our viewing area (we have the math all nicely worked out right here), it is perhaps counter intuitively, easier to move all the points we are trying to view.
This can be done by multiplying all of the points by a translation matrix.
Here is the wikipedia page for translation, from which I took the following matrix.
Vx, Vy, and Vz are the amount we want to move things in the x, y, and z directions. Keep in mind, if we want to move the camera in the positive x direction, we need a negative Vx, and vice versa. This is because we are moving the points instead of the camera. Feel free to try it and see, if you want.
You may also have noticed that both of the matrices I showed are 4x4, and your coordinates are 3x1. This is because the matrices are meant to be used with homogeneous coordinates. These seem strange because they use 4 variables to represent a 3D point, but its just x, y, z, and w, where you make w =1 for your points. I believe this variable is used for depth buffers, among other things, but it is basically ubiquitously present in graphics' matrix math, so you'll want to get used to using it.
Now that you have these matrices, you can apply the translation one to your points, then apply the perspective one to those points you got out. Then simply ignore the z components, and there you are! You have a 2D image from -1 to 1 in the x and y directions.

In Qt drawPoint method does not plot anything if negative valued parameters are supplies

in Qt creator drawPoint() method does not put point if negative valued parameters are passed
following is code for Bresenham's algorithm.but, it is not working in qt creator.it just plots circle in one quadrant.
Bresenham::Bresenham(QWidget*parent):QWidget(parent)
{}
void Bresenham::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e);
QPainter qp(this);
drawPixel(&qp);
}
void Bresenham::drawPixel(QPainter *qp)
{
QPen pen(Qt::red,2,Qt::SolidLine);
qp->setPen(pen);
int x=0,y,d,r=100;
y=r;
d=3-2*r;
do
{
qp->drawPoint(x,y);
qp->drawPoint(y,x);
qp->drawPoint(y,-x);
qp->drawPoint(x,-y);
qp->drawPoint(-x,-y);
qp->drawPoint(-y,-x);
qp->drawPoint(-x,y);
qp->drawPoint(-y,x);
if(d<0)
{
d=d+4*x+6;
}
else
{
d=d+(4*x-4*y)+10;
y=y-1;
}
x=x+1;
}while(x<y);
}
You need to translate the Qt coordinate system to the classic cartesian one. Choose a new center QPoint orig and replace all
qp->drawPoint(x,y);
with
qp->drawPoint(orig + QPoint(x,y));
The Qt coordinates system origin is at (0,0) and the y-axis is inverted. For instance, a segment from A(2,7) to B(6,1) look like this:
Notice how there is only the positive-x, positive-y quadrant. For simplicity assume that no negative coordinates exist.
Note:
For performance reasons it is better to compute all the points first and then draw them all using
QPainter::drawPoints ( const QPoint * points, int pointCount );

How to trace the missing pixels when using drawLine

We know that for drawing on an image in qt, qpainter is used. Recently, I used drawLine() function to draw whatever an user is scribbling. This was done by passing the lastPoint and currentPoint from the mouseMoveEvent to a custom function which implements drawLine(). I have passed the arguments for that custom function as given below:
void myPaint::mouseMoveEvent(QMouseEvent *event) {
qDebug() << event->pos();
if ((event->buttons() & Qt::LeftButton) && scribbling) {
pixelList.append(event->pos());
drawLineTo(event->pos());
lastPoint = event->pos();
}
}
Now with the help of qDebug() I noticed that some pixels are missed while drawing but the drawing is precise. I looked up the source of qt-painting where I saw that drawLine() was calling drawLines() which was making use of qpainterPath to have a shape drawn on the image.
My question is that, is there anyway to track these "missed" pixels or any approach to find all the pixels which have been drawn?
Thanks!
void myPaint::drawLineTo(const QPoint &endPoint) {
QPainter painter(image); //image is initialized in the constructor of myPaint
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::blue, myPenWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
painter.drawLine(lastPoint, endPoint);
modified = true;
lastPoint = endPoint; //at the mousePressEvent, the event->pos() will be stored as
// lastPoint
update();
}
For a start, don't draw in a mouseEvent(). Actually handling a mouseevent should be done as quick as possible. Also, it is not a good idea to look at the Qt source, it can be confusing. Rather assume that what Qt gives you work, and first try to answer "What I am doing wrong?". As I said drawing in a mouse event is definitely wrong.
Your description is really subjective, maybe an image of your output is better. Are you trying to emulate a pen (like in windows paint)? In this case do the mouse button has to be down ? is that the purpose of your variable scribbling?
There is more. following the documentation, QMouseEvent::buttons() always return a combination of all buttons for mouse move event. Which make sense : the mouse movements are independent of the buttons. It means
if ((event->buttons() & Qt::LeftButton)
will always be true.
Let's assume you want to draw the path of your mouse when the left button is pressed. Then you use something like :
void myPaint::mousePressEvent(QMouseEvent *event){
scribbling = true;
pixelList.clear();
}
void myPaint::mouseReleaseEvent(QMouseEvent *event){
scribbling = false;
}
void myPaint::mouseMoveEvent(QMouseEvent *event) {
if ( scribbling) {
pixelList.append(event->pos());
}
}
void myPaint::paintEvent(){
QPainter painter(this)
//some painting here
if ( scribbling) {
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(Qt::blue, myPenWidth, Qt::SolidLine, Qt::RoundCap,Qt::RoundJoin));
// here draw your path
// for example if your path can be made of lines,
// or you just put the points if they are close from each other
}
//other painting here
}
If after all of this you don't have a good rendering, try using float precision (slower), ie QMouseEvent::posF() instead of QMouseEvent::pos().
EDIT :
"I want to know whether there is any way to calculate all the sub-pixels between any two pixels that we send as arguments to drawLine"
Yes there is. I don't know why you need to do such thing but is really simple. A line can be characterized with the equation
y = ax + b
Both of the endpoints of the line p0 = (x0, y0) and p1 = (x1, y1) satisfy this equation so you can easily find a and b. Now all you need to do is increment from x0 to x1 by the amount of
pixels you want (say 1), and to compute the corresponding y value, each time saving point(x,y).
So will go over all of the points saved in pixelList and repeat this process for any two consecutive points.

QGraphicsItem's - selection & rotation

I'd like to implement application which allows user to select few QGraphicsItems and then rotate them as a group. I know that I could add all items into one QGraphicsItemGroup but I need to keep Z-value of each item. Is it possible?
I also have a second question.
I'm trying to rotate QGraphicsItem around some point (different from (0,0) - let's say (200,150)). After that operation I want to rotate this item once more time but now around (0,0). I'm using code below:
QPointF point(200,150); // point is (200,150) at first time and then it is changed to (0,0) - no matter how...
qreal x = temp.rx();
qreal y = temp.ry();
item->setTransform(item->transform()*(QTransform().translate(x,y).rotate(angle).translate(-x,-y)));
I noticed that after second rotation the item is not rotated around point (0,0) but around some other point (I don't know which). I also noticed that if I change order of operations it all works great.
What am I doing wrong?
Regarding your first problem, why should the z-values be a problem when putting them into a QGraphicsGroup?
On the other hand you could also iterate through the selected items and just apply the transformation.
I guess this snippet will solve your 2nd problem:
QGraphicsView view;
QGraphicsScene scene;
QPointF itemPosToRotate(-35,-35);
QPointF pivotPoint(25,25);
QGraphicsEllipseItem *pivotCircle = scene.addEllipse(-2.5,-2.5,5,5);
pivotCircle->setPos(pivotPoint);
QGraphicsRectItem *rect = scene.addRect(-5,-5,10,10);
rect->setPos(itemPosToRotate);
// draw some coordinate frame lines
scene.addLine(-100,0,100,0);
scene.addLine(0,100,0,-100);
// do half-cicle rotation
for(int j=0;j<=5;j++)
for(int i=1;i<=20;i++) {
rect = scene.addRect(-5,-5,10,10);
rect->setPos(itemPosToRotate);
QPointF itemCenter = rect->pos();
QPointF pivot = pivotCircle->pos() - itemCenter;
// your local rotation
rect->setRotation(45);
// your rotation around the pivot
rect->setTransform(QTransform().translate(pivot.x(), pivot.y()).rotate(180.0 * (qreal)i/20.0).translate(-pivot.x(),-pivot.y()),true);
}
view.setScene(&scene);
view.setTransform(view.transform().scale(2,2));
view.show();
EDIT:
In case you meant to rotate around the global coordinate frame origin change the rotations to:
rect->setTransform(QTransform().translate(-itemCenter.x(), -itemCenter.y()).rotate(360.0 * (qreal)j/5.0).translate(itemCenter.x(),itemCenter.y()) );
rect->setTransform(QTransform().translate(pivot.x(), pivot.y()).rotate(180.0 * (qreal)i/20.0).translate(-pivot.x(),-pivot.y()),true);

Resources