QML 进阶---Coordinate Transformations

QML 进阶—Coordinate Transformations

Qt的坐标变换

一、坐标系统
Qt的坐标系由QPainter类控制。QPaintDevice,QPaintEngine,QPainter这三个类构成了Qt绘画系统的基础。QPainter用于执行绘图操作,QPaintDevice是可以使用QPainter来绘制的抽象二维空间,而QPainteEngine提供了painter在不同类型设备上绘制的接口。
QPaintDevice类是可以绘制的对象的基类,其绘图功能被QWidget,QImage,QPixmap,QPicture和QOpenGLPaintDevice类继承。绘图设备的默认坐标系起源于左上角。x值向右增加,y值向下增加。 默认单位是一个像素(基于像素设备),打印机上的一点(1/72英寸)。QPainter的逻辑坐标到物理坐标的映射是由其transformation matrix, viewport, “window”来处理的。逻辑和物理坐标系默认为一致。QPainter还支持坐标变换(例如旋转和缩放)。

二、渲染
2.1 Logical Representation
图形基元的大小(宽度和高度)总是与其数学模型相对应,忽略了渲染笔的宽度:

QML 进阶---Coordinate Transformations_第1张图片
2.2 Aliased Painting
绘制时,像素渲染由QPainter :: Antialiasing渲染标识符控制。对于任意给定的引擎,QPainter可能遵守也可能不遵守(引擎规则),RenderHint枚举用来指定这个标识。QPainter :: Antialiasing值表示如果可能,引擎应该对原语的边缘进行边缘化,即通过使用不同的颜色强度来平滑边缘。
但默认情况下,painter是aliased(不抗锯齿的),其他规则也适用。当使用一个像素宽的笔渲染时,像素将呈现在数学定义点的右侧和下方:

QML 进阶---Coordinate Transformations_第2张图片

当用具有偶数个像素宽的笔渲染时,像素将以数学定义的点对准地呈现,然而当用具有奇数个像素宽的笔渲染时,备用像素将被渲染在数学点的右侧和下方,如一个像素的情况:

QML 进阶---Coordinate Transformations_第3张图片
请注意,由于历史原因,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()函数。

2.3 Anti-aliased Painting
如果您设置了QPainter抗锯齿渲染标识符,则像素将在数学定义点的两边对称渲染:
QML 进阶---Coordinate Transformations_第4张图片

三、Transformations
默认情况下,QPainter运行在相关设备的自己的坐标系中,但也完全支持仿射坐标变换。您可以使用QPainter :: scale()函数缩放坐标系给定的偏移量,您可以使用QPainter :: rotate()函数顺时针旋转坐标系,您可以使用它来转换(即,向坐标点添加给定的偏移量),使用 QPainter :: translate()函数。
QML 进阶---Coordinate Transformations_第5张图片
您也可以使用QPainter :: shear()函数扭转原点周围的坐标系。 所有的转换操作都使用QPainter的转换矩阵,您可以使用QPainter :: worldTransform()函数来检索。 矩阵将平面中的点转换为另一点。如果您一遍又一遍地需要相同的转换,还可以使用QTransform对象和QPainter :: worldTransform()和QPainter :: setWorldTransform()函数。你可以通过调用QPainter::save() 函数随时存储Qpainter的转换矩阵状态, 这个函数将矩阵保存在内部堆栈上。QPainter :: restore()函数可以将保存信息从栈中取出。
转换矩阵的一个常见需求是在各种绘制设备上重复使用相同的绘图代码时。没有坐标转换,那么绘制结果与绘制设备的分辨率紧密相关。打印机具有高分辨率,例如 每英寸600点,而屏幕通常每英寸72至100点。

QML 进阶---Coordinate Transformations_第6张图片

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);

我们转换坐标系,使点(0,0)位于窗口小部件的中心,而不是位于左上角。我们还按照 side / 100来缩放系统,其中side是窗口小部件的宽度或高度,以较短者为准。 我们希望时钟是正方形,即使设备不是。这将给我们一个200 x 200的正方形区域,原点(0,0)在中心,我们可以在上面绘制。

QTime time = QTime::currentTime();

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

通过旋转坐标系绘制时钟的时针,并调用QPainter :: drawConvexPolygon()。由于旋转,它被绘制指向正确的方向。形状用存储在hourHand里面的静态变量(在函数开始处定义)——一个包含x,y值的二维数组来指定,其对应于四个点(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);
 }

最后,我们绘制时钟面,其中包括十二条短线,相互间隔30度。

三、Window-Viewport转换
使用QPainter绘制时,我们使用逻辑坐标系指定坐标点,然后将其转换为设备上的物理坐标。逻辑坐标到物理坐标的映射由QPainter的world变换——worldTransform()(在“Transformations”章节中描述),viewport() 和 window()三个函数来完成。viewport 表示一个矩形的物理坐标,window描述了其对应的逻辑坐标,默认情况下,逻辑和物理坐标系统重合。
使用window-viewport转换,您可以使逻辑坐标系适合您的偏好。这个机制也可使使绘图代码与绘制设备无关。例如,您可以通过调用QPainter :: setWindow()函数使逻辑坐标从(-50,-50)到(50,50)与中心的(0,0)延伸:

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

现在,逻辑坐标(-50,-50)对应于设备上的物理坐标(0,0)。与设备无关,您的代码将始终按照指定的逻辑坐标进行操作。通过设置“window”或viewport Rect,您可以对坐标进行线性变换。请注意,“window”的每个角都映射到viewport的相应角落,反之亦然:

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

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

如果我们让逻辑坐标系成为一个正方形,那我们还应该使用QPainter :: setViewport()函数使viewport变成一个正方形。在上面的示例中,我们使其等同于适合绘制设备矩形的最大的正方形。通过考虑设备的尺寸来在设置window或者viewport,可以将代码保持与设备无关。请注意,window-viewport转换只是一个线性变换,即它不执行剪辑。 这意味着如果您在当前设置的“window”之外绘制,则您的绘制仍将使用相同的线性代数方法转换为viewport。

QML 进阶---Coordinate Transformations_第7张图片
viewport,“window”和变换矩阵确定QPainter逻辑坐标如何映射到设备的物理坐标。默认情况下,world变换矩阵是单位矩阵,“window”和viewport设置等同于设备的设置,即world,“window”和设备坐标系是相等的,但是我们已经看到,系统可以 使用转换操作和window-viewport转换进行操作。 上图说明了该过程。

你可能感兴趣的:(qt,qml+c++)