Qt 图形视图 /图形视图框架坐标系统的设计理念和使用方法

news2024/12/23 11:26:08

文章目录

  • 概述
  • Qt 坐标系统
  • 图形视图的渲染过程
  • Item图形项坐标系
  • Scene场景坐标系
  • View视图坐标系
  • map坐标映射
    • 场景坐标转项坐标
    • 视图坐标转图形项坐标
    • 图形项之间的坐标转换
  • 其他

概述

The Graphics View Coordinate System 图形视图坐标系统是Qt图形视图框架的重要组成部分,按照帮助文档中的划分,其与 The Graphics View Architecture 图形视图架构是在一个层级上的,主要包含图形项坐标系、场景坐标系、视图坐标系、坐标映射等内容。本文将结合帮助文档、Qt编程书籍、Qt示例程序等研习图形视图框架坐标系统的设计理念和使用方法。
在这里插入图片描述
补充,20230501,
编写HelpDoc的人,大概率是极为熟悉该模块的开发者,他们编写文档时的立意是全局化的,很有可能在写第一个章节时,潜移默化的渗透少许第二、甚至第三四五章节的知识点,尽管他们可能已经在尽力避免类似事情的发生,但那很难做到。所以有很多帮助内容,初读时很有跳跃感,比较吃力。此时先读个大概,了解全局,再回头细读,可能是一种可行的方法。

转载请标明原文链接,
https://blog.csdn.net/quguanxin/category_12597847.html

Qt 坐标系统

图形视图框架的坐标系统基于Qt大框架的坐标系统。逻辑坐标系的使用,使绘制代码独立于绘画设备的分辨率和坐标系,开发者可以在逻辑坐标系中编写绘制代码,QPainter 会自动将其映射到实际的设备坐标系。
在Qt框架下,QPainter类实际掌控着Qt坐标系统,并且它与 QPaintDevice 和 QPaintEngine 类共同组成了Qt的绘制系统,而且Qt绘制系统竟然还有一个名字,Arthur 亚瑟,这可能是一个项目代号,具体不得而知。QPainter 用以执行绘制操作,QPaintDevice 是一个二维绘制空间,QPainter就是在该空间进行绘制操作,QPaintEngine为painter在不同设备上的绘制操作提供了interface接口。
QPaintDevice类是所有支持绘制的类的基类,它的绘画能力被 QWidget, QImage, QPixmap, QPicture, and QOpenGLPaintDevice 继承。一个Qt绘制设备的默认坐标系原点在Device左上角,X轴向右增长,Y轴向下增长。在基于像素的设备上,以像素为默认长度单位,在打印设备上,默认单位是一个点(1/72英寸)。QPainter的逻辑坐标到QPaintDevice的物理坐标的映射,由QPainter的变换矩阵、视口和窗口处理。
在这里插入图片描述
图形视图坐标系统以Qt坐标系统为基础,不支持y轴向上增长的反向坐标系(inverted Y-axis coordinate system)。在传统的笛卡尔坐标系中,坐标原点 (0,0) 位于左下角,y轴向上增长,也就是说 y 值越大,坐标点在垂直方向上越高。而在Qt中采用Y轴向下增长,其原因可能是,在绘制图像时,通常图像的第一行像素位于最上方,向下递增,这符合存储图像数据的方式,如此更加自然。

图形视图的渲染过程

在这里插入图片描述
前边提到过,图形视图使用Qt的坐标系统,上述图片段落大意是将图形视图框架的渲染过程代入了 Qt 绘制系统。Qt图形视图框架下,在渲染时,图形视图的场景坐标对应于QPainter的逻辑坐标,视图坐标等同于设备坐标。有关逻辑坐标和设备坐标之间关系的更多内容,在Qt绘制系统的相关文章中讲述。在 ‘绘制’ 这个层次的概念上,Qt图形视图框架并没有脱离QPainter机制,而是在QPainter的基础上进行了扩展和封装,如,自动处理场景的渲染和重绘。

在图形视图中有三个有效effective的坐标系统:图形项坐标系、场景坐标系和视图坐标系。为了简化实现,Qt 图形视图提供了便利函数,允许您在三个坐标系统之间进行映射。

Item图形项坐标系

Item居于live自己的本地坐标系中,可以理解为Item在自己的本地坐标系中运作。图形项坐标系通常以项的中心点 (0,0) 为中心(注意,有的不是哦,后文有提及),这个点也是所有坐标变换的中心。图形项坐标系下的几何图元(Geometric primitives) 通常被称为,点、Item线、Item矩形。
当你创建一个自定义项时,你只需要关注Item本地坐标系即可,场景和视图会帮你执行所有变换。如在 Diagram Scene Example 示例程序中,创建基本流程图形状的代码,是在Item本地坐标系中进行绘制的,根本不用关心场景和视图坐标系的任何。

DiagramItem::DiagramItem(DiagramType diagramType, QMenu *contextMenu, QGraphicsItem *parent) : QGraphicsPolygonItem(parent) {
    ...
    switch (myDiagramType) {
        ...
        case Conditional:  //流程图形状,判定框
            myPolygon << QPointF(-100, 0) << QPointF(0, 100)
                      << QPointF(100, 0) << QPointF(0, -100)
                      << QPointF(-100, 0);
            break;
        case Step:        //流程图形状,过程框
            myPolygon << QPointF(-100, -100) << QPointF(100, -100)
                      << QPointF(100, 100) << QPointF(-100, 100)
                      << QPointF(-100, -100);
            break;
        ...
    }
    setPolygon(myPolygon);
    ...
}

Qt 图形视图框架在尽力的使用Item本地坐标系坐标交互位置信息,以方便开发者使用。例如,当你 (潜台词,在Item对象中) 接收到鼠标按下或拖拽进入事件时,事件(QGraphicsSceneMouseEvent)对象携带的位置会以Item坐标给出(到图形项对象)。QGraphicsItem::contains() 虚函数用于判断某个点是否在物品内,如果在则返回 true,否则返回 false,该函数的参数为Item坐标系下的坐标。类似地,Item的边界矩形和形状也是以Item坐标表示的。下文以 mousePressEvent 鼠标事件为例,观察该事件在视图、场景和图形项中的运行规律,

//在图形项QGraphicsItem类中
virtual void QGraphicsItem::mousePressEvent(QGraphicsSceneMouseEvent *event);
//在场景QGraphicsScene类中
virtual void QGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event);
//是视图QGraphicsView类中
virtual void QGraphicsView::mousePressEvent(QMouseEvent *event);

如上函数同为mousePressEvent原型,QGraphicsView 使用QWidget传统的QMouseEvent 事件类型,而 QGraphicsItem 和 QGraphicsScene 使用 QGraphicsSceneMouseEvent 事件类型,后者可传递两种位置信息,如下,
在这里插入图片描述
相关示例代码,放在了后续 ‘View视图坐标系’ 章节中。通过调试信息可确认,在Item对象中接收到的mouseEvent事件,通过 mouseEvent->pos 函数传递出来的是Item坐标系下的坐标。

父坐标 和 子坐标, (同一个视觉位置点的父坐标和子坐标)

在这里插入图片描述

不得不穿插对英语句子的理解,只是个人理解,原谅我英语没学好,
At item’s position is the coordinate of the item’s center point in its parent’s coordinate system。
结合上图中QGraphicsItem的构造函数,对上文的一个合理的释义,可能是,“(this这个) Item 的位置” 是指 (this) Item 的中心点在其父级(父Item) 坐标系中的坐标,有时也简称为父坐标。从某种意义上来说,若一个Item没有任何父(Item),那么场景将被当做是它的父(Item),顶层Item的位置以场景坐标来表示。整体上,这一小段Doc,目的是传递一个父坐标的概念,即,Item的(pos函数)位置是该Item原点在其父Item坐标系中的坐标。
在这里插入图片描述
理解上边这段话,着实花费了我不少时间。如上English,Child是子图形项的意思,parent是 this Item‘s parant 父项的意思,复数形式的coordinates可能通常代表坐标系、单数coordinate可能通常代表坐标点。与前文那句英文类似,这段话里似乎也藏了许多潜台词,故此晦涩。本段开始就告诉我们,子坐标系是相对于(be relative to)父坐标系的,真不太好理解。接着文中提到,如果子级Item未经过变换,则(潜台词,在窗口中,用户视觉上是同一个点,如下图的红点)子坐标与父级坐标之间的差距,与(the two)Items(前面提到的Child和Parent)在父坐标中的距离(也即父Item和子Item中心点之间的距离)相同。只能结合下文具体的例子继续理解,
在这里插入图片描述
如果一个未经过变换的子Item恰好位于其父项的中心点上,那么两个项的坐标系统将是相同的。然而,如上图中,红色矩形代表的子级Item,其在父级Item坐标系下的位置是 (10, 0),而不是 (0,0) 原点。此时,视觉红点,其在子级Item坐标系下的坐标为(0, 10),同时它在父级坐标系下的坐标是(10, 10)。如此,同样一个红点的子坐标(0,10)和父坐标(10,10)之间的差异便是(10,0),这个(10,0)也正好是子项和父项之间的距离(两个矩形中心点的距离),这个距离是10个单位长度。而文中 the difference between a child coordinate and a parent coordinate,并没有特定的指代某点,应该是指子Item上的任意的一个点,即,从视图上看到的任意一个点,其子项坐标与父项坐标的差值是一样的,都是子项和父项原点之间的距离。

由于项位置和项变换是与父项相对的,子项的坐标系并不不会受到父项坐标系变换的影响,尽管父项的变动隐式的转换了子项。还在上面的例子中,即使父项被旋转和缩放,子项坐标系下的(0,10)点依然将对应父项坐标系下的(10,10)点。然而,相对于场景,子项将跟随父项的变换和位置。如果父项被缩放2倍,那么,子项在场景中的坐标将变成(20,0),子项坐标系下的(10,0),在父项坐标系下对应的位置依然是(20,0),在场景坐标系中对应的点是(40,0)。

行文至此,我们再倒回头来聊聊前面没理解的 “子坐标系是相对于父坐标系的”,其含义是,无论父级图形项如何缩放、旋转等变动,子项在父项做标系下的位置不变,子项坐标系下的点,其对应的父项坐标系下的坐标也还是原来的值,没有变化,这就是所谓的相对性,即(子项任意可视点的)子项坐标系下的坐标与对应的父项坐标系下的坐标都还是原来的坐标。与上述相对性对立的,是子项相对于场景/绘制设备的绝对位置和变换则受父项变换的影响。可以不太精确的定义两个名词,相对坐标系和绝对坐标系。这种父子层级结构让坐标变换更加清晰可控,子项目可以独立于父项的变换来定义自身坐标,但最终的绝对变换位置仍由父项的属性确定。

除了少数例外( 如QGraphicsItem::pos() )之外,QGraphicsItem的函数都在项坐标系中运行,不受该项或其任何父项目的变换的影响。例如,一个项的边界矩形(即boundingRect)始终给出项坐标系下的坐标。

Scene场景坐标系

场景表示所有Item图形项的基础坐标系统。场景坐标系统描述的是所有顶层Item的位置,也是所有从View视图到Scene场景的 QGraphicsSceneMouseEvent 场景型事件的基石。场景中的每个项除了具有Item本地位置和本地边界矩形之外,还有一个场景位置和场景边界矩形 (QGraphicsItem::scenePos(), QGraphicsItem::sceneBoundingRect())。如下所示,
在这里插入图片描述

在这里插入图片描述
场景位置描述了项在场景坐标系中的位置,而项的场景边界矩形则构成了QGraphicsScene 确定场景中哪些区域已更改的基础。场景中的更改通过 QGraphicsScene::changed() 信号传播,参数是一个场景矩形列表。通过上图中对 boundingRect() 和 sceneBoundingRect() 的对比,可以直观的看出两者的区别,本地边界矩形被用以QGraphicsView的重绘过程,而场景边界矩形是混合了本地边界矩形和坐标系变换之后的场景坐标系下的矩形,其主要用以探测出项在场景中的位置变动。

QRectF Arrow::boundingRect() const {
    qreal extra = (pen().width() + 20) / 2.0;

    return QRectF(line().p1(), QSizeF(line().p2().x() - line().p1().x(), line().p2().y() - line().p1().y()))
        .normalized()
        .adjusted(-extra, -extra, extra, extra);
}

View视图坐标系

视图坐标系是窗口部件的坐标系。视图坐标系下的每个单元都对应一个像素。这个坐标系统的特殊之处在于,它是相对于 widget部件 或 viewport视口的,不受被观察场景的影响。QGraphicsView 的viewport 视口的左上角坐标永远是(0,0),右下角坐标永远是(视口宽度,视口高度)。

所有鼠标事件和拖放事件最初都是以视图坐标接收的,你需要将这些坐标映射到场景中,以便与Item项交互。也可以这么理解,用户的键鼠事件等最早(originally)是由视图对象捕获的,而场景对象可能是作为事件过滤器被安装到关联的视图对象上的,为此我们进行了如下验证。我们扩展 Diagram Scene Example 示例程序,添加从QGraphicsView派生的GraphicsView类,并分别在DiagramItem、DiagramScene、DiagramView 中新实现或修改 mousePressEvent 虚函数,

//派生图形项中对鼠标按压事件的处理
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
    qDebug() << "\r\n--#-- DiagramItem::mousePressEvent --#--";
    qDebug() << "DiagramItem::pos:" << event->pos();
    qDebug() << "DiagramItem::scenePos:" << event->scenePos();
    //must
    return QGraphicsPolygonItem::mousePressEvent(event);
}
//派生场景中对鼠标按压事件的处理
void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {
    qDebug() << "\r\n--#-- DiagramScene::mousePressEvent --#--";
    qDebug() << "DiagramScene::pos:" << mouseEvent->pos();
    qDebug() << "DiagramScene::scenePos:" << mouseEvent->scenePos();
    ...
}
//派生视图中对鼠标按压事件的处理
void DiagramView::mousePressEvent(QMouseEvent *event) {
    qDebug() << " \r\n--#-- DiagramView::mousePressEvent --#--";
    qDebug() << "DiagramView::localPos:" << event->localPos();
    qDebug() << "DiagramView::windowPos:" << event->windowPos();
    qDebug() << "DiagramView::screenPos:" << event->screenPos();
    qDebug() << "DiagramView::pos:" << event->pos();             //==localPos
    qDebug() << "DiagramView::globalPos:" << event->globalPos(); //==screenPos
    //must
    return QGraphicsView::mousePressEvent(event);
}

/* //创建一个ShpeItem点击其靠近右下角的某个位置
--#-- DiagramView::mousePressEvent --#--
DiagramView::localPos: QPointF(355,265)
DiagramView::windowPos: QPointF(564,332)
DiagramView::screenPos: QPointF(664,432)
DiagramView::pos: QPoint(355,265)       //==localPos   //以视图左上角为00
DiagramView::globalPos: QPoint(664,432) //==screenPos

--#-- DiagramScene::mousePressEvent --#--
DiagramScene::pos: QPointF(0,0)             //只要点击位置在Item内,则始终为00
DiagramScene::scenePos: QPointF(2573,2562)

--#-- DiagramItem::mousePressEvent --#--
DiagramItem::pos: QPointF(75,78)           //以Item中心位置为00原点
DiagramItem::scenePos: QPointF(2573,2562)  //与在场景对象中的捕获坐标值是一致的
*/

通过上运行结果,我们验证了Qt鼠标事件在Qt图形视图框架下的传递顺序,基本可推定,视图对象会在最初捕获用户事件,然后可能的转换过程方案有:转为视图对象直接将 QMouseEvent 转为 QGraphicsSceneMouseEvent 事件;场景对象是视图对象的事件过滤器,图形项对象是场景的事件过滤器,由场景和图形项对象各自分别转换,但由于图形项没有从QObject继承,即无法安装过滤器,这种方案的可能性不大。针对上述问题,后续将研究相关源码,并在其他文章中继续讨论。

当使用未经过任何变换(如缩放、旋转等)的视图observing观察场景时,场景上的unit单位长度对应屏幕上的一个像素。也即在没有应用任何变换的情况下,场景坐标与视图坐标是相互对应的。
例如,如果在场景坐标系中有两个点A(10,10) 和 B(20,10),那么它们在未经变换的视图中的像素距离就是10个像素。这种对应的关系使得最开始在场景上布局和定位图形项时很方便,因为你可以直接以像素为单位来思考。若应用了任何变换,比如缩放或旋转,场景坐标与视图坐标之间就不再是简单的1-1对应关系了,这时就需要使用Qt提供的坐标映射函数来在不同坐标系之间转换。

map坐标映射

通常,在处理场景中的图形项时,需要在场景、图形项、视图之间进行必要的坐标或任意矩形的映射。

例如,当你在 QGraphicsView 的视口中单击鼠标时,可以通过调用 QGraphicsView::mapToScene() 将坐标映射到场景,然后调用QGraphicsScene::itemAt() 来查询光标下的图形项对象。如果你想知道一个图形项在视图视口中的位置,可以在图形项对象上调用QGraphicsItem::mapToScene(),然后在视图上调用QGraphicsView::mapFromScene()。如果你想找出视口中椭圆内的所有图形项,可以将椭圆对象的 QPainterPath 传递给 mapToScene() 映射到场景,然后将映射后的路径传递给QGraphicsScene::items()。

在图形项和场景之间,通过调用QGraphicsItem::mapToScene() and QGraphicsItem::mapFromScene() 函数来相互映射坐标和形状。在图形项与其父项之间通过调用QGraphicsItem::mapToParent() and QGraphicsItem::mapFromParent()来进行映射,或者通过调用 QGraphicsItem::mapToItem() 和 QGraphicsItem::mapFromItem() 在(同一个场景下)不同图形项之间进行映射。所有的映射函数都支持点、矩形、多边形和路径,以 mapToScene 为例,在QGraphicsItem类(图左) 和 QGraphicsView类(图右),有以下6种重载,
在这里插入图片描述
下文将对部分场景下的坐标转换过程,通过示例程序进行说明。

场景坐标转项坐标

在整个Qt图形视图坐标系统中,场景坐标处于至关重要的承上启下的位置。且通过上文可知,场景类并没有直接提供坐标映射的接口,这些映射接口仅存在于QGraphicsItem类 和 QGraphicsView类中。那么在场景对象的处理函数中,如何进行坐标的相关运算呢?
在这里插入图片描述
除了上述 itemAt 函数之外,QGraphicsScene 类还提供了 6 个items(…) 的重载函数、selectedItems() 、views() 等函数,获取目标图形项实例对象。一旦通过场景坐标获取到了图形项实例和视图实例,便可以使用QGraphicsItem类 和 QGraphicsView类中的坐标映射接口。

在 Diagram Scene Example 示例程序,场景类的实现中,就使用了items函数的一个版本,

void DiagramScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *mouseEvent) {
    if (line != 0 && myMode == InsertLine) {
        QList<QGraphicsItem *> startItems = items(line->line().p1());
        if (startItems.count() && startItems.first() == line)
            startItems.removeFirst();
        QList<QGraphicsItem *> endItems = items(line->line().p2());
        if (endItems.count() && endItems.first() == line)
            endItems.removeFirst();
        ...
}

在Diagram Scene Example 示例程序,场景类的实现中,添加如下itemAt函数的使用测试,

void DiagramScene::mousePressEvent(QGraphicsSceneMouseEvent *mouseEvent) {
...
    #if 1
    QTransform deviceTransform; //in param
    QGraphicsItem *tempitem = this->itemAt(mouseEvent->scenePos(), deviceTransform);
    QPointF ItemLocalPt = tempitem->mapFromScene(mouseEvent->scenePos());
    qDebug() << "DiagramScene::ItemLocalPt:" << ItemLocalPt;
    #endif
...
}

如上,通过调用场景类的 itemAt 函数,得到 QGraphicsItem 实例对象指针后,便可使用 QGraphicsItem 的映射函数啦。

视图坐标转图形项坐标

如上,视图中提供(be available)了与图形项类相同的映射函数,用于在视图和场景之间进行映射。要从视图映射到图形项,你首先需要将视图坐标映射到场景坐标系,然后再从场景映射到图形项坐标系。我们扩展 Diagram Scene Example 示例程序,

//从QGraphicsView派生DiagramView,替代原view实例 /实现如下非功能的测试函数
void DiagramView::mousePressEvent(QMouseEvent *event) {
  //QPoint#pos()==QPointF#localPos()
  qDebug() << "DiagramView# localPos:" << event->pos() << "windowPos:" << event->windowPos() << "screenPos:" << event->screenPos();
#if 1
    //在视图中得到鼠标位置的Item本地坐标
    QPointF itemLocalPos;
    //视图坐标转场景坐标 //event->pos()==event->localPos()
    QPointF scenePtFromView = mapToScene(event->pos());
    //获取鼠标事件发生的位置对应的QGraphicsItem实例
    QGraphicsItem *item = this->scene()->itemAt(scenePtFromView, QTransform());
    if (item)
        itemLocalPos = item->mapFromScene(scenePtFromView);
    qDebug() << "DiagramView# localpos:" << itemLocalPos << "scenePos:" << scenePtFromView;
#endif
    //must
    return QGraphicsView::mousePressEvent(event);
}

//在DiagramItem追加如下测试函数
void DiagramItem::mousePressEvent(QGraphicsSceneMouseEvent *event) {
    qDebug() << "DiagramItem# localpos:" << event->pos() << "scenePos:" << event->scenePos();
    //must
    return QGraphicsPolygonItem::mousePressEvent(event);
}

/* 运行修改后的示例程序,添加一个ShpeItem,点击其中的位置,运行结果如,
DiagramView# localPos: QPoint(187,171) windowPos: QPointF(396,238) screenPos: QPointF(496,338)
DiagramView# localpos: QPointF(5,47) scenePos: QPointF(2405,2468)
DiagramItem# localpos: QPointF(5,47) scenePos: QPointF(2405,2468)
*/

如上代码,将View视图实例QMouseEvent事件中的本地坐标(以视图Widget左上角为原点),也即视图坐标系下的坐标,通过 QGraphicsView::mapToScene 函数转换为场景坐标系下的坐标,进而找到该场景坐标下的Item实例,之后通过调用Item的mapFromScene函数,将场景坐标映射到Item本地坐标系下。通过对比DiagramView对象和DiagramItem对象内的打印信息,可验证转换效果。

在 Drill Down Example 这个示例程序中,

void View::mouseReleaseEvent(QMouseEvent *event) {
    if (QGraphicsItem *item = itemAt(event->pos())) {
        if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item))
            showInformation(image);
    }
    QGraphicsView::mouseReleaseEvent(event);
}

如上,我们细看 QGraphicsView 类,可知,QGraphicsView 自身与 QGraphicsScene 类一样,同样提供了 itemAt(…) 和 items(…) 函数。但是本质上还是要将视图坐标映射到场景坐标系,然后再从场景映射到图形项坐标系。透过以下 QGraphicsView::items 函数的源码,可以证明这一点,

//
QGraphicsItem *QGraphicsView::itemAt(const QPoint &pos) const {
    Q_D(const QGraphicsView);
    if (!d->scene)  return 0;
    const QList<QGraphicsItem *> itemsAtPos = items(pos);
    return itemsAtPos.isEmpty() ? 0 : itemsAtPos.first();
}
//
QList<QGraphicsItem *> QGraphicsView::items(const QPoint &pos) const {
    Q_D(const QGraphicsView);
    if (!d->scene)
        return QList<QGraphicsItem *>();
    // ### Unify these two, and use the items(QPointF) version in
    // QGraphicsScene instead. The scene items function could use the viewport
    // transform to map the point to a rect/polygon.
    if ((d->identityMatrix || d->matrix.type() <= QTransform::TxScale)) {
        // Use the rect version
        QTransform xinv = viewportTransform().inverted();
        return d->scene->items(xinv.mapRect(QRectF(pos.x(), pos.y(), 1, 1)), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());
    }
    // Use the polygon version
    return d->scene->items(mapToScene(pos.x(), pos.y(), 1, 1), Qt::IntersectsItemShape, Qt::DescendingOrder, viewportTransform());
}

图形项之间的坐标转换

以 Diagram Scene Example Arrow 类的 updatePosition 函数为例,说明Item图形项之间的坐标映射过程。

void Arrow::updatePosition() {
    //将图形项myStartItem/myEndItem的原点转换为另一个Arrow图形项的坐标系下
    QLineF line(mapFromItem(myStartItem, 0, 0), mapFromItem(myEndItem, 0, 0));
    //绘制myStartItem/myEndItem中心点之间的线段
    setLine(line);
}

如上,为了绘制两个ShapeItem之间的连接线,将myStartItem项的中心原点映射为线段的起点,将myEndItem项的中心原点映射为线段的终点,构建 QLineF 对象并更新 QGraphicsLineItem 类内部维护的线段成员变量。特别需要注意的是,图形项QGraphicsLineItem的本地坐标系和场景坐标系是重合的,因此,this->mapFromItem(myStartItem, 0, 0) 与 myStartItem->mapToScene(QPointF(0,0)) 是相等的,而 this->mapToScene(QPointF(0,0)) 和 this->scenePos() 等返回的都是(0, 0)场景坐标原点。

其他

前面讲到 itemAt 或 items 函数时,它们都有一个QTransform 对象作为输入参数,在 QGraphicsItem 的 mapFromScene、mapToScene等函数实现、QGraphicsItemPrivate的诸多实现中都用到了 QTransform 成员变量或函数参数。该类在 Qt 大框架的模块划分上属于 Qt GUI 模块,隶属 Qt 绘制系统。
要更彻底掌握 Qt 绘制系统坐标系统 和 Qt 图形视图框架坐标系统的设计思路和使用方法,或者仅是想稍加深入的阅读上述框架的源代码,QTransform 是绕不开的知识点。它用于指定二维坐标系统变换,提供了平移(translate)、缩放(scale)、剪切(shear)、旋转(rotate) 和投影(project) 等基本变换操作,常被用于渲染图形时对坐标系统进行变换。QTransform是一个真正的3x3矩阵,允许进行透视投影变换,与QMatrix相比,它功能更加强大,在Qt中QTransform被推荐用作变换类。限于文章篇幅,此文不再对此展开。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1547483.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【win10 win11添加右键】git bash

打开注册表编辑器。 按下Win键 R&#xff0c;然后输入”regedit”并按下回车键来打开注册表编辑器。计算机\HKEY_CLASSES_ROOT\Directory\Background\shell\git_bash\command2. 导航到注册表路径&#xff1a;依次展开”HKEY_CLASSES_ROOT\Directory\Background\shell”。右键…

Git入门(Git快速下载,安装,配置,远程仓库,本地仓库,IDEA提交代码,VScode提交代码使用方案一体)

Git快速下载 通过阿里镜像可以自由挑选版本并快速下载CNPM Binaries Mirrorhttp://npm.taobao.org/mirrors/git-for-windows/ 这里安装最新版本 下载安装文件 安装完后双击文件即可开始安装git 安装 git的安装傻瓜式Next即可 配置 打开git&#xff1a;桌面空白处右击&#…

FTLK-1.3.9的编译和使用

文章目录 1. FTLK-1.3.9源码下载 1. FTLK-1.3.9源码下载 前往FTLK官网下载FTLK-1.3.9源码 *

ARM IHI0069F GIC architecture specification (4)

1.3 支持的配置和兼容性 在 Armv8-A 中&#xff0c;EL2 和 EL3 是可选的&#xff0c;PE 可以支持一个、两个或都不支持这些异常级别。 然而&#xff1a; • PE 要求EL3 支持安全和非安全状态。 • PE 需要EL2 来支持虚拟化。 • 如果未实施EL3&#xff0c;则只有一个安全状态。…

信号处理--基于DEAP数据集的情绪分类的典型深度学习模型构建

关于 本实验采用DEAP情绪数据集进行数据分类任务。使用了三种典型的深度学习网络&#xff1a;2D 卷积神经网络&#xff1b;1D卷积神经网络GRU&#xff1b; LSTM网络。 工具 数据集 DEAP数据 图片来源&#xff1a; DEAP: A Dataset for Emotion Analysis using Physiological…

【二】TensorFlow神经网络模型构建之卷积函数

卷积函数是构建神经网络的重要支架&#xff0c;是在一批图像上扫描的二维过滤器。 tf.nn.convolution(input,filter,padding,stridesNone,dilation_rateNone,nameNone,data_formatNone)该函数计算N维卷积的和。tf.nn.conv2d(input,filter,padding,strides,use_cudnn_on_gpuNon…

【卫星影像三维重建】有理函数模型库-rpcm

卫星成像模型rpcm库使用 1.介绍1.1 卫星成像模型1.2 rpcm库 2.安装2.1 依赖库2.2 安装 3.用法3.1 投影3.2 定位3.3 影像裁剪crop3.3.1 使用说明3.3.2 具体使用 3.4 footprint3.5 angle影像的成像夹角 4.参考 1.介绍 1.1 卫星成像模型 卫星遥感影像的成像模型是描述影像与地面…

从零开始搭建游戏服务器 第七节 创建GameServer

目录 前言正文创建GameServer模块修改配置创建NettyClient连接到登录服登录服修改创建协议游戏服注册到登录服 总结 前言 上一节我们使用自定义注解反射简化了协议解包和逻辑处理分发流程。 那么到了这里登录服登录服的架构已经搭建的差不多了&#xff0c;一些比较简单的、并发…

pyhive入门介绍和实例分析(探索票价与景点评分之间是否存在相关性)

介绍 PyHive 是一组 Python DB-API 和 SQLAlchemy 接口&#xff0c;可用于 Presto 和 Hive。它为 Python 提供了一个与 Presto 和 Hive 进行交互的平台&#xff0c;使得数据分析师和工程师可以更方便地进行数据处理和分析。 以下是使用 PyHive 进行数据分析时需要注意的几点&…

HWOD:名字的漂亮度

一、题目 描述 给出一个字符串&#xff0c;该字符串仅由小写字母组成&#xff0c;定义这个字符串的漂亮度是其所有字母漂亮度的总和 每个字母都有一个漂亮度&#xff0c;范围在1到26之间。没有任何两个不同字母拥有相同的漂亮度。字母忽略大小写。 给出多个字符串&#xff…

面试篇:HashMap

1.问&#xff1a;了解过红黑树吗&#xff1f;它有什么性质&#xff1f; 答&#xff1a;红黑树是一种自平衡的二叉搜索树&#xff0c;他的查找&#xff0c;添加和删除的时间复杂度都为O(logN)。 他有以下五种性质&#xff1a; 1.红黑树的节点只有红色或者黑色两种颜色 2.红黑树的…

java Web线上网游商品交易平台用eclipse定制开发mysql数据库BS模式java编程jdbc

一、源码特点 jsp线上网游商品交易平台是一套完善的web设计系统&#xff0c;对理解JSP java SERLVET mvc编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为TOMCAT7.0,eclipse开发&#xff0c;数据库为Mysql5.0…

Charles for Mac 强大的网络调试工具

Charles for Mac是一款功能强大的网络调试工具&#xff0c;可以帮助开发人员和测试人员更轻松地进行网络通信测试和调试。以下是一些Charles for Mac的主要特点&#xff1a; 软件下载&#xff1a;Charles for Mac 4.6.6注册激活版 流量截获&#xff1a;Charles可以截获和分析通…

QT+Opencv+yolov5实现监测

功能说明&#xff1a;使用QTOpencvyolov5实现监测 仓库链接&#xff1a;https://gitee.com/wangyoujie11/qt_yolov5.git git本仓库到本地 一、环境配置 1.opencv配置 将OpenCV-MinGW-Build-OpenCV-4.5.2-x64文件夹放在自己的一个目录下&#xff0c;如我的路径&#xff1a; …

OriginBot智能机器人开源套件

详情可参见&#xff1a;OriginBot智能机器人开源套件——支持ROS2/TogetherROS&#xff0c;算力强劲&#xff0c;配套古月居定制课程 (guyuehome.com) OriginBot智能机器人开源套件 最新消息&#xff1a;OriginBot V2.1.0版本正式发布&#xff0c;新增车牌识别&#xff0c;点击…

Spring Cloud 八:微服务架构中的数据管理

Spring Cloud 一&#xff1a;Spring Cloud 简介 Spring Cloud 二&#xff1a;核心组件解析 Spring Cloud 三&#xff1a;API网关深入探索与实战应用 Spring Cloud 四&#xff1a;微服务治理与安全 Spring Cloud 五&#xff1a;Spring Cloud与持续集成/持续部署&#xff08;CI/C…

政安晨:【Keras机器学习实践要点】(五)—— 通过子类化创建新层和模型

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras实战演绎机器学习 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 介绍 本文将涵盖构建自己的子类化层和模型所…

使用Spark单机版环境

在Spark单机版环境中&#xff0c;可通过多种方式进行实战操作。首先&#xff0c;可使用特定算法或数学软件计算圆周率π&#xff0c;并通过SparkPi工具验证结果。其次&#xff0c;在交互式Scala版或Python版Spark Shell中&#xff0c;可以进行简单的计算、打印九九表等操作&…

ABAP - 上传文件模板到SMW0,并从SMW0上下载模板

upload file template to SMW0 and download the template from it 首先上传文件到tcode SMW0 选择新建后,输入文件名和描述,再选择想要上传的文件 上传完成后: 在表WWWPARAMS, WWWDATA里就会有信息存进去 然后就可以程序里写代码了: 屏幕上的效果:

jupyter notebook导出含中文的pdf(LaTex安装和Pandoc、MiKTex安装)

用jupyter notebook导出pdf时&#xff0c;因为报错信息&#xff0c;需要用到Tex nbconvert failed: xelatex not found on PATH, if you have not installed xelatex you may need to do so. Find further instructions at https://nbconvert.readthedocs.io/en/latest/install…