Qt的坐标系统是由QPainter类控制的,而QPainter是在绘图设备上进行绘制的。一个绘图设备的默认坐标系统中,原点(0,0)在其左上角,x坐标向右增长,y坐标向下增长。
QPainter的逻辑坐标与绘图设备的物理坐标之间的映射由QPainter的变换矩阵、视口和窗口处理。逻辑坐标和物理坐标默认是一致的。QPainter也支持坐标变换(比如旋转和缩放)。相关内容可在Qt帮助中输入Coordinate System关键字查看。
基本变换
默认的,QPainter在相关设备的坐标系统上进行操作,但是它也完全支持仿射坐标变换。绘图时可以用QPainter::scale()函数缩放坐标系统,使用QPainter::rotate()函数顺时针旋转坐标系统,使用QPainter::translate()函数平移坐标系统,使用QPainter::shear()围绕原点扭曲坐标系统。
坐标系统的2D变换由QTransform类实现,QTransform类对象可以存储多个变换操作,当同样的变换需要多次使用时,建议使用QTransform类对象。
QPainter的坐标变换方法的应用实例:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
// 填充界面背景为白色
painter.fillRect(rect(), Qt::white);
painter.setPen(QPen(Qt::red, 11));
// 绘制一条线段
painter.drawLine(QPoint(5, 6), QPoint(100, 99));
// 将坐标系统进行平移,使(200, 150)点作为原点
painter.translate(200, 150);
// 开启抗锯齿
painter.setRenderHint(QPainter::Antialiasing);
// 重新绘制相同的线段
painter.drawLine(QPoint(5, 6), QPoint(100, 99));
// 保存painter的状态
painter.save();
// 将坐标系统旋转90度
painter.rotate(90);
painter.setPen(Qt::cyan);
// 重新绘制相同的线段
painter.drawLine(QPoint(5, 6), QPoint(100, 99));
// 恢复painter的状态
painter.restore();
painter.setBrush(Qt::darkGreen);
// 绘制一个矩形
painter.drawRect(-50, -50, 100, 50);
painter.save();
// 将坐标系统进行缩放
painter.scale(0.5, 0.4);
painter.setBrush(Qt::yellow);
// 重新绘制相同的矩形
painter.drawRect(-50, -50, 100, 50);
painter.restore();
painter.setPen(Qt::blue);
painter.setBrush(Qt::darkYellow);
// 绘制一个椭圆
painter.drawEllipse(QRect(60, -100, 50, 50));
// 将坐标系统进行扭曲
painter.shear(1.5, -0.7);
painter.setBrush(Qt::darkGray);
// 重新绘制相同的椭圆
painter.drawEllipse(QRect(60, -100, 50, 50));
}
结果:
QTransform类的应用实例
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
setMouseTracking(true);
QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
angle = 0;
this->resize(400,300);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *)
{
angle += 10;
if(angle == 360)
angle = 0;
int side = qMin(width(), height());
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
QTransform transform;
//Moves the coordinate system width()/2 along the x axis and height()/2 along the y axis, and returns a reference to the matrix.
transform.translate(width()/2, height()/2);
//Scales the coordinate system by side/300.0 horizontally and side/300.0 vertically, and returns a reference to the matrix.
transform.scale(side/300.0, side/300.0);
transform.rotate(angle);
painter.setWorldTransform(transform);
painter.drawEllipse(-120, -120, 240, 240);
painter.drawLine(0, 0, 100, 0);
}
结果:
窗口-视口转换
使用QPainter进行绘制时,会使用逻辑坐标进行绘制,然后再转换为绘图设备的物理坐标。逻辑坐标到物理坐标的映射由QPainter的worldTransform( )函数、QPainter的viewport( )以及window( )函数进行处理。其中,视口(viewport)表示物理坐标下指定的一个任意矩形,而窗口(window,与窗口部件的概念不同)表示逻辑坐标下的相同矩形。默认的,逻辑坐标和物理坐标是重合的,它们都相当于绘图设备上的矩形。
使用窗口-视口转换可以使逻辑坐标系统适合应用要求,令绘图代码独立于绘图设备。如下代码可使逻辑坐标以(-50, -50)为原点,宽为100,高为100。
QPainter painter(this);
painter.setWindow(QRect(-50,-50,100,100));
现在逻辑坐标的(-50, -50)对应绘图设备的物理坐标的(0,0)点。当设置窗口或者视口矩形时,实际上是执行了坐标的一个线性变换,窗口的4个角会映射到视口对应的4个角,反之亦然。因此,一个很好的一个办法是让视口和窗口维持相同的宽高比来防止形变。如果设置了逻辑坐标系统为一个正方形,那么也需要设置视口为一个正方形,如下代码将视口设置为适合绘图设备矩形的最大矩形:
int side = qMin(width(),height());
int x = (width()-side/2);
int y = (height()-side/2);
painter.setViewport(x,y,side,side);
一个应用实例:
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
setMouseTracking(true);
this->resize(400,300);
}
Widget::~Widget()
{
delete ui;
}
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setWindow(-50, -50, 100, 100);
painter.setBrush(Qt::green);
painter.drawRect(0, 0, 20, 20);
}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
QString pos = QString("%1,%2").arg(event->pos().x()).arg(event->pos().y());
QToolTip::showText(event->globalPos(), pos, this);
}
![](https://i-blog.csdnimg.cn/blog_migrate/ba5c30a648053c9722182a27666ce1be.png)
可以看出绘制的正方形发生了形变,成为了一个长方形。这是由于我们设置widget的宽为400,高为300,而我们设置逻辑坐标矩形为以(-50,-50)为起点,宽100,高100。按比例对应,在水平方向,逻辑坐标的一个单位对应物理坐标的4个单位;在垂直方向,逻辑坐标的一个单位对应物理坐标的3个单位。所以逻辑坐标中宽20、高20的矩形在物理坐标中就是宽80、高60的矩形。为了防止形变,需要将视口的宽和高的对应比例设置为相同值,即把视口也设置为一个正方形。更改paintEvent()函数如下:
void Widget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
int side = qMin(width(), height());
int x = (width() / 2);
int y = (height() / 2);
// 设置视口
painter.setViewport(x, y, side, side);
painter.setWindow(0, 0, 100, 100);
painter.setBrush(Qt::green);
painter.drawRect(0, 0, 20, 20);
}
结果: