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() { }