Hello Qt-QT coordinate system

Posted by feign3 on Thu, 03 Mar 2022 19:43:26 +0100

1. Introduction to the QT coordinate system

Each window in Qt has a coordinate system. The upper left corner of the default window is the coordinate origin, and then increases horizontally to the right, decreases horizontally to the left, increases vertically to the bottom, and decreases vertically to the top. The origin is the (0, 0) point, increasing or decreasing in pixels.

1. Physical Coordinate System

The physical coordinate system is the device coordinate system, where the origin is in the upper left corner, in pixels, the X coordinate grows to the right, and the Y coordinate grows down.

2. Logical Coordinate System

An Abstract coordinate system whose units are determined by a specific problem and whose direction of growth is determined by a specific problem.

QPainter draws graphics using a logical coordinate system, in which the size and location of graphics are converted to a physical coordinate system and then drawn on the device. By default, the logical coordinate system is the same as the physical coordinate system.

3. Viewport

A view port is any specified rectangular area in a physical coordinate system.

4. Windows

A window is a rectangular area corresponding to a physical coordinate system in a logical coordinate system.

Viewport and window are the same rectangle in different coordinate systems. There is a mapping relationship between the viewport and the coordinate points in the window. Viewport and window can convert each other by coordinate transformation.

2. Coordinate system transformation

The mapping of QPainter logical coordinates to QPaintDevice physical coordinates is done by the transformation matrix, viewport and window of QPainter. By default, physical coordinates are coincident with logical coordinate systems, and QPainter supports coordinate conversion, such as rotation and scaling.

Coordinate system transformations are performed using transformation matrices, which are usually set using the QTransform class. The QPainter class provides transformation functions for coordinate systems such as translation, scaling, rotation, and warping.

void translate(const QPointF & offset)

void translate(const QPoint & offset)

void translate(qreal dx, qreal dy)

void scale(qreal sx, qreal sy)

void shear(qreal sh, qreal sv)

void rotate(qreal angle)

void save()

void restore()

1. Shift transformation

QT uses the translate() function for translation transformation.

Shift transformation code:

    QPainter painter(this);

    painter.setBrush(Qt::yellow);

    painter.drawRect(0,0,50,50);

    //Set the point under the current coordinate system (100, 100) as the origin

    painter.translate(100,100);

    painter.setBrush(Qt::red);

    painter.drawRect(0,0,50,50);

    //Set the point (-100, -100) under the current coordinate system as the origin

    painter.translate(-100,-100);

    painter.drawLine(0,0,20,20);

2. Scale transformation

scale() function is used for scaling.

Scale transformation code:

    QPainter painter(this);

    painter.setBrush(Qt::yellow);

    painter.drawRect(0,0,100,100);

    //Double the coordinate system in X and Y

    painter.scale(2,2);

    painter.setBrush(Qt::red);

    painter.drawRect(50,50,50,50);

3. Warp transformation

The shear() function is used to perform the warp transformation.

Warp transformation code:

    QPainter painter(this);

    painter.setBrush(Qt::yellow);

    painter.drawRect(0,0,50,50);

    //Warp the y-axis of the current coordinate system

    painter.shear(0,1);

    painter.setBrush(Qt::red);

    painter.drawRect(50,0,50,50);

4. Reversion and transformation

The rotate() function is used for the flip transformation.

Flip transform code:

    QPainter painter(this);

    //Rotate 30 degrees clockwise, centered on the origin

    painter.rotate(30);

    painter.drawLine(0,0,100,0) ;

    //Rotate 30 degrees clockwise, centered on the origin

    painter.rotate(30);

    painter.drawLine(0,0,100,0);

5. Protection of coordinate system state

The drawing process requires fast coordinate system switching and drawing different graphics, so the coordinate system state needs to be protected. You can use the save() function to save the current state of the coordinate system, then perform the transformation operation, and then use the restore() function to restore the previous coordinate system state. Essentially, the operation is to stack and stack the coordinate system.

Coordinate system state protection code:

    QPainter painter(this);

    //Save the current coordinate system state

    painter.save();

    //Shift coordinate system origin to (100, 100)

    painter.translate(100,100);

    painter.drawLine(0,0,50,50);

    //Restore the coordinate system with the current origin at the point (100, 100) to the saved coordinate system state

    painter.restore();

    painter.drawLine(0,0,50,50);

3. Drawing examples

1. Sine wave drawing

SineWave.h file:

#ifndef SINEWAVE_H
#define SINEWAVE_H


#include <QWidget>
#include <QPainter>

class SineWave : public QWidget
{
    Q_OBJECT
private:
    void drawBackGround(QPainter* painter);
    void drawCave(QPainter* painter);
    void paintEvent(QPaintEvent *event);
public:
    SineWave(QWidget *parent = 0);
    ~SineWave();
};

#endif // SINEWAVE_H

SineWave.cpp file:

#include "SineWave.h"
#include <QPen>
#include <QPointF>
#include <qmath.h>

SineWave::SineWave(QWidget *parent):QWidget(parent)
{

}

SineWave::~SineWave()
{

}

void SineWave::drawBackGround(QPainter* painter)
{
    QPen pen;
    const double delta = 1;
    pen.setStyle(Qt::SolidLine);
    painter->setViewport(50, 50, width()-100, height()-100);
    painter->setWindow(-10, 2, 20, -4); // (-10, 2)    (10, -2)
    painter->fillRect(-10, 2, 20, -4, Qt::black);

    pen.setColor(Qt::white);
    pen.setWidthF(painter->window().width()/painter->viewport().width() * delta);

    painter->setPen(pen);
    painter->drawLine(QPointF(-10, 1.5), QPointF(10, 1.5));
    painter->drawLine(QPointF(-10, 1), QPointF(10, 1));
    painter->drawLine(QPointF(-10, 0.5), QPointF(10, 0.5));
    painter->drawLine(QPointF(-10, -0.5), QPointF(10, -0.5));
    painter->drawLine(QPointF(-10, -1), QPointF(10, -1));
    painter->drawLine(QPointF(-10, -1.5), QPointF(10, -1.5));

    pen.setWidthF(painter->window().height()/painter->viewport().height() * delta);
    painter->setPen(pen);
    painter->drawLine(QPointF(-8, 2), QPointF(-8, -2));
    painter->drawLine(QPointF(-6, 2), QPointF(-6, -2));
    painter->drawLine(QPointF(-4, 2), QPointF(-4, -2));
    painter->drawLine(QPointF(-2, 2), QPointF(-2, -2));
    painter->drawLine(QPointF(2, 2), QPointF(2, -2));
    painter->drawLine(QPointF(4, 2), QPointF(4, -2));
    painter->drawLine(QPointF(6, 2), QPointF(6, -2));
    painter->drawLine(QPointF(8, 2), QPointF(8, -2));

    pen.setWidthF(painter->window().width()/painter->viewport().width() * delta*10);
    pen.setColor(Qt::green);
    painter->setPen(pen);
    painter->drawLine(QPointF(-10, 0), QPointF(10, 0));   // x
    pen.setWidthF(painter->window().height()/painter->viewport().height() * delta*10);
    painter->setPen(pen);
    painter->drawLine(QPointF(0, 2), QPointF(0, -2));   // y
}


void SineWave::SineWave::drawCave(QPainter* painter)
{
    for(double x=-10; x<10; x+=0.01)
    {
          double y = qSin(x);
          painter->drawPoint(QPointF(x, y));
    }
}

void SineWave::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    drawBackGround(&painter);
    drawCave(&painter);
}

2. Drawing board

Functional requirements: Graphics, straight lines, rectangles, ellipses can be drawn freely, and line colors can be selected for drawing.

A. Free Graphic Drawing

Free drawing must record the coordinates of all points that the mouse moves through, and drawing parameters must have the ability to save all coordinates.

The drawing scheme is as follows:

a. Press the mouse to start drawing and record the starting coordinates

b. Record the coordinates of the points that the mouse moves through

c. End drawing when mouse releases, record end coordinates

d. Draw straight lines between adjacent points in record order

B. Basic Graphics Drawing

When drawing basic graphics, the coordinates of the starting point of the graphics are determined when the mouse is pressed, the graphics are drawn in real-time coordinates when the mouse moves, the final end coordinates are determined when the mouse is released, and the graphics are drawn. Therefore, only two coordinates need to be recorded for basic graphic drawing.

The drawing scheme is as follows:

a. Press the mouse to start drawing and record the starting coordinates

b. Use each coordinate passed by the mouse as a temporary end coordinate

c. End drawing with mouse release, record final end coordinates

d. Draw graphics between start and end coordinates

Widget.h file:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QRadioButton>
#include <QComboBox>
#include <QGroupBox>
#include <QList>
#include <QPoint>

class Widget : public QWidget
{
    Q_OBJECT
    enum DrawType
    {
        NONE,
        FREE,
        LINE,
        RECT,
        ELLIPSE
    };
    struct DrawParam
    {
        DrawType type;
        Qt::GlobalColor color;
        QList<QPoint> points;
    };

    QGroupBox m_group;
    QRadioButton m_free;
    QRadioButton m_line;
    QRadioButton m_rect;
    QRadioButton m_ellipse;
    QComboBox m_color;

    QList<DrawParam> m_drawList;
    DrawParam m_current;

    DrawType drawType();
    Qt::GlobalColor drawColor();
    void draw(QPainter& painter, DrawParam& param);
    void append(QPoint p);
protected:
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
    void paintEvent(QPaintEvent *event);
public:
    Widget(QWidget* parent = 0);
    ~Widget();
};

#endif // WIDGET_H

Widget.cpp file:

#include "Widget.h"
#include <QMouseEvent>
#include <QPainter>
#include <QPen>
#include <QBrush>

Widget::Widget(QWidget *parent):QWidget(parent)
{
    m_group.setParent(this);
    m_group.setTitle("Setting");
    m_group.resize(600, 65);
    m_group.move(20, 20);

    m_free.setParent(&m_group);
    m_free.setText("Free");
    m_free.resize(70, 30);
    m_free.move(35, 20);
    m_free.setChecked(true);

    m_line.setParent(&m_group);
    m_line.setText("Line");
    m_line.resize(70, 30);
    m_line.move(140, 20);

    m_rect.setParent(&m_group);
    m_rect.setText("Rect");
    m_rect.resize(70, 30);
    m_rect.move(245, 20);

    m_ellipse.setParent(&m_group);
    m_ellipse.setText("Ellipse");
    m_ellipse.resize(70, 30);
    m_ellipse.move(350, 20);

    m_color.setParent(&m_group);
    m_color.resize(80, 25);
    m_color.move(480, 23);
    m_color.addItem("Black");
    m_color.addItem("Blue");
    m_color.addItem("Green");
    m_color.addItem("Red");
    m_color.addItem("Yellow");

    setFixedSize(width(), 600);

    m_current.type = NONE;
    m_current.color = Qt::white;
    m_current.points.clear();
}

Widget::DrawType Widget::drawType()
{
    DrawType ret = NONE;

    if( m_free.isChecked() )    ret = FREE;
    if( m_line.isChecked() )    ret = LINE;
    if( m_rect.isChecked() )    ret = RECT;
    if( m_ellipse.isChecked() ) ret = ELLIPSE;

    return ret;
}

Qt::GlobalColor Widget::drawColor()
{
    Qt::GlobalColor ret = Qt::black;

    if( m_color.currentText() == "Black")    ret = Qt::black;
    if( m_color.currentText() == "Blue")     ret = Qt::blue;
    if( m_color.currentText() == "Green")    ret = Qt::green;
    if( m_color.currentText() == "Red")      ret = Qt::red;
    if( m_color.currentText() == "Yellow")   ret = Qt::yellow;

    return ret;
}


void Widget::mousePressEvent(QMouseEvent *event)
{
    m_current.type = drawType();
    m_current.color = drawColor();
    m_current.points.append(event->pos());
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    append(event->pos());

    update();
}

void Widget::mouseReleaseEvent(QMouseEvent *event)
{
    append(event->pos());

    m_drawList.append(m_current);
    m_current.type = NONE;
    m_current.color = Qt::white;
    m_current.points.clear();

    update();
}

void Widget::append(QPoint p)
{
    if( m_current.type != NONE )
    {
        if( m_current.type == FREE )
        {
            m_current.points.append(p);
        }
        else
        {
            if( m_current.points.count() == 2 )
            {
                m_current.points.removeLast();
            }

            m_current.points.append(p);
        }
    }
}

void Widget::paintEvent(QPaintEvent *)
{
    QPainter painter(this);

    for(int i=0; i<m_drawList.count(); i++)
    {
        draw(painter, m_drawList[i]);
    }

    draw(painter, m_current);
}

void Widget::draw(QPainter& painter, DrawParam& param)
{
    if( (param.type != NONE) && (param.points.count() >= 2) )
    {
        int x = (param.points[0].x() < param.points[1].x()) ? 
                    param.points[0].x() : param.points[1].x();
        int y = (param.points[0].y() < param.points[1].y()) ? 
                    param.points[0].y() : param.points[1].y();
        int w = qAbs(param.points[0].x() - param.points[1].x()) + 1;
        int h = qAbs(param.points[0].y() - param.points[1].y()) + 1;

        painter.setPen(QPen(param.color));
        painter.setBrush(QBrush(param.color));

        switch(param.type)
        {
        case FREE:
            for(int i=0; i<param.points.count()-1; i++)
            {
                painter.drawLine(param.points[i], param.points[i+1]);
            }
            break;
        case LINE:
            painter.drawLine(param.points[0], param.points[1]);
            break;
        case RECT:
            painter.drawRect(x, y, w, h);
            break;
        case ELLIPSE:
            painter.drawEllipse(x, y, w, h);
            break;
        default:
            break;
        }
    }
}

Widget::~Widget()
{

}

 

Topics: Qt