【Qt图形视图框架】2-坐标系统

坐标系统

Qt坐标系统是由QPainter类控制的。与QPaintDeviceQPaintEngine类一起,QPainter构成了Qt绘画系统Arthur的基础。QPainter是用来执行绘图操作的,QPaintDevice是一个二维空间的抽象,可以使用QPainter进行绘图,QPaintEngine提供了 QPainter在不同类型QPaintDevice上绘图的接口。
QPaintDevice类是可绘制对象的基类:它的绘制功能被QWidget、QImage、QPixmap、QPicture和QOpenGLPaintDevice类继承。绘制设备的默认坐标系统的原点位于左上角。x值向右增加,y值向下增加。默认单位是基于像素的设备上的一个像素和打印机上的一个点(1/72英寸)。
QPainter逻辑坐标到物理QPaintDevice坐标的映射是通过QPainter的转换矩阵、视口和“窗口”进行处理的。默认情况下,逻辑和物理坐标系统是重合的。QPainter也支持坐标转换(例如旋转和缩放)。

渲染

逻辑表示法

图形原图元的大小(宽度和高度)通常如下对应它的数学模型,忽略渲染笔的宽度:
在这里插入图片描述

锯齿绘制

当绘制时,像素渲染是由QPainter::Antialiasing呈现的。

RenderHint枚举用于指定QPainter的标志,可能会被任何给定的引擎接受,也可能不会。如果QPainter::Antialiasing被接受,表明引擎需要抗锯齿图元的边缘,比如使用不同的颜色强度平滑边缘。

RenderHint枚举用于指定QPainter的标志,可能会被任何给定的引擎接受,也可能不会。如果QPainter::Antialiasing被接受,表明引擎需要抗锯齿图元的边缘,比如使用不同的颜色强度平滑边缘。

但默认情况下,painter有别名且其他规则也适用:当使用一个像素宽的笔进行渲染时,像素将被渲染到数学定义的点的右边和下面。例如:
在这里插入图片描述
当渲染钢笔使用偶数个像素,像素将围绕数学定义的点进行对称渲染,而渲染钢笔使用奇数个像素,行列方向上不对称的多出来的一个像素将被渲染到数学点的右侧和下方。具体例子见下面的QRectF图。
在这里插入图片描述
注意:由于历史原因,QRect::right()和QRect::bottom()函数的返回值偏离了矩形的真正的右下角。

QRect的right()函数返回left() + width() - 1, bottom()函数返回top() + height() - 1。图中右下角的绿色点显示了这些函数的返回坐标。

建议只使用QRectF代替: QRectF类在平面中使用浮点坐标来定义一个矩形(QRect使用整数坐标),QRectF::right()和QRectF::bottom()函数返回的是真正的右下角。或者使用QRect时,使用x() + width()和y() + height()来返回右下角,避免使用right()和bottom()函数。

抗锯齿绘制

如果你设置了QPainter的抗锯齿渲染提示,像素将在数学定义点的两边对称渲染。
在这里插入图片描述

变换

默认情况下,QPainter在自己的坐标系统上操作相关设备,但它也完全支持坐标仿射转换。

可以使用QPainter::scale()函数按给定的偏移量缩放坐标系统,使用QPainter::rotate()函数顺时针进行旋转,使用QPainter::translate()函数进行平移(如对一系列点给定偏移量)。
在这里插入图片描述
使用QPainter::shear()函数可以绕着原点旋转坐标系。所有的转换操作都在QPainter的转换矩阵上进行,QPainter::worldTransform()函数可以返回该变换矩阵,使用该矩阵可以将平面上的一点变换为另一点。

如果需要重复相同的转换动作,可以使用QTransform对象和QPainter::worldTransform()和QPainter::setWorldTransform()函数。可以通过调用QPainter::save()函数来保存QPainter的转换矩阵。QPainter::restore()函数会重新恢复该矩阵。

转换矩阵的一个常见需求是在各种绘图设备上重用相同的绘图代码。如果没有变换,绘制效果与显示设备的分辨率有关。如打印机达到600点每英寸的高分辨率,而屏幕通常只有有72到100点每英寸。

模拟时钟的例子

这个例子演示了如何使用QPainter的转换和缩放功能来简化绘图。建议在进一步阅读之前编译并运行这个示例。特别是尝试将窗口调整为不同的大小。
在这里插入图片描述

void AnalogClockWindow::render(QPainter *p)
{
    static const QPoint hourHand[3] = {
        QPoint(7, 8),
        QPoint(-7, 8),
        QPoint(0, -40)
    };// 时针角点,相对平移旋转后的坐标系
    static const QPoint minuteHand[3] = {
        QPoint(7, 8),
        QPoint(-7, 8),
        QPoint(0, -70)
    };// 分针角点,相对平移旋转后的坐标系

    QColor hourColor(127, 0, 127);// 时针颜色
    QColor minuteColor(0, 127, 127, 191);// 分针颜色

    p->setRenderHint(QPainter::Antialiasing);// 设置渲染方式为抗锯齿渲染
    p->translate(width() / 2, height() / 2);// 平移绘制原点至显示窗口中心

    int side = qMin(width(), height());// 获取显示窗口短边,确保时钟位于窗口最短边内
    p->scale(side / 200.0, side / 200.0);// 使用比例系数为side/200进行缩放以使用-100到100之间的x,y坐标

上述例子转换坐标系统,使点(0,0)位于小部件的中心,而不是左上角。还将系统坐标系使用比例系数为side/200进行缩放以使用-100到100之间的x,y坐标,其中side是窗口部件的宽度或高度,取两者中最短的。最终时钟以正方形展示。时钟将尽可能大的显示在方形中,并适应窗口部件的大小。

另请参阅窗口-视口转换一节。

    QTime time = QTime::currentTime();

    p->save();
    p->rotate(30.0 * ((time.hour() + time.minute() / 60.0)));
    p->drawConvexPolygon(hourHand, 3);
    p->restore();

通过旋转坐标系统并调用QPainter::drawConvexPolygon()来绘制时钟的时针。
多边形被指定为交替的x、y值数组,存储在hourHand静态变量中(在函数开始时定义),它对应于四个点(2,0)、(0,2)、(- 2,0)和(0,-25)。
围绕代码对QPainter::save()和QPainter::restore()的调用保证了后面的代码不会被我们使用的转换所干扰。

 	p->save();
    p->rotate(6.0 * (time.minute() + time.second() / 60.0));
    p->drawConvexPolygon(minuteHand, 3);
    p->restore();

上述代码对时钟的分针做同样的操作,分针由四个点(1,0)、(0,1)、(- 1,0)和(0,-40)定义。这些坐标指定了一个比分针更细更长的指针。

    p->setPen(minuteColor);

    for (int j = 0; j < 60; ++j) {
        if ((j % 5) != 0)
            p->drawLine(92, 0, 96, 0);
        p->rotate(6.0);
    }

有关转换矩阵的更多信息,请参阅QTransform文档。

窗口-视口(Window-Viewport)转换

当使用QPainter绘图时,我们使用逻辑坐标指定点,然后将其转换为绘制设备的物理坐标。
逻辑坐标到物理坐标的映射由QPainter的世界转换worldTransform()处理(在转换部分描述),以及QPainterviewport()和window()处理。视口表示指定任意矩形的物理坐标。“窗口”用逻辑坐标描述相同的矩形。在默认情况下,逻辑和物理坐标系重合,并相当于油漆设备的矩形。
使用窗口-视口转换,可以使逻辑坐标系统符合偏好。其还可用于使绘图代码独立于绘制装置。例如,可以通过调用QPainter::setWindow()函数,使逻辑坐标从(- 50,- 50)扩展到(50,50),中间是(0,0):

QPainter painter(this);
painter.setWindow(QRect(-50, -50, 100, 100));

通过上述操作,逻辑坐标(-50,-50)对应于绘制设备的物理坐标(0,0)。绘制代码将独立于绘制设备,始终操作指定的逻辑坐标。
我们可以通过设置“窗口”或视口矩形执行坐标的线性转换。注意,“窗口”的每个角落都映射到视口的相应角落,反之亦然。因此,通常让视口和“窗口”保持相同的纵横比以防止变形:

int side = qMin(width(), height())
int x = (width() - side / 2);
int y = (height() - side / 2);

painter.setViewport(x, y, side, side);

如果将逻辑坐标系设置为正方形,应该使用QPainter::setViewport()函数将视口设置为正方形。在上面的例子中,使其等价于绘图设备的矩形中最大的正方形。通过在设置窗口或视口时考虑绘制设备的大小,可以使绘图代码独立于绘制设备。
注意窗口-视口转换只是一个线性转换,即不执行裁剪。这意味着如果在当前设置的“窗口”外绘制,绘制仍然使用相同的线性代数方法转换到视口。
在这里插入图片描述
视口、“窗口”和转换矩阵决定了QPainter坐标映射到绘制设备物理坐标的逻辑方式。默认情况下,世界转换矩阵是单位矩阵,“窗口”和视口设置等价于绘制设备的设置,即世界、“窗口”和设备坐标系是等价的,但正如我们所看到的,系统可以通过转换操作和窗口-视口转换进行操作。上面的插图描述了这个过程。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值