坐标系统
所谓坐标系统,也就是QPaintDevice上面的坐标。默认坐标系统位于设备的左上角,也就是坐标原点 (0, 0)。x 轴方向向右;y 轴方向向下。
将QPainter的逻辑坐标与QPaintDevice的物理坐标进行映射的工作,是由QPainter的变换矩阵(transformation matrix)、视口(viewport)和窗口(window)完成的。
在 Qt 的坐标系统中,每个像素占据 1×1 的空间。你可以把它想象成一张方格纸,每个小格都是1个像素。方格的焦点定义了坐标,也就是说,像素 (x, y) 的中心位置其实是在 (x + 0.5, y + 0.5) 的位置上。这个坐标系统实际上是一个“半像素坐标系”。我们可以通过下面的示意图来理解这种坐标系:
绘制矩形
当我们绘制矩形左上角 (1, 2) 时,实际绘制的像素是在右下方。
当绘制大于1个像素时,情况比较复杂:如果绘制像素是偶数,则实际绘制会包裹住逻辑坐标值;如果是奇数,则是包裹住逻辑坐标值,再加上右下角一个像素的偏移。
从上图可以看出,
1.如果实际绘制是偶数像素,则会将逻辑坐标值夹在相等的两部分像素之间;
2.如果是奇数,则会在右下方多出一个像素。
Qt 的这种处理,带来的一个问题是,我们可能获取不到真实的坐标值。由于历史原因,QRect::right()和QRect::bottom()的返回值并不是矩形右下角点的真实坐标值:QRect::right()返回的是
left() + width() – 1;QRect::bottom()则返回 top() + height() –1,上图的绿色点指出了这两个函数的返回点的坐标。
为避免这个问题,我们建议是使用QRectF。
QRectF使用浮点值,而不是整数值,来描述坐标。这个类的两个函数QRectF::right()和QRectF::bottom()是正确的。如果你不得不使用QRect,那么可以利用 x() + width() 和 y() + height() 来替代 right() 和 bottom() 函数。
坐标变换
QPainter是一个状态机。那么,有时我想保存下当前的状态:当我临时绘制某些图像时,就可能想这么做。当然,我们有最原始的办法:将可能改变的状态,比如画笔颜色、粗细等,在临时绘制结束之后再全部恢复。对此,QPainter提供了内置的函数:save()和restore()。save()就是保存下当前状态;restore()则恢复上一次保存的结果。这两个函数必须成对出现:QPainter使用栈来保存数据,每一次save(),将当前状态压入栈顶,restore()则弹出栈顶进行恢复。
Qt 提供了四种坐标变换:平移 translate,旋转 rotate,缩放 scale,扭曲 shear
平移 translate
旋转 rotate
缩放 scale
扭曲 shear
视口坐标和窗口坐标
Qt 的坐标分为逻辑坐标和物理坐标。
在我们绘制时,提供给QPainter的都是逻辑坐标。
之前我们看到的坐标变换,也是针对逻辑坐标的。
所谓物理坐标,就是绘制底层QPaintDevice的坐标。
单单只有逻辑坐标,我们是不能在设备上进行绘制的。要想在设备上绘制,必须提供设备认识的物理坐标。
Qt 使用 viewport-window 机制将我们提供的逻辑坐标转换成绘制设备使用的物理坐标,
方法是,在逻辑坐标和物理坐标之间提供一层“窗口”坐标。
视口是由任意矩形指定的物理坐标;
窗口则是该矩形的逻辑坐标表示。
默认情况下,物理坐标和逻辑坐标是一致的,都等于设备矩形。
窗口坐标(逻辑坐标)
PaintedWidget::PaintedWidget(QWidget *parent) : QWidget(parent) { resize(400, 400);//设置了窗口的大小和标题 setWindowTitle(tr("Paint Demo")); } void PaintDemo::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setWindow(0, 0, 200, 200); painter.fillRect(0, 0, 200, 200, Qt::red); }
如代码 没setWindows时 红色矩形为窗体四分之一 ,设置之后,红色矩形为整个窗体。 为什么呢?
窗口为逻辑坐标,将400400的窗体映射为了200200,原本对应1像素的单位变成了对应2像素的单位,
20012001=200200 原本的四分之一
20022002 = 400400 正好是物理像素大小 fillRect以新单位为单位
400*400即铺满大小
补充:
translate()函数只是简单地将坐标原点重新设置.
setWindow()则是将整个坐标系进行了修改。这段代码的运行结果是将整个窗口进行了填充。
painter.translate(200, 200);
我们将坐标原点设置到 (200, 200) 处,横坐标范围是 [-200, 200],纵坐标范围是 [-200, 200]
painter.setWindow(-160, -320, 320, 640);
坐标原点也是在窗口正中心,但是,我们将物理宽 400px 映射成窗口宽 320px,物理高 400px 映射成窗口高 640px,此时,横坐标范围是 [-160, 160],纵坐标范围是 [-320, 320]
视口(物理坐标)
PaintedWidget::PaintedWidget(QWidget *parent) : QWidget(parent) { resize(400, 400);//设置了窗口的大小和标题 setWindowTitle(tr("Paint Demo")); } void PaintDemo::paintEvent(QPaintEvent *) { QPainter painter(this); // painter.setWindow(0, 0, 400, 400);//等同于默认 painter.setViewport(0, 0, 200, 200); painter.fillRect(0, 0, 200, 200, Qt::red); }
如代码所示,没setViewPort之前 红色矩形 为窗体四分之一 setViewPort之后 红色矩形为窗体十六分之一
为什么呢?
视口为物理坐标,将400400物理像素 压缩成了200200物理像素 新单位为原本0.5像素 2000.52000.5 =
100100 像素 对应400*400的正好为十六分之一
总结
(窗口坐标)逻辑坐标 -》 窗体坐标 -》 (视口坐标)物理坐标
即setWindow():resize(400*400):setViewPort()