当前位置: 首页 > news >正文

Qt下使用图形视图框架实现图像上各图形绘制

系列文章目录

提示:这里是该系列文章的所有文章的目录
第一章: Qt学习:图形视图框架的使用
第二章: Qt下使用图形视图框架实现图像上各图形绘制


文章目录

  • 系列文章目录
  • 前言
  • 一、图像上绘制图形的方式
  • 二、自定义图形视图控件
  • 三、可绘制矩形项类
  • 四、可绘制椭圆/圆形项类
  • 五、可绘制多边形项类
  • 六、使用示例
  • 总结


前言

本文主要讲述了在Qt下使用图形视图框架实现在图像上绘制矩形、圆形和多边形,并且可以获取各图形项在图像上的像素坐标,下面结合相应的示例进行讲解,以便大家学习,如有错误之处,欢迎大家批评指正。

项目效果
请添加图片描述


提示:以下是本篇文章正文内容,下面案例可供参考

一、图像上绘制图形的方式

在Qt中,在图像上绘制图形可以用下面这些方式:
1、使用QPainter直接在图像上绘制。
2、先将图像绘制到窗口或控件上,然后在窗口或控件上通过重写paintEvent进行绘制绘制图形
3、使用图形视图框架的方式,这是一种更高级的绘图机制,特别适合处理大量可交互的图形项。
这里我使用图形视图框架的方式来进行图形绘制,并可以对图像上绘制的图形进行交互

图形视图框架(Graphics View Framework)
该框架提供了一种基于图形项(Graphics Items)的模型视图编程方法。它包含三个核心类:
​​QGraphicsScene​​:管理所有图形项的容器,提供场景管理(如碰撞检测、坐标变换等)。
​​QGraphicsItem​​:代表场景中的一个图形项(如矩形、椭圆、文本等),用户也可以自定义图形项。
​​QGraphicsView​​:用于显示场景的视图组件,支持缩放、旋转和平移等交互。
使用图形视图框架在图像上绘制图形的步骤:
将图像作为场景的背景(或作为一个图形项放入场景)。
在场景中添加自定义的图形项(如矩形、圆形等)。
通过视图来显示场景。

二、自定义图形视图控件

这里我自定义实现了一个图形视图控件,视图显示棋盘格背景,实现加载显示图像并在图像上绘制矩形、圆形和多边形等功能,并能获取图像项在图像上的像素位置,下面是具体实现代码:
图形视图控件 MyGraphicsView
1.mygraphicsview.h

#ifndef MYGRAPHICSVIEW_H
#define MYGRAPHICSVIEW_H#include <QApplication>
#include <QWidget>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QGraphicsSceneMouseEvent>
#include <QScrollBar>
#include <QToolButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QWheelEvent>
#include <QResizeEvent>
#include <QMouseEvent>
#include <QMessageBox>
#include <QFileDialog>
#include <QPainter>
#include <QPixmap>
#include <QList>
#include <QPair>
#include <QDebug>#include "drawablerectitem.h"
#include "drawableellipseitem.h"
#include "drawablepolygonitem.h"#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;//绘制模式枚举
enum DrawMode
{NoDraw,RectangleMode,CircleMode,PolygonMode
};//定义椭圆数据结构
struct OpenCVEllipse
{cv::Point center;cv::Size axes;double angle;double startAngle;double endAngle;
};/*** @class MyGraphicsView* @brief 自定义图形视图控件,支持图像加载和图形绘制** 该类提供以下功能:* 1. 加载和显示图像* 2. 在图像上绘制矩形、圆形和多边形* 3. 缩放、重置视图* 4. 管理图形项的位置和状态*/
class MyGraphicsView : public QWidget
{Q_OBJECT
public:explicit MyGraphicsView(QWidget *parent = nullptr);~MyGraphicsView();void initValue();void createToolbar();void createChessboardBackground();void setDragModeBasedOnState();void forceViewUpdate();void exitDrawingMode();void loadImage(const QString &filePath);void clearImage();void fitToView();void zoom(double factor, const QPoint &pos = QPoint());void removeOutOfBoundsRectangles();void updateRectPixelPositions();void removeOutOfBoundsCircles();void updateCirclePixelPositions();void removeOutOfBoundsPolygons();void updatePolygonPixelPositions();const vector<QRectF>& getRectPixelPositions();const vector<QRectF>& getCirclePixelPositions();const vector<QPolygonF>& getPolygonPixelPositions();const vector<Rect> getOpenCVRects();const vector<OpenCVEllipse> getOpenCVEllipses();const vector<vector<Point>> getOpenCVPolygons();protected:void resizeEvent(QResizeEvent *event) override;void wheelEvent(QWheelEvent *event) override;bool eventFilter(QObject *obj, QEvent *event) override;private slots:void onImportClicked();void onZoomInClicked();void onZoomOutClicked();void onResetClicked();void onDrawRectClicked();void onDrawCircleClicked();void onDrawPolygonClicked();private:QGraphicsScene *m_scene;            //图形场景QGraphicsView *m_view;              //图形视图QGraphicsPixmapItem *m_imageItem;   //图像项QVBoxLayout *m_toolbarLayout;       //工具栏布局QSize m_originalPixmapSize;         //原始图像尺寸bool m_isFitted;        //是否处于初始适配状态qreal m_currentScale;   //当前缩放比例DrawMode m_currentDrawMode;   //当前绘制模式QPointF m_rectStartPos;   //绘制起始点QPointF m_circleCenter;   //圆心位置QVector<QPointF> m_polygonPoints;   //多边形顶点bool m_polygonDrawingStarted = false;   //是否开始绘制多边形std::vector<DrawableRectItem*> m_rects;           //场景中的矩形项std::vector<QRectF> m_rectPixelPositions;         //所有矩形的像素位置std::vector<DrawableEllipseItem*> m_circles;      //场景中的圆形项std::vector<QRectF> m_circlePixelPositions;       //所有圆形的像素位置std::vector<DrawablePolygonItem*> m_polygons;     //场景中的多边形项std::vector<QPolygonF> m_polygonPixelPositions;   //所有多边形的像素位置DrawableRectItem* m_tempRectItem;         //临时矩形项DrawableEllipseItem* m_tempCircleItem;    //临时圆形项DrawablePolygonItem* m_tempPolygonItem;   //临时多边形项};
#endif // MYGRAPHICSVIEW_H

2.mygraphicsview.cpp

#include "mygraphicsview.h"MyGraphicsView::MyGraphicsView(QWidget *parent): QWidget(parent), m_scene(new QGraphicsScene(this)), m_view(new QGraphicsView(this))
{//初始化成员变量initValue();//设置固定大小this->setFixedSize(800,600);//设置布局QHBoxLayout *mainLayout = new QHBoxLayout(this);mainLayout->setSpacing(0);mainLayout->setContentsMargins(0, 0, 0, 0);//创建工具栏createToolbar();//配置图形视图m_view->setScene(m_scene);m_view->setRenderHint(QPainter::Antialiasing);m_view->setRenderHint(QPainter::SmoothPixmapTransform);m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//创建棋盘格背景createChessboardBackground();//添加布局mainLayout->addLayout(m_toolbarLayout);mainLayout->addWidget(m_view, 1);setLayout(mainLayout);//安装事件过滤器m_view->viewport()->installEventFilter(this);//设置初始拖拽模式setDragModeBasedOnState();
}MyGraphicsView::~MyGraphicsView()
{//清理图像项clearImage();delete m_tempRectItem;delete m_tempCircleItem;delete m_tempPolygonItem;delete m_scene;delete m_view;
}//初始化成员变量
void MyGraphicsView::initValue()
{m_imageItem = nullptr;m_toolbarLayout = nullptr;m_originalPixmapSize = QSize(0, 0);m_isFitted = true;m_currentScale = 1.0;m_currentDrawMode = NoDraw;m_rectStartPos = QPointF();m_circleCenter = QPointF();m_polygonPoints.clear();m_polygonDrawingStarted = false;m_rects.clear();m_rectPixelPositions.clear();m_circles.clear();m_circlePixelPositions.clear();m_polygons.clear();m_polygonPixelPositions.clear();m_tempRectItem = nullptr;m_tempCircleItem = nullptr;m_tempPolygonItem = nullptr;
}//创建工具栏
void MyGraphicsView::createToolbar()
{m_toolbarLayout = new QVBoxLayout;m_toolbarLayout->setSpacing(10);m_toolbarLayout->setAlignment(Qt::AlignTop);m_toolbarLayout->setContentsMargins(5, 10, 5, 10);QList<QPair<QString, QString>> buttons ={{"导入图像", "import"},{"放大", "zoom_in"},{"缩小", "zoom_out"},{"还原", "reset"},{"矩形", "rect"},{"圆形", "circle"},{"多边形", "polygon"}};for(const auto &btn : buttons){QToolButton *toolBtn = new QToolButton(this);toolBtn->setText(btn.first);toolBtn->setToolTip(btn.first);toolBtn->setFixedSize(40, 40);m_toolbarLayout->addWidget(toolBtn);if(btn.first == "导入图像"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onImportClicked);}else if(btn.first == "放大"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onZoomInClicked);}else if(btn.first == "缩小"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onZoomOutClicked);}else if(btn.first == "还原"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onResetClicked);}else if(btn.first == "矩形"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onDrawRectClicked);}else if(btn.first == "圆形"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onDrawCircleClicked);}else if(btn.first == "多边形"){connect(toolBtn, &QToolButton::clicked, this, &MyGraphicsView::onDrawPolygonClicked);}}m_toolbarLayout->addStretch(1);
}//创建棋盘格背景
void MyGraphicsView::createChessboardBackground()
{//创建棋盘格纹理QPixmap tilePixmap(20, 20);tilePixmap.fill(Qt::white);QPainter painter(&tilePixmap);painter.fillRect(0, 0, 10, 10, QColor(220, 220, 220));painter.fillRect(10, 10, 10, 10, QColor(220, 220, 220));painter.end();m_scene->setBackgroundBrush(tilePixmap);
}//根据当前状态设置拖拽模式
void MyGraphicsView::setDragModeBasedOnState()
{if(m_currentDrawMode != NoDraw){//在绘制模式下强制禁用拖拽,只允许绘制操作m_view->setDragMode(QGraphicsView::NoDrag);m_view->setCursor(Qt::CrossCursor);}else if(m_imageItem){//非绘制模式下且已加载图像,使用拖拽模式m_view->setDragMode(QGraphicsView::ScrollHandDrag);m_view->setCursor(Qt::OpenHandCursor);}else{//没有图像时禁用拖拽m_view->setDragMode(QGraphicsView::NoDrag);m_view->setCursor(Qt::ArrowCursor);}//确保视图焦点m_view->setFocus();
}//强制视图更新
void MyGraphicsView::forceViewUpdate()
{//强制重绘整个场景m_scene->update();//更新所有视图if(!m_scene->views().isEmpty()){QGraphicsView* view = m_scene->views().first();view->viewport()->update();}//如果有图像项,触发其更新if(m_imageItem){m_imageItem->update();}//强制应用UI刷新QApplication::processEvents();
}//退出所有绘制模式
void MyGraphicsView::exitDrawingMode()
{//清理矩形临时项if(m_tempRectItem){m_scene->removeItem(m_tempRectItem);delete m_tempRectItem;m_tempRectItem = nullptr;}m_rectStartPos = QPointF();//清理圆形临时项if(m_tempCircleItem){m_scene->removeItem(m_tempCircleItem);delete m_tempCircleItem;m_tempCircleItem = nullptr;}m_circleCenter = QPointF();//清理多边形临时项if(m_tempPolygonItem){m_scene->removeItem(m_tempPolygonItem);delete m_tempPolygonItem;m_tempPolygonItem = nullptr;}m_polygonPoints.clear();m_polygonDrawingStarted = false;//设置不选中for(auto& rectData : m_rects){rectData->setSelected(false);}for(auto& circleData : m_circles){circleData->setSelected(false);}for(auto& polygonData : m_polygons){polygonData->setSelected(false);}//重置绘制模式m_currentDrawMode = NoDraw;//恢复默认视图设置setDragModeBasedOnState();forceViewUpdate();
}//加载图像文件
void MyGraphicsView::loadImage(const QString &filePath)
{//清理现有图像clearImage();//加载新图像QPixmap pixmap(filePath);if(pixmap.isNull()){return;}//记录原始大小m_originalPixmapSize = pixmap.size();//创建新图像项m_imageItem = m_scene->addPixmap(pixmap);m_imageItem->setTransformationMode(Qt::SmoothTransformation);m_imageItem->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);m_imageItem->setZValue(0);   //图像项在底层//适配视图并居中显示fitToView();//标记视图状态m_isFitted = true;//设置拖拽模式setDragModeBasedOnState();
}//清除当前图像和所有图形项
void MyGraphicsView::clearImage()
{//删除图像项if(m_imageItem){m_scene->removeItem(m_imageItem);delete m_imageItem;m_imageItem = nullptr;}m_originalPixmapSize = QSize(0, 0);//删除所有矩形for(auto& rectData : m_rects){m_scene->removeItem(rectData);delete rectData;}m_rects.clear();m_rectPixelPositions.clear();m_tempRectItem = nullptr;//删除所有圆形for(auto& circleData : m_circles){m_scene->removeItem(circleData);delete circleData;}m_circles.clear();m_circlePixelPositions.clear();m_tempCircleItem = nullptr;//删除所有多边形for(auto& polygonData : m_polygons){m_scene->removeItem(polygonData);delete polygonData;}m_polygons.clear();m_polygonPixelPositions.clear();m_tempPolygonItem = nullptr;//禁用滚动条m_view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);m_view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);//设置拖拽模式setDragModeBasedOnState();
}//适配视图到图像
void MyGraphicsView::fitToView()
{if(!m_imageItem) return;//重置视图变换m_view->resetTransform();//获取视图和图像的尺寸QRectF viewRect = m_view->viewport()->rect();QRectF imageRect = m_imageItem->boundingRect();//计算最佳缩放比例qreal scaleX = viewRect.width() / imageRect.width();qreal scaleY = viewRect.height() / imageRect.height();m_currentScale = qMin(scaleX, scaleY);//应用缩放m_view->scale(m_currentScale, m_currentScale);//设置场景矩形为图像边界m_scene->setSceneRect(imageRect);//居中显示图像m_imageItem->setPos(0, 0);m_view->centerOn(m_imageItem);//设置滚动条策略bool showScrollbars = (m_currentScale < 1.0);m_view->setHorizontalScrollBarPolicy(showScrollbars ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);m_view->setVerticalScrollBarPolicy(showScrollbars ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);//标记视图状态m_isFitted = true;
}//缩放视图
void MyGraphicsView::zoom(double factor, const QPoint &pos)
{if(!m_imageItem) return;//保存当前鼠标位置在场景中的位置QPointF scenePos = m_view->mapToScene(pos);//应用缩放m_view->scale(factor, factor);//更新当前缩放比例m_currentScale *= factor;//计算缩放后的鼠标位置在视图中的位置QPointF viewportPos = m_view->mapFromScene(scenePos);//计算鼠标位置变化量QPointF delta = pos - viewportPos;//调整滚动条以保持鼠标位置不变m_view->horizontalScrollBar()->setValue(m_view->horizontalScrollBar()->value() + static_cast<int>(delta.x()));m_view->verticalScrollBar()->setValue(m_view->verticalScrollBar()->value() + static_cast<int>(delta.y()));
}//删除超出边界的矩形
void MyGraphicsView::removeOutOfBoundsRectangles()
{if(!m_imageItem || m_rects.empty()) return;//获取图像在场景中的边界bool anyRectRemoved = false;QRectF imageBounds = m_imageItem->sceneBoundingRect();//遍历所有矩形for(auto it = m_rects.begin(); it != m_rects.end(); ){DrawableRectItem* sceneRect = *it;QRectF sceneRectBounds = sceneRect->sceneBoundingRect();//检查矩形是否完全在图像区域内(包括边界)bool isInside = true;if(sceneRectBounds.right() > imageBounds.right() ||sceneRectBounds.left() < imageBounds.left() ||sceneRectBounds.bottom() > imageBounds.bottom() ||sceneRectBounds.top() < imageBounds.top()) {isInside = false;}if(!isInside){//从场景中移除并删除矩形m_scene->removeItem(sceneRect);delete sceneRect;it = m_rects.erase(it);anyRectRemoved = true;}else{++it;}}//更新视图if(anyRectRemoved){m_view->viewport()->update();}//更新像素位置信息updateRectPixelPositions();
}//更新矩形像素位置
void MyGraphicsView::updateRectPixelPositions()
{if(!m_imageItem || m_rects.empty()){m_rectPixelPositions.clear();return;}//获取图像项的场景边界矩形QRectF imageSceneRect = m_imageItem->sceneBoundingRect();m_rectPixelPositions.clear();for(auto& rectData : m_rects){QRectF sceneRect = rectData->sceneGeometryRect();//转换为像素坐标(相对于图像项)QRectF pixelRect(sceneRect.x() - imageSceneRect.x(),sceneRect.y() - imageSceneRect.y(),sceneRect.width(),sceneRect.height());//添加像素位置m_rectPixelPositions.push_back(pixelRect);}
}//删除超出边界的圆形
void MyGraphicsView::removeOutOfBoundsCircles()
{if(!m_imageItem || m_circles.empty()) return;//获取图像在场景中的边界bool anyCircleRemoved = false;QRectF imageBounds = m_imageItem->sceneBoundingRect();//遍历所有圆形for(auto it = m_circles.begin(); it != m_circles.end(); ){DrawableEllipseItem* sceneCircle = *it;QRectF sceneCircleBounds = sceneCircle->sceneBoundingRect();//检查圆形是否与图像区域相交if(!imageBounds.intersects(sceneCircleBounds)){//从场景中移除并删除圆形m_scene->removeItem(sceneCircle);delete sceneCircle;it = m_circles.erase(it);anyCircleRemoved = true;}else{++it;}}//更新视图if(anyCircleRemoved){m_view->viewport()->update();}//更新像素位置信息updateCirclePixelPositions();
}//更新圆形像素位置
void MyGraphicsView::updateCirclePixelPositions()
{if(!m_imageItem || m_circles.empty()){m_circlePixelPositions.clear();return;}//获取图像项的场景边界矩形QRectF imageSceneRect = m_imageItem->sceneBoundingRect();m_circlePixelPositions.clear();for(auto& circleData : m_circles){QRectF sceneRect = circleData->sceneGeometryRect();//转换为像素坐标(相对于图像项)QRectF pixelRect(sceneRect.x() - imageSceneRect.x(),sceneRect.y() - imageSceneRect.y(),sceneRect.width(),sceneRect.height());//添加像素位置m_circlePixelPositions.push_back(pixelRect);}
}//删除超出边界的多边形
void MyGraphicsView::removeOutOfBoundsPolygons()
{if(!m_imageItem || m_polygons.empty()) return;//获取图像在场景中的边界bool anyPolygonRemoved = false;QRectF imageBounds = m_imageItem->sceneBoundingRect();//遍历所有多边形for(auto it = m_polygons.begin(); it != m_polygons.end(); ){DrawablePolygonItem* polygonItem = *it;QVector<QPointF> vertices = polygonItem->vertices();//检查多边形是否有顶点在图像外部bool hasPointOutside = false;for(const QPointF& vertex : vertices){QPointF scenePoint = polygonItem->mapToScene(vertex);if(!imageBounds.contains(scenePoint)){hasPointOutside = true;break;   //发现一个外部点就停止检查}}//从场景中移除并删除多边形if(hasPointOutside){m_scene->removeItem(polygonItem);delete polygonItem;it = m_polygons.erase(it);anyPolygonRemoved = true;}else{++it;}}//更新视图if(anyPolygonRemoved){m_view->viewport()->update();}//更新像素位置信息updatePolygonPixelPositions();
}//更新多边形像素位置
void MyGraphicsView::updatePolygonPixelPositions()
{if(!m_imageItem || m_polygons.empty()){m_polygonPixelPositions.clear();return;}//获取图像项的场景边界矩形QRectF imageSceneRect = m_imageItem->sceneBoundingRect();m_polygonPixelPositions.clear();for(auto& polygonData : m_polygons){//获取多边形在场景中的顶点QPolygonF scenePolygon = polygonData->vertices();//转换每个顶点为像素位置QPolygonF pixelPolygon;for(const QPointF& scenePoint : scenePolygon){//直接相对于图像项左上角计算位置QPointF pixelPoint(scenePoint.x() - imageSceneRect.x(),scenePoint.y() - imageSceneRect.y());pixelPolygon.append(pixelPoint);}m_polygonPixelPositions.push_back(pixelPolygon);}
}//获取所有矩形的像素位置
const std::vector<QRectF>& MyGraphicsView::getRectPixelPositions()
{removeOutOfBoundsRectangles();return m_rectPixelPositions;
}//获取所有圆形的像素位置
const std::vector<QRectF>& MyGraphicsView::getCirclePixelPositions()
{removeOutOfBoundsCircles();return m_circlePixelPositions;
}//获取所有多边形的像素位置
const std::vector<QPolygonF>& MyGraphicsView::getPolygonPixelPositions()
{removeOutOfBoundsPolygons();return m_polygonPixelPositions;
}//获取矩形位置
const vector<Rect> MyGraphicsView::getOpenCVRects()
{//删除超出边界的矩形removeOutOfBoundsRectangles();//创建结果向量vector<Rect> result;result.reserve(m_rectPixelPositions.size());//转换每个QRectF到cv::Rectfor(const QRectF& rect : m_rectPixelPositions){result.emplace_back(static_cast<int>(round(rect.x())),static_cast<int>(round(rect.y())),static_cast<int>(round(rect.width())),static_cast<int>(round(rect.height())));}return result;
}//获取椭圆位置
const vector<OpenCVEllipse> MyGraphicsView::getOpenCVEllipses()
{//删除超出边界的圆形removeOutOfBoundsCircles();//创建结果向量vector<OpenCVEllipse> opencvEllipses;for(const QRectF& rect : m_circlePixelPositions){OpenCVEllipse ellipse;//中心点坐标ellipse.center = cv::Point(static_cast<int>(round(rect.center().x())),static_cast<int>(round(rect.center().y())));//半长轴和半短轴ellipse.axes = cv::Size(static_cast<int>(round(rect.width() / 2.0)),static_cast<int>(round(rect.height() / 2.0)));//旋转角度ellipse.angle = 0.0;//绘制完整椭圆ellipse.startAngle = 0;ellipse.endAngle = 360;//添加到结果向量opencvEllipses.push_back(ellipse);}return opencvEllipses;
}// 获取OpenCV格式的多边形数据
const std::vector<std::vector<cv::Point>> MyGraphicsView::getOpenCVPolygons()
{//删除超出边界的多边形removeOutOfBoundsPolygons();//创建结果向量std::vector<std::vector<cv::Point>> opencvPolygons;opencvPolygons.reserve(m_polygonPixelPositions.size());//转换每个多边形for(const QPolygonF& polygon : m_polygonPixelPositions){//为当前多边形创建点向量std::vector<cv::Point> currentPolygon;currentPolygon.reserve(polygon.size());//转换每个顶点for(const QPointF& point : polygon){currentPolygon.emplace_back(static_cast<int>(std::round(point.x())),static_cast<int>(std::round(point.y())));}//添加到结果向量opencvPolygons.push_back(std::move(currentPolygon));}return opencvPolygons;
}//窗口大小变化事件处理
void MyGraphicsView::resizeEvent(QResizeEvent *event)
{QWidget::resizeEvent(event);if(m_imageItem){//保存调整前的视图中心QPointF sceneCenter = m_view->mapToScene(m_view->viewport()->rect().center());//在初始适配状态下才重新适应视图if(m_isFitted){fitToView();}else{//对于非适配状态,保持当前的缩放比例m_view->resetTransform();m_view->scale(m_currentScale, m_currentScale);//恢复之前的视图中心m_view->centerOn(sceneCenter);//设置滚动条策略QRectF viewRect = m_view->viewport()->rect();QRectF imageRect = m_imageItem->boundingRect();QRectF scaledRect(0, 0, imageRect.width() * m_currentScale,imageRect.height() * m_currentScale);bool showScrollbars = (viewRect.width() < scaledRect.width() ||viewRect.height() < scaledRect.height());m_view->setHorizontalScrollBarPolicy(showScrollbars ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);m_view->setVerticalScrollBarPolicy(showScrollbars ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);m_view->setSceneRect(imageRect);m_imageItem->setPos(0, 0);m_view->centerOn(m_imageItem);}//更新矩形像素位置updateRectPixelPositions();}else{QRectF viewportRect = m_view->viewport()->rect();m_scene->setSceneRect(QRectF(0, 0, viewportRect.width(), viewportRect.height()));}
}//鼠标滚轮事件处理
void MyGraphicsView::wheelEvent(QWheelEvent *event)
{if(m_imageItem){QPoint pos = event->position().toPoint();double factor = 1.0 + event->angleDelta().y() / 1200.0;if(factor < 0.1){factor = 0.1;}//标记视图不再处于初始适配状态m_isFitted = false;zoom(factor, pos);event->accept();}QWidget::wheelEvent(event);
}//事件过滤器处理
bool MyGraphicsView::eventFilter(QObject *obj, QEvent *event)
{//仅处理视图的鼠标事件if(obj != m_view->viewport()){return QWidget::eventFilter(obj, event);}//处理矩形绘制if(m_currentDrawMode == RectangleMode){if(auto mouseEvent = dynamic_cast<QMouseEvent*>(event)){switch(mouseEvent->type()){case QEvent::MouseButtonPress:if(mouseEvent->button() == Qt::LeftButton){//赋值绘制起始点QPointF scenePos = m_view->mapToScene(mouseEvent->pos());m_rectStartPos = scenePos;//创建临时矩形项if(!m_tempRectItem){m_tempRectItem = new DrawableRectItem();m_tempRectItem->setDrawing(true);m_tempRectItem->setRect(QRectF(scenePos, scenePos));m_tempRectItem->setBorderColor(Qt::blue);m_tempRectItem->setBorderWidth(2.0);m_tempRectItem->setVertexColor(QColor(255, 255, 255, 200));m_tempRectItem->setVertexSize(5);m_tempRectItem->setBrush(Qt::NoBrush);m_scene->addItem(m_tempRectItem);if(m_imageItem){m_tempRectItem->setZValue(m_imageItem->zValue() + 1);}}return true;}break;case QEvent::MouseMove:if(m_tempRectItem && (mouseEvent->buttons() & Qt::LeftButton)){//更新临时矩形大小QPointF scenePos = m_view->mapToScene(mouseEvent->pos());QRectF rect(m_rectStartPos, scenePos);m_tempRectItem->setRect(rect.normalized());return true;}break;case QEvent::MouseButtonRelease:if(m_tempRectItem && mouseEvent->button() == Qt::LeftButton  ){//完成矩形绘制QRectF rect = m_tempRectItem->rect();auto rectItem = new DrawableRectItem();rectItem->setRect(rect);rectItem->setBorderColor(Qt::blue);rectItem->setBorderWidth(2.0);rectItem->setVertexColor(Qt::white);rectItem->setVertexSize(6);rectItem->setBrush(Qt::NoBrush);rectItem->setZValue(m_imageItem->zValue() + 1);rectItem->setSelected(true);m_scene->addItem(rectItem);//保存矩形数据DrawableRectItem* rectData = rectItem;m_rects.push_back(rectData);//清理临时项m_scene->removeItem(m_tempRectItem);delete m_tempRectItem;m_tempRectItem = nullptr;//重置起始点位置m_rectStartPos = QPointF();//退出绘制模式m_currentDrawMode = NoDraw;setDragModeBasedOnState();forceViewUpdate();//更新矩形像素位置updateRectPixelPositions();return true;}break;default:break;}}}//处理圆形绘制else if(m_currentDrawMode == CircleMode){if(auto mouseEvent = dynamic_cast<QMouseEvent*>(event)){QPointF scenePos = m_view->mapToScene(mouseEvent->pos());switch(mouseEvent->type()){case QEvent::MouseButtonPress:if(mouseEvent->button() == Qt::LeftButton){//创建临时圆形项if(!m_tempCircleItem){m_circleCenter = scenePos;m_tempCircleItem = new DrawableEllipseItem();m_tempCircleItem->setDrawing(true);m_tempCircleItem->setBorderColor(Qt::green);m_tempCircleItem->setBorderWidth(2.0);m_tempCircleItem->setVertexColor(QColor(255, 255, 255, 200));m_tempCircleItem->setVertexSize(5);m_tempCircleItem->setBrush(Qt::NoBrush);m_scene->addItem(m_tempCircleItem);if(m_imageItem){m_tempCircleItem->setZValue(m_imageItem->zValue() + 1);}m_tempCircleItem->setDrawingStartPoint(m_circleCenter);}return true;}break;case QEvent::MouseMove:if(m_tempCircleItem){QLineF radiusLine(m_circleCenter, scenePos);qreal radius = radiusLine.length();m_tempCircleItem->setRect(m_circleCenter.x() - radius,m_circleCenter.y() - radius,radius * 2,radius * 2);m_tempCircleItem->setDrawingEndPoint(scenePos);return true;}break;case QEvent::MouseButtonRelease:if(m_tempCircleItem && mouseEvent->button() == Qt::LeftButton){//完成圆形绘制QRectF circleRect = m_tempCircleItem->rect();auto circleItem = new DrawableEllipseItem();circleItem->setRect(circleRect);circleItem->setBorderColor(Qt::green);circleItem->setBorderWidth(2.0);circleItem->setVertexColor(Qt::white);circleItem->setVertexSize(6);circleItem->setBrush(Qt::NoBrush);circleItem->setZValue(m_imageItem->zValue() + 1);circleItem->setSelected(true);m_scene->addItem(circleItem);//保存圆形数据DrawableEllipseItem* circleData = circleItem;m_circles.push_back(circleData);//清理临时项m_scene->removeItem(m_tempCircleItem);delete m_tempCircleItem;m_tempCircleItem = nullptr;//重置圆心位置m_circleCenter = QPointF();//退出绘制模式m_currentDrawMode = NoDraw;setDragModeBasedOnState();forceViewUpdate();//更新圆形像素位置updateCirclePixelPositions();return true;}break;default:break;}}}//处理多边形绘制else if(m_currentDrawMode == PolygonMode){if(auto mouseEvent = dynamic_cast<QMouseEvent*>(event)){QPointF scenePos = m_view->mapToScene(mouseEvent->pos());switch (mouseEvent->type()){case QEvent::MouseButtonPress:if(mouseEvent->button() == Qt::LeftButton){//开始绘制多边形if(!m_polygonDrawingStarted){m_polygonPoints.clear();//创建临时多边形项if(!m_tempPolygonItem){m_tempPolygonItem = new DrawablePolygonItem();m_tempPolygonItem->setDrawing(true);m_tempPolygonItem->setBorderColor(Qt::yellow);m_tempPolygonItem->setBorderWidth(2.0);m_tempPolygonItem->setVertexColor(QColor(255, 255, 255, 200));m_tempPolygonItem->setVertexSize(5);m_tempPolygonItem->setBrush(Qt::NoBrush);m_scene->addItem(m_tempPolygonItem);if (m_imageItem){m_tempPolygonItem->setZValue(m_imageItem->zValue() + 1);}}m_polygonDrawingStarted = true;}//添加新点m_polygonPoints.append(scenePos);m_tempPolygonItem->addPoint(scenePos);m_tempPolygonItem->setActiveLine(scenePos);//强制重绘确保显示m_tempPolygonItem->update();m_view->viewport()->update();return true;}break;case QEvent::MouseMove:if(m_tempPolygonItem){m_tempPolygonItem->setActiveLine(scenePos);m_tempPolygonItem->update();m_view->viewport()->update();return true;}break;case QEvent::MouseButtonRelease:if(m_tempPolygonItem && mouseEvent->button() == Qt::RightButton){//添加预置点m_polygonPoints.append(scenePos);//完成多边形绘制(最少需要3个点)if(m_polygonPoints.size() >= 3){auto polygonItem = new DrawablePolygonItem();polygonItem->setDrawing(false);polygonItem->setClosed(true);for(const auto& point : m_polygonPoints){polygonItem->addPoint(point);}polygonItem->setBorderColor(Qt::yellow);polygonItem->setBorderWidth(2.0);polygonItem->setVertexColor(Qt::white);polygonItem->setVertexSize(6);polygonItem->setBrush(Qt::NoBrush);polygonItem->setZValue(m_imageItem->zValue() + 1);polygonItem->setSelected(true);m_scene->addItem(polygonItem);//保存多边形数据DrawablePolygonItem* polyData = polygonItem;m_polygons.push_back(polyData);}//清理临时项m_scene->removeItem(m_tempPolygonItem);delete m_tempPolygonItem;m_tempPolygonItem = nullptr;//重置变量m_polygonPoints.clear();m_polygonDrawingStarted = false;//退出绘制模式m_currentDrawMode = NoDraw;setDragModeBasedOnState();forceViewUpdate();//更新多边形像素位置updatePolygonPixelPositions();return true;}break;default:break;}}}//非绘制模式下else if(m_currentDrawMode == NoDraw){switch (event->type()){case QEvent::MouseMove:if(auto mouseEvent = dynamic_cast<QMouseEvent*>(event)){//取消所有选中状态for(auto& rectData : m_rects){rectData->setSelected(false);}for(auto& circleData : m_circles){circleData->setSelected(false);}for(auto& polygonData : m_polygons){polygonData->setSelected(false);}}break;case QEvent::MouseButtonDblClick:if(auto mouseEvent = dynamic_cast<QMouseEvent*>(event)){//获取双击位置的所有图形项QPointF scenePos = m_view->mapToScene(mouseEvent->pos());QList<QGraphicsItem*> itemsAtPos = m_scene->items(scenePos);//排除背景图像项QGraphicsItem* itemToDelete = nullptr;for(QGraphicsItem* item : itemsAtPos){//跳过背景图像项if(item == m_imageItem) continue;//检查是否真正在图形项内部if(item->contains(item->mapFromScene(scenePos))){itemToDelete = item;break;   //只删除最顶层的项}}//如果找到可删除的项if(itemToDelete){//显示确认对话框QMessageBox::StandardButton reply;reply = QMessageBox::question(nullptr, "删除确认","确定要删除这个图形项吗?",QMessageBox::Yes | QMessageBox::No);if(reply == QMessageBox::Yes){//查找并删除矩形项for(auto it = m_rects.begin(); it != m_rects.end();){DrawableRectItem* rectItem = *it;if(rectItem == itemToDelete){m_scene->removeItem(rectItem);delete rectItem;it = m_rects.erase(it);}else{++it;}}//查找并删除圆形项for(auto it = m_circles.begin(); it != m_circles.end();){DrawableEllipseItem* ellipseItem = *it;if(ellipseItem == itemToDelete){m_scene->removeItem(ellipseItem);delete ellipseItem;it = m_circles.erase(it);}else{++it;}}//查找并删除多边形项for(auto it = m_polygons.begin(); it != m_polygons.end();){DrawablePolygonItem* polygonItem = *it;if(polygonItem == itemToDelete){m_scene->removeItem(polygonItem);delete polygonItem;it = m_polygons.erase(it);}else{++it;}}}//更新视图setDragModeBasedOnState();forceViewUpdate();}}break;default:break;}}return QWidget::eventFilter(obj, event);
}//导入图像按钮点击处理
void MyGraphicsView::onImportClicked()
{QString filePath = QFileDialog::getOpenFileName(this, "选择图像", "","图像文件 (*.png *.jpg *.jpeg *.bmp)");if(!filePath.isEmpty()){loadImage(filePath);}
}//放大按钮点击处理
void MyGraphicsView::onZoomInClicked()
{//重置绘制模式m_currentDrawMode = NoDraw;if(m_imageItem){m_isFitted = false;QPoint viewCenter = m_view->viewport()->rect().center();zoom(1.2, viewCenter);}else{QMessageBox::warning(this, "警告", "请先导入图像");}
}//缩小按钮点击处理
void MyGraphicsView::onZoomOutClicked()
{//重置绘制模式m_currentDrawMode = NoDraw;if(m_imageItem){m_isFitted = false;QPoint viewCenter = m_view->viewport()->rect().center();zoom(0.8, viewCenter);}else{QMessageBox::warning(this, "警告", "请先导入图像");}
}//重置视图按钮点击处理
void MyGraphicsView::onResetClicked()
{//重置绘制模式m_currentDrawMode = NoDraw;if(m_imageItem){//调用适应函数重置视图fitToView();//设置拖拽模式setDragModeBasedOnState();}else{QMessageBox::warning(this, "警告", "请先导入图像");}
}//绘制矩形按钮点击处理
void MyGraphicsView::onDrawRectClicked()
{//在导入图像前不能添加矩形if(!m_imageItem){QMessageBox::warning(this, "警告", "请先导入图像再添加矩形");return;}//退出其他绘制模式exitDrawingMode();//进入矩形绘制模式m_rectStartPos = QPointF();m_currentDrawMode = RectangleMode;setDragModeBasedOnState();
}//绘制圆形按钮点击处理
void MyGraphicsView::onDrawCircleClicked()
{//在导入图像前不能添加圆形if(!m_imageItem){QMessageBox::warning(this, "警告", "请先导入图像再添加圆形");return;}//退出其他绘制模式exitDrawingMode();//进入圆形绘制模式m_circleCenter = QPointF();m_currentDrawMode = CircleMode;setDragModeBasedOnState();
}//绘制多边形按钮点击处理
void MyGraphicsView::onDrawPolygonClicked()
{//在导入图像前不能添加圆形if(!m_imageItem){QMessageBox::warning(this, "警告", "请先导入图像再添加多边形");return;}updatePolygonPixelPositions();//退出其他绘制模式exitDrawingMode();//开始多边形绘制m_polygonPoints.clear();m_polygonDrawingStarted = false;m_currentDrawMode = PolygonMode;setDragModeBasedOnState();
}

三、可绘制矩形项类

这里实现了一个可绘制矩形项类,主要功能如下,其实最重要的是重写了一些鼠标事件以及绘制函数,详细内容可见下文代码:
矩形项 DrawableRectItem
1.drawablerectitem.h

#ifndef DRAWABLERECTITEM_H
#define DRAWABLERECTITEM_H#include <QGraphicsRectItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QColor>
#include <QCursor>
#include <QVector>
#include <cmath>
#include <QTimer>
#include <QDebug>/*** @class DrawableRectItem* @brief 可绘制矩形项类,继承自QGraphicsRectItem** 该类扩展了标准矩形项的功能,支持:* 1. 自定义绘制样式(填充色、边框颜色、宽度)* 2. 绘制过程中的虚线显示* 3. 选择状态下的控制点(四个顶点)* 4. 鼠标交互:移动、调整大小* 5. 特殊的绘制状态管理*/
class DrawableRectItem : public QGraphicsRectItem
{
public:explicit DrawableRectItem(QGraphicsItem *parent = nullptr);void initItem();void setFillColor(const QColor &color);void setBorderColor(const QColor &color);void setBorderWidth(qreal width);void setVertexColor(const QColor &color);void setVertexSize(qreal size);QColor fillColor() const;QColor borderColor() const;qreal borderWidth() const;void setDrawing(bool drawing);bool isDrawing() const;void requestViewUpdate();QVector<QPointF> getVertices() const;QRectF sceneGeometryRect() const;protected:QRectF boundingRect() const override;void mousePressEvent(QGraphicsSceneMouseEvent *event) override;void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;private:QColor m_fillColor;     //矩形填充颜色QColor m_borderColor;   //矩形边框颜色qreal m_borderWidth;    //边框宽度QColor m_vertexColor;   //控制点填充颜色qreal m_vertexSize;     //控制点半径大小int m_resizeVertex;     //当前调整的控制点索引bool m_isDrawing;       //是否处于绘制状态bool m_isResizing;      //是否正在调整大小bool m_isDragging;      //是否正在拖动整个矩形QPointF m_startPoint;   //拖动起始点};
#endif // DRAWABLERECTITEM_H

2.drawablerectitem.cpp

#include "drawablerectitem.h"DrawableRectItem::DrawableRectItem(QGraphicsItem *parent): QGraphicsRectItem(parent)
{//设置图形项的标志setFlag(QGraphicsItem::ItemIsSelectable, true);setFlag(QGraphicsItem::ItemIsMovable, true);setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);setAcceptHoverEvents(true);//初始化项的状态和属性initItem();
}//初始化矩形项的状态和属性
void DrawableRectItem::initItem()
{m_fillColor = Qt::transparent;   //透明填充m_borderColor = Qt::blue;        //蓝色边框m_vertexColor = Qt::white;       //白色控制点m_borderWidth = 2.0;m_vertexSize = 6;m_resizeVertex = -1;m_isDrawing = false;m_isResizing = false;m_isDragging = false;m_startPoint = QPointF();
}//设置填充颜色
void DrawableRectItem::setFillColor(const QColor &color)
{m_fillColor = color;update();   //更新视图
}//设置边框颜色
void DrawableRectItem::setBorderColor(const QColor &color)
{m_borderColor = color;update();
}//设置边框宽度
void DrawableRectItem::setBorderWidth(qreal width)
{m_borderWidth = width;update();
}//设置控制点颜色
void DrawableRectItem::setVertexColor(const QColor &color)
{m_vertexColor = color;update();
}//设置控制点大小
void DrawableRectItem::setVertexSize(qreal size)
{m_vertexSize = size;update();
}//获取填充颜色
QColor DrawableRectItem::fillColor() const
{return m_fillColor;
}//获取边框颜色
QColor DrawableRectItem::borderColor() const
{return m_borderColor;
}//获取边框宽度
qreal DrawableRectItem::borderWidth() const
{return m_borderWidth;
}//设置是否处于绘制状态
void DrawableRectItem::setDrawing(bool drawing)
{m_isDrawing = drawing;update();
}//检查是否处于绘制状态
bool DrawableRectItem::isDrawing() const
{return m_isDrawing;
}//请求所有关联视图更新显示
void DrawableRectItem::requestViewUpdate()
{//获取所有关联的视图if(!scene()) return;QList<QGraphicsView*> views = scene()->views();for(QGraphicsView* view : views){//更新视图区域以确保及时刷新view->viewport()->update();}
}//获取矩形的四个顶点位置
QVector<QPointF> DrawableRectItem::getVertices() const
{QVector<QPointF> vertices;QRectF r = rect();vertices << r.topLeft()       //左上<< r.topRight()      //右上<< r.bottomRight()   //右下<< r.bottomLeft();   //左下return vertices;
}//获取场景坐标系中的实际边界矩形
QRectF DrawableRectItem::sceneGeometryRect() const
{//获取局部坐标系的实际椭圆边界QRectF localRect = DrawableRectItem::rect();//转换为场景坐标系return mapRectToScene(localRect);
}//计算边界矩形(包含控制点范围)
QRectF DrawableRectItem::boundingRect() const
{//扩大边界框以包含控制点const qreal halfVertexSize = m_vertexSize * 2;const QRectF baseRect = QGraphicsRectItem::rect();return baseRect.adjusted(-halfVertexSize, -halfVertexSize, halfVertexSize, halfVertexSize);
}//鼠标按下事件处理
void DrawableRectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{//仅处理左键点击if(event->button() != Qt::LeftButton){QGraphicsRectItem::mousePressEvent(event);return;}//检查是否点击在顶点的圆形区域内QPointF pos = event->pos();const qreal vertexMargin = m_vertexSize * 2;   //点击的容差范围QVector<QPointF> vertices = getVertices();for(int i = 0; i < vertices.size(); i++){//计算两点之间的距离,如果距离在容差范围内,视为点击了控制点QPointF vertex = vertices[i];qreal distance = QLineF(pos, vertex).length();if (distance <= vertexMargin){//进入调整状态m_resizeVertex = i;m_isResizing = true;setCursor(Qt::CrossCursor);//记录调整起始点m_startPoint = event->scenePos();event->accept();return;}}//检查是否点击在矩形内部if(rect().contains(pos)){//进入拖动状态m_isDragging = true;setCursor(Qt::ClosedHandCursor);m_startPoint = event->scenePos();event->accept();return;}//其他情况传递基类处理QGraphicsRectItem::mousePressEvent(event);
}//鼠标移动事件处理
void DrawableRectItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{if(m_isResizing){//保存调整前的矩形区域QRectF oldRect = rect();QRectF r = oldRect;QPointF scenePos = event->scenePos();//计算自上次移动后的位置增量QPointF delta = scenePos - m_startPoint;m_startPoint = scenePos;//根据点击的顶点进行不同方向的调整switch(m_resizeVertex){case 0: r.setTopLeft(r.topLeft() + delta); break;           //左上case 1: r.setTopRight(r.topRight() + delta); break;         //右上case 2: r.setBottomRight(r.bottomRight() + delta); break;   //右下case 3: r.setBottomLeft(r.bottomLeft() + delta); break;     //左下}//保存更新后的矩形(确保有效大小)QRectF newRect = r.normalized();if(newRect.width() < 1) newRect.setWidth(1);if(newRect.height() < 1) newRect.setHeight(1);setRect(newRect);//计算需要更新的区域(合并旧区域和新区域)QRectF updateRect = oldRect.united(newRect);updateRect.adjust(-m_vertexSize*2, -m_vertexSize*2,m_vertexSize*2, m_vertexSize*2);   //扩展更新区域包含顶点//触发局部重绘update(updateRect);// 请求视图立即刷新requestViewUpdate();event->accept();}else if(m_isDragging){//拖动矩形前更新位置QPointF oldPos = pos();QRectF oldRect = rect();//计算位置增量QPointF delta = event->scenePos() - m_startPoint;m_startPoint = event->scenePos();//转换为局部坐标增量病更新位置QPointF localDelta = mapFromScene(mapToScene(QPointF(0,0)) + delta);setPos(pos() + localDelta);//计算移动后的区域QRectF movedRect = rect();movedRect.moveTo(pos());//计算需要更新的区域(合并新旧位置区域)QRectF updateRect = oldRect.united(movedRect);updateRect.moveTo(oldPos.x() < pos().x() ? oldPos.x() : pos().x(),oldPos.y() < pos().y() ? oldPos.y() : pos().y());updateRect.adjust(-m_vertexSize*2, -m_vertexSize*2,m_vertexSize*2, m_vertexSize*2);//触发场景重绘scene()->update(updateRect);//请求视图立即刷新requestViewUpdate();event->accept();}else{QGraphicsRectItem::mouseMoveEvent(event);}
}//鼠标释放事件处理
void DrawableRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{//结束状态m_resizeVertex = -1;m_isResizing = false;m_isDragging = false;setSelected(true);//恢复手型光标setCursor(Qt::OpenHandCursor);//请求重绘以显示控制点update();//确保视图刷新requestViewUpdate();QGraphicsRectItem::mouseReleaseEvent(event);
}//鼠标悬停移动事件处理
void DrawableRectItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{//设置为选中setSelected(true);//检查是否悬停在顶点的圆形区域内QPointF pos = event->pos();const qreal vertexMargin = m_vertexSize * 2;QVector<QPointF> vertices = getVertices();for(int i = 0; i < vertices.size(); i++){QPointF vertex = vertices[i];qreal distance = QLineF(pos, vertex).length();if(distance <= vertexMargin){//悬停在控制点上,置为十字光标setCursor(Qt::CrossCursor);return;}}//检查是否悬停在矩形内部if(rect().contains(pos)){//置为手指型光标setCursor(Qt::PointingHandCursor);return;}QGraphicsRectItem::hoverMoveEvent(event);
}//重写绘制函数
void DrawableRectItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{Q_UNUSED(option);Q_UNUSED(widget);//打开抗锯齿painter->setRenderHint(QPainter::Antialiasing, true);//在绘制状态下使用虚线边框if(m_isDrawing){painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::DashLine));painter->setBrush(m_fillColor);painter->drawRect(rect());}//非绘制状态下if(!m_isDrawing){//实线绘制矩形painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::SolidLine));painter->setBrush(m_fillColor);painter->drawRect(rect());//当项被选中且未被拖动时,显示顶点if(isSelected() && !m_isDragging){painter->setPen(QPen(Qt::black, 1));painter->setBrush(m_vertexColor);for(const QPointF &vertex : getVertices()){painter->drawEllipse(vertex, m_vertexSize, m_vertexSize);}}}
}

四、可绘制椭圆/圆形项类

圆形项 DrawableEllipseItem
1.drawableellipseitem.h

#ifndef DRAWABLEELLIPSEITEM_H
#define DRAWABLEELLIPSEITEM_H#include <QGraphicsEllipseItem>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QColor>
#include <QCursor>
#include <QVector>
#include <QTimer>
#include <cmath>
#include <QDebug>/*** @class DrawableEllipseItem* @brief 可绘制椭圆/圆形项类,继承自QGraphicsEllipseItem** 该类扩展了标准椭圆项的功能,支持:* 1. 自定义绘制样式(填充色、边框颜色、宽度)* 2. 绘制过程中的半径线显示* 3. 选择状态下的控制点(上下左右四个方向)* 4. 鼠标交互:移动、调整大小* 5. 特殊的绘制状态管理*/
class DrawableEllipseItem : public QGraphicsEllipseItem
{
public:explicit DrawableEllipseItem(QGraphicsItem *parent = nullptr);void initItem();void setFillColor(const QColor &color);void setBorderColor(const QColor &color);void setBorderWidth(qreal width);void setVertexColor(const QColor &color);void setVertexSize(qreal size);QColor fillColor() const;QColor borderColor() const;qreal borderWidth() const;void setDrawing(bool drawing);bool isDrawing() const;void setDrawingStartPoint(const QPointF& point);void setDrawingEndPoint(const QPointF& point);void requestViewUpdate();QVector<QPointF> getVerticesOnCircle() const;QRectF sceneGeometryRect() const;protected:QRectF boundingRect() const override;void mousePressEvent(QGraphicsSceneMouseEvent *event) override;void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;private:QColor m_fillColor;     //椭圆填充颜色QColor m_borderColor;   //椭圆边框颜色qreal m_borderWidth;    //边框宽度QColor m_vertexColor;   //控制点填充颜色qreal m_vertexSize;     //控制点半径大小int m_resizeVertex;     //当前调整的控制点索引bool m_isDrawing;       //是否处于绘制状态bool m_isResizing;      //是否正在调整大小bool m_isDragging;      //是否正在拖动整个椭圆QPointF m_startPoint;     //拖动起始点QPointF m_drawingStart;   //圆心位置QPointF m_drawingEnd;     //鼠标当前位置(半径线终点)};
#endif // DRAWABLEELLIPSEITEM_H

2.drawableellipseitem.cpp

#include "drawableellipseitem.h"DrawableEllipseItem::DrawableEllipseItem(QGraphicsItem *parent): QGraphicsEllipseItem(parent)
{//设置图形项的标志setFlag(QGraphicsItem::ItemIsSelectable, true);setFlag(QGraphicsItem::ItemIsMovable, true);setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);setAcceptHoverEvents(true);//初始化项的状态和属性initItem();
}//初始化椭圆项的状态和属性
void DrawableEllipseItem::initItem()
{m_fillColor = Qt::transparent;   //透明填充m_borderColor = Qt::green;       //绿色边框m_vertexColor = Qt::white;       //白色控制点m_borderWidth = 2.0;m_vertexSize = 6;m_resizeVertex = -1;m_isDrawing = false;m_isResizing = false;m_isDragging = false;m_startPoint = QPointF();m_drawingStart = QPointF();m_drawingEnd = QPointF();
}//设置填充颜色
void DrawableEllipseItem::setFillColor(const QColor &color)
{m_fillColor = color;update();   //更新视图
}//设置边框颜色
void DrawableEllipseItem::setBorderColor(const QColor &color)
{m_borderColor = color;update();
}//设置边框宽度
void DrawableEllipseItem::setBorderWidth(qreal width)
{m_borderWidth = width;update();
}//设置控制点颜色
void DrawableEllipseItem::setVertexColor(const QColor &color)
{m_vertexColor = color;update();
}//设置控制点大小
void DrawableEllipseItem::setVertexSize(qreal size)
{m_vertexSize = size;update();
}//获取填充颜色
QColor DrawableEllipseItem::fillColor() const
{return m_fillColor;
}//获取边框颜色
QColor DrawableEllipseItem::borderColor() const
{return m_borderColor;
}//获取边框宽度
qreal DrawableEllipseItem::borderWidth() const
{return m_borderWidth;
}//设置是否处于绘制状态
void DrawableEllipseItem::setDrawing(bool drawing)
{m_isDrawing = drawing;update();
}//检查是否处于绘制状态
bool DrawableEllipseItem::isDrawing() const
{return m_isDrawing;
}//设置椭圆绘制起始点(圆心位置)
void DrawableEllipseItem::setDrawingStartPoint(const QPointF& point)
{m_drawingStart = point;update();
}//设置椭圆绘制结束点(当前鼠标位置)
void DrawableEllipseItem::setDrawingEndPoint(const QPointF& point)
{m_drawingEnd = point;update();
}//请求所有关联视图更新显示
void DrawableEllipseItem::requestViewUpdate()
{//获取所有关联的视图if(!scene()) return;QList<QGraphicsView*> views = scene()->views();for(QGraphicsView* view : views){//更新视图区域以确保及时刷新view->viewport()->update();}
}//获取椭圆上的四个控制点位置
QVector<QPointF> DrawableEllipseItem::getVerticesOnCircle() const
{QVector<QPointF> vertices;QPointF center = rect().center();qreal radiusX = rect().width() / 2.0;qreal radiusY = rect().height() / 2.0;vertices << QPointF(center.x(), center.y() - radiusY);   //上点vertices << QPointF(center.x() + radiusX, center.y());   //右点vertices << QPointF(center.x(), center.y() + radiusY);   //下点vertices << QPointF(center.x() - radiusX, center.y());   //左点return vertices;
}//获取场景坐标系中的实际边界矩形
QRectF DrawableEllipseItem::sceneGeometryRect() const
{//获取局部坐标系的实际椭圆边界QRectF localRect = QGraphicsEllipseItem::rect();//转换为场景坐标系return mapRectToScene(localRect);
}//计算边界矩形(包含控制点范围)
QRectF DrawableEllipseItem::boundingRect() const
{//扩大边界框以包含控制点const qreal halfVertexSize = m_vertexSize * 2;const QRectF baseRect = QGraphicsEllipseItem::rect();return baseRect.adjusted(-halfVertexSize, -halfVertexSize, halfVertexSize, halfVertexSize);
}//鼠标按下事件处理
void DrawableEllipseItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{//仅处理左键点击if(event->button() != Qt::LeftButton){QGraphicsEllipseItem::mousePressEvent(event);return;}//检查是否点击在顶点的圆形区域内QPointF pos = event->pos();const qreal vertexMargin = m_vertexSize * 2;   //点击的容差范围QVector<QPointF> vertices = getVerticesOnCircle();for(int i = 0; i < vertices.size(); i++){//计算两点之间的距离,如果距离在容差范围内,视为点击了控制点QPointF vertex = vertices[i];qreal distance = QLineF(pos, vertex).length();if(distance <= vertexMargin){//进入调整状态m_resizeVertex = i;m_isResizing = true;setCursor(Qt::CrossCursor);//记录调整起始点m_startPoint = event->scenePos();event->accept();return;}}//检查是否点击在圆形内部if(rect().contains(pos)){//在圆形内部,进入拖动状态m_isDragging = true;setCursor(Qt::ClosedHandCursor);event->accept();return;}//其他情况传递基类处理QGraphicsEllipseItem::mousePressEvent(event);
}//鼠标移动事件处理
void DrawableEllipseItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{if(m_isResizing){//保存调整前的矩形区域QRectF oldRect = rect();QRectF r = oldRect;QPointF scenePos = event->scenePos();//计算自上次移动后的位置增量QPointF delta = scenePos - m_startPoint;m_startPoint = scenePos;//根据点击的顶点进行不同方向的调整switch(m_resizeVertex){case 0: r.setTopLeft(r.topLeft() + delta); break;           //上点case 1: r.setTopRight(r.topRight() + delta); break;         //右点case 2: r.setBottomRight(r.bottomRight() + delta); break;   //下点case 3: r.setBottomLeft(r.bottomLeft() + delta); break;     //左点}//保存更新后的矩形(确保有效大小)QRectF newRect = r.normalized();if(newRect.width() < 1) newRect.setWidth(1);if(newRect.height() < 1) newRect.setHeight(1);setRect(newRect);//计算需要更新的区域(合并旧区域和新区域)QRectF updateRect = oldRect.united(newRect);updateRect.adjust(-m_vertexSize*2, -m_vertexSize*2,m_vertexSize*2, m_vertexSize*2);   //扩展更新区域包含顶点//触发局部重绘update(updateRect);//请求视图立即刷新requestViewUpdate();event->accept();}else if(m_isDragging){//拖动圆形前更新位置QPointF oldPos = pos();QRectF oldRect = rect();//执行拖动操作QGraphicsEllipseItem::mouseMoveEvent(event);//计算移动后的区域QRectF movedRect = rect();movedRect.moveTo(pos());//计算需要更新的区域QRectF updateRect = oldRect.united(movedRect);updateRect.moveTo(oldPos.x() < pos().x() ? oldPos.x() : pos().x(),oldPos.y() < pos().y() ? oldPos.y() : pos().y());updateRect.adjust(-m_vertexSize*2, -m_vertexSize*2,m_vertexSize*2, m_vertexSize*2);//触发场景重绘scene()->update(updateRect);//请求视图立即刷新requestViewUpdate();event->accept();}else{QGraphicsEllipseItem::mouseMoveEvent(event);}
}//鼠标释放事件处理
void DrawableEllipseItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{//结束状态m_resizeVertex = -1;m_isResizing = false;m_isDragging = false;//恢复手型光标setCursor(Qt::OpenHandCursor);//请求重绘以显示控制点update();//确保视图刷新requestViewUpdate();QGraphicsEllipseItem::mouseReleaseEvent(event);
}//鼠标悬停移动事件处理
void DrawableEllipseItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{//设置为选中setSelected(true);//检查是否悬停在顶点的圆形区域内QPointF pos = event->pos();const qreal vertexMargin = m_vertexSize * 2;QVector<QPointF> vertices = getVerticesOnCircle();for(int i = 0; i < vertices.size(); i++){QPointF vertex = vertices[i];qreal distance = QLineF(pos, vertex).length();if(distance <= vertexMargin){//悬停在控制点上,置为十字光标setCursor(Qt::CrossCursor);return;}}//检查是否悬停在圆形内部if(rect().contains(pos)){//置为手指型光标setCursor(Qt::PointingHandCursor);return;}QGraphicsEllipseItem::hoverMoveEvent(event);
}//重写绘制函数
void DrawableEllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{Q_UNUSED(option);Q_UNUSED(widget);//打开抗锯齿painter->setRenderHint(QPainter::Antialiasing, true);//在绘制状态下使用虚线边框if(m_isDrawing){//绘制椭圆painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::DashLine));painter->setBrush(m_fillColor);painter->drawEllipse(rect());//绘制虚线半径线painter->setPen(QPen(Qt::darkGray, m_borderWidth, Qt::DashLine));painter->drawLine(m_drawingStart, m_drawingEnd);}//非绘制状态下if(!m_isDrawing){//实线绘制椭圆painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::SolidLine));painter->setBrush(m_fillColor);painter->drawEllipse(rect());if(isSelected() && !m_isDragging){painter->setPen(QPen(Qt::black, 1));painter->setBrush(m_vertexColor);for(const QPointF &vertex : getVerticesOnCircle()){painter->drawEllipse(vertex, m_vertexSize, m_vertexSize);}}}
}

五、可绘制多边形项类

多边形项 DrawablePolygonItem
1.drawablepolygonitem.h

#ifndef DRAWABLEPOLYGONITEM_H
#define DRAWABLEPOLYGONITEM_H#include <QGraphicsObject>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QPointF>
#include <QCursor>
#include <QVector>
#include <cmath>
#include <QDebug>/*** @class DrawablePolygonItem* @brief 可绘制多边形项类,继承自QGraphicsPolygonItem** 该类实现了交互式多边形绘制功能,支持:* 1. 自定义绘制样式(填充色、边框颜色、宽度)* 2. 多边形的绘制过程管理(添加顶点、预览点)* 3. 多边形完成后的编辑(移动顶点、移动整个多边形)* 4. 鼠标交互:绘制、编辑、移动*/
class DrawablePolygonItem : public QGraphicsPolygonItem
{
public:explicit DrawablePolygonItem(QGraphicsItem *parent = nullptr);void initItem();void setFillColor(const QColor &color);void setBorderColor(const QColor &color);void setBorderWidth(qreal width);void setVertexColor(const QColor &color);void setVertexSize(qreal size);QColor fillColor() const;QColor borderColor() const;qreal borderWidth() const;void setDrawing(bool drawing);bool isDrawing() const;void setClosed(bool closed);bool isClosed() const;void setActiveLine(const QPointF &point);void addPoint(const QPointF &point);void updatePolygon();bool findVertexAtPosition(const QPointF &pos, int &vertexIndex);void requestViewUpdate();QVector<QPointF> vertices() const;protected:QRectF boundingRect() const override;void mousePressEvent(QGraphicsSceneMouseEvent *event) override;void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;private:QColor m_fillColor;     //多边形填充颜色QColor m_borderColor;   //多边形边框颜色qreal m_borderWidth;    //边框宽度QColor m_vertexColor;   //控制点填充颜色qreal m_vertexSize;     //控制点半径大小int m_resizeVertex;        //当前调整的顶点索引bool m_isClosed;           //多边形是否已闭合bool m_isDrawing;          //是否处于绘制状态bool m_isResizing;         //是否正在调整顶点bool m_isDragging;         //是否正在拖动整个多边形QPointF m_startPoint;      //操作起始点QPointF m_activeLineEnd;   //活动线终点(鼠标当前位置)QVector<QPointF> m_vertices;   //顶点坐标集合};
#endif // DRAWABLEPOLYGONITEM_H

2.drawablepolygonitem.cpp

#include "drawablepolygonitem.h"DrawablePolygonItem::DrawablePolygonItem(QGraphicsItem *parent): QGraphicsPolygonItem(parent)
{//设置图形项的标志setFlag(QGraphicsItem::ItemIsSelectable, true);setFlag(QGraphicsItem::ItemIsMovable, true);setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);setAcceptHoverEvents(true);//初始化项的状态和属性initItem();
}//初始化多边形项的状态和属性
void DrawablePolygonItem::initItem()
{m_fillColor = Qt::transparent;   //透明填充m_borderColor = Qt::yellow;      //黄色边框m_vertexColor = Qt::white;       //白色控制点m_borderWidth = 2.0;m_vertexSize = 6;m_resizeVertex = -1;m_isClosed = false;m_isDrawing = false;m_isResizing = false;m_isDragging = false;m_startPoint = QPointF();m_activeLineEnd = QPointF();m_vertices.clear();
}//设置填充颜色
void DrawablePolygonItem::setFillColor(const QColor &color)
{m_fillColor = color;update();   //更新视图
}//设置边框颜色
void DrawablePolygonItem::setBorderColor(const QColor &color)
{m_borderColor = color;update();
}//设置边框宽度
void DrawablePolygonItem::setBorderWidth(qreal width)
{m_borderWidth = width;update();
}//设置控制点颜色
void DrawablePolygonItem::setVertexColor(const QColor &color)
{m_vertexColor = color;update();
}//设置控制点大小
void DrawablePolygonItem::setVertexSize(qreal size)
{m_vertexSize = size;update();
}//获取填充颜色
QColor DrawablePolygonItem::fillColor() const
{return m_fillColor;
}//获取边框颜色
QColor DrawablePolygonItem::borderColor() const
{return m_borderColor;
}//获取边框宽度
qreal DrawablePolygonItem::borderWidth() const
{return m_borderWidth;
}//设置是否处于绘制状态
void DrawablePolygonItem::setDrawing(bool drawing)
{m_isDrawing = drawing;update();
}//检查是否处于绘制状态
bool DrawablePolygonItem::isDrawing() const
{return m_isDrawing;
}//设置多边形是否闭合
void DrawablePolygonItem::setClosed(bool closed)
{m_isClosed = closed;updatePolygon();
}//检查多边形是否已闭合
bool DrawablePolygonItem::isClosed() const
{return m_isClosed;
}//设置活动线终点(预览线位置)
void DrawablePolygonItem::setActiveLine(const QPointF &point)
{m_activeLineEnd = point;update();
}//添加新顶点到多边形
void DrawablePolygonItem::addPoint(const QPointF &point)
{m_vertices.append(point);updatePolygon();
}//更新多边形几何形状
void DrawablePolygonItem::updatePolygon()
{QPolygonF polygon(m_vertices);if(m_isClosed && m_vertices.size() > 2){polygon.append(m_vertices.first());}setPolygon(polygon);update();
}//查找指定位置是否有顶点
bool DrawablePolygonItem::findVertexAtPosition(const QPointF &pos, int &vertexIndex)
{const qreal vertexMargin = m_vertexSize * 3;   //点击的容差范围for(int i = 0; i < m_vertices.size(); i++){if(QLineF(pos, m_vertices.at(i)).length() <= vertexMargin){vertexIndex = i;return true;}}return false;
}//请求所有关联视图更新显示
void DrawablePolygonItem::requestViewUpdate()
{//获取所有关联的视图if(!scene()) return;QList<QGraphicsView*> views = scene()->views();for(QGraphicsView* view : views){//更新视图区域以确保及时刷新view->viewport()->update();}
}// 获取顶点坐标集合
QVector<QPointF> DrawablePolygonItem::vertices() const
{return m_vertices;
}//计算边界矩形(包含控制点范围)
QRectF DrawablePolygonItem::boundingRect() const
{//扩大边界框以包含控制点const qreal halfVertexSize = m_vertexSize * 2;const QRectF baseRect = QGraphicsPolygonItem::boundingRect();return baseRect.adjusted(-halfVertexSize, -halfVertexSize, halfVertexSize, halfVertexSize);
}//鼠标按下事件处理
void DrawablePolygonItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
{//仅处理左键点击if(event->button() != Qt::LeftButton){QGraphicsPolygonItem::mousePressEvent(event);return;}//检查是否点击在顶点的圆形区域内QPointF pos = event->pos();if(findVertexAtPosition(pos, m_resizeVertex)){//进入调整状态m_isResizing = true;setCursor(Qt::CrossCursor);m_startPoint = event->scenePos();event->accept();return;}//检查是否点击在内部if(contains(pos)){//进入拖动状态m_isDragging = true;setCursor(Qt::ClosedHandCursor);m_startPoint = event->scenePos();event->accept();return;}//其他情况传递基类处理QGraphicsPolygonItem::mousePressEvent(event);
}//鼠标移动事件处理
void DrawablePolygonItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{if(m_isResizing){//计算场景坐标偏移量QPointF delta = event->scenePos() - m_startPoint;m_startPoint = event->scenePos();//更新顶点位置m_vertices[m_resizeVertex] = mapFromScene(mapToScene(m_vertices[m_resizeVertex]) + delta);//更新多边形updatePolygon();requestViewUpdate();event->accept();}else if(m_isDragging){//计算位置增量QPointF delta = event->scenePos() - m_startPoint;m_startPoint = event->scenePos();//更新所有顶点位置for(int i = 0; i < m_vertices.size(); i++){m_vertices[i] = mapFromScene(mapToScene(m_vertices[i]) + delta);}//更新多边形updatePolygon();requestViewUpdate();event->accept();}else{QGraphicsPolygonItem::mouseMoveEvent(event);}
}//鼠标释放事件处理
void DrawablePolygonItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{//结束状态m_resizeVertex = -1;m_isResizing = false;m_isDragging = false;//恢复手型光标setCursor(Qt::OpenHandCursor);//请求重绘以显示控制点update();//确保视图刷新requestViewUpdate();QGraphicsPolygonItem::mouseReleaseEvent(event);
}void DrawablePolygonItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{//设置为选中setSelected(true);//检查是否悬停在顶点的圆形区域内QPointF pos = event->pos();int vertexIndex = -1;if(findVertexAtPosition(pos, vertexIndex)){//悬停在顶点上,置为十字光标setCursor(Qt::CrossCursor);return;}//检查是否悬停在圆形内部if(contains(pos)){//置为手指型光标setCursor(Qt::PointingHandCursor);return;}QGraphicsPolygonItem::hoverMoveEvent(event);
}void DrawablePolygonItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {Q_UNUSED(option);Q_UNUSED(widget);//打开抗锯齿painter->setRenderHint(QPainter::Antialiasing, true);//在绘制状态下显示虚线预览线if(m_isDrawing && !m_vertices.isEmpty()){//鼠标位置绘制"虚拟"顶点painter->setPen(QPen(Qt::black, 1));painter->setBrush(QColor(200, 200, 255, 150));   //半透明的蓝色painter->drawEllipse(m_activeLineEnd, m_vertexSize, m_vertexSize);//绘制顶点painter->setPen(QPen(Qt::black, 1));painter->setBrush(m_vertexColor);for(const QPointF &vertex : m_vertices){painter->drawEllipse(vertex, m_vertexSize, m_vertexSize);}//绘制虚线预览线painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::DashLine));painter->drawLine(m_vertices.first(), m_activeLineEnd);painter->drawLine(m_vertices.last(), m_activeLineEnd);//绘制实线边painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::SolidLine));painter->setBrush(m_fillColor);for (int i = 1; i < m_vertices.size(); i++){painter->drawLine(m_vertices[i-1], m_vertices[i]);}}//非绘制状态下,实线显示各边if(!m_isDrawing){painter->setPen(QPen(m_borderColor, m_borderWidth, Qt::SolidLine));painter->setBrush(m_fillColor);for (int i = 1; i < m_vertices.size(); i++){painter->drawLine(m_vertices[i-1], m_vertices[i]);}painter->drawLine(m_vertices.last(), m_vertices.first());//当项被选中且未被拖动时,显示顶点if(isSelected() && !m_isDragging){painter->setPen(QPen(Qt::black, 1));painter->setBrush(m_vertexColor);for(const QPointF &vertex : m_vertices){painter->drawEllipse(vertex, m_vertexSize, m_vertexSize);}}}
}

六、使用示例

1.mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QMessageBox>#include "mygraphicsview.h"QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();void initWidget();void updateImage();void updateMat();private slots:void on_pb_show_clicked();void on_pb_load_clicked();void on_pb_draw_1_clicked();void on_pb_draw_2_clicked();private:Ui::MainWindow *ui;MyGraphicsView *m_graphicsView;QString m_originFile;QImage m_originImage;Mat m_originMat;
};
#endif // MAINWINDOW_H

2.mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);this->initWidget();
}MainWindow::~MainWindow()
{delete ui;
}//初始化界面
void MainWindow::initWidget()
{setWindowTitle("图像智能交互界面");resize(600, 400);m_graphicsView = new MyGraphicsView();}void MainWindow::updateImage()
{//创建结果图像(复制原始图像)QImage resultImage = m_originImage.copy();//准备在图像上绘制QPainter painter(&resultImage);painter.setRenderHint(QPainter::Antialiasing, true);//绘制矩形painter.setPen(QPen(Qt::blue, 2));painter.setBrush(Qt::NoBrush);for(const QRectF& rect : m_graphicsView->getRectPixelPositions()){painter.drawRect(rect);}//绘制圆形/椭圆painter.setPen(QPen(Qt::green, 2));for(const QRectF& ellipseRect : m_graphicsView->getCirclePixelPositions()){painter.drawEllipse(ellipseRect);}//绘制多边形painter.setPen(QPen(Qt::yellow, 2));painter.setBrush(Qt::NoBrush);for(const QPolygonF& polygon : m_graphicsView->getPolygonPixelPositions()){painter.drawPolygon(polygon);}painter.end();//将图像设置到标签ui->lb_image->setPixmap(QPixmap::fromImage(resultImage).scaled(ui->lb_image->size(),Qt::KeepAspectRatio, Qt::SmoothTransformation));
}void MainWindow::updateMat()
{//读取原始图像std::string filename = m_originFile.toStdString();cv::Mat imageMat = cv::imread(filename, cv::IMREAD_COLOR);//如果读取失败(可能是中文路径问题),改用Qt方式读取if(imageMat.empty()){QImage qImage(m_originFile);if(qImage.isNull()) return;//转换为与OpenCV兼容的格式qImage = qImage.convertToFormat(QImage::Format_RGB888);//创建cv::Mat并复制数据imageMat = cv::Mat(qImage.height(), qImage.width(), CV_8UC3);for (int y = 0; y < qImage.height(); y++) {const uchar* src = qImage.constScanLine(y);uchar* dst = imageMat.ptr<uchar>(y);memcpy(dst, src, static_cast<size_t>(qImage.bytesPerLine()));}//转换颜色空间(Qt RGB -> OpenCV BGR)cv::cvtColor(imageMat, imageMat, cv::COLOR_RGB2BGR);}//绘制矩形std::vector<cv::Rect> rects = m_graphicsView->getOpenCVRects();for(const cv::Rect& rect : rects){cv::rectangle(imageMat, rect, cv::Scalar(0, 0, 255), 2);}//绘制椭圆std::vector<OpenCVEllipse> ellipses = m_graphicsView->getOpenCVEllipses();for(const OpenCVEllipse& ellipse : ellipses){cv::ellipse(imageMat,ellipse.center,ellipse.axes,ellipse.angle,ellipse.startAngle,ellipse.endAngle,cv::Scalar(0, 255, 0),2);}//绘制多边形std::vector<std::vector<cv::Point>> polygons = m_graphicsView->getOpenCVPolygons();bool isClosed = true;for(const auto& polygon : polygons){const cv::Point* pts = polygon.data();int npts = static_cast<int>(polygon.size());cv::polylines(imageMat, &pts, &npts, 1, isClosed, cv::Scalar(255, 255, 0), 2);}//将Mat转换为QImageQImage resultImage(imageMat.data, imageMat.cols, imageMat.rows, static_cast<int>(imageMat.step),QImage::Format_RGB888);//将图像设置到标签ui->lb_image->setPixmap(QPixmap::fromImage(resultImage).scaled(ui->lb_image->size(),Qt::KeepAspectRatio, Qt::SmoothTransformation));
}void MainWindow::on_pb_show_clicked()
{if(!m_originFile.isEmpty()){m_graphicsView->loadImage(m_originFile);}m_graphicsView->show();m_graphicsView->fitToView();
}void MainWindow::on_pb_load_clicked()
{QString filePath = QFileDialog::getOpenFileName(this, "选择图像", "","图像文件 (*.png *.jpg *.jpeg *.bmp)");if(!filePath.isEmpty()){m_originFile = filePath;qDebug()<<"图像名:"<<m_originFile;//加载图像时转换格式m_originImage = QImage(filePath).convertToFormat(QImage::Format_ARGB32_Premultiplied);ui->lb_image->setPixmap(QPixmap::fromImage(m_originImage).scaled(ui->lb_image->size(),Qt::KeepAspectRatio, Qt::SmoothTransformation));}
}void MainWindow::on_pb_draw_1_clicked()
{updateImage();
}void MainWindow::on_pb_draw_2_clicked()
{updateMat();
}

总结

本文中使用图形视图框架实现了一个简单的类似于图像标注的工具,实现了矩形项,圆形项及多边形项的绘制,可以看到各自定义图形项的实现都类似,就是重写各项的鼠标事件以及绘制事件。该框架还具有更多复杂的交互功能,需要我们不断的学习实践哦~


hello:
共同学习,共同进步,如果还有相关问题,可在评论区留言进行讨论。

http://www.dtcms.com/a/303535.html

相关文章:

  • 一个Pycharm窗口添加多个项目来满足运行多个项目的需求
  • linux常用的指令
  • HTML响应式SEO公司网站源码
  • MVSNet系列网络概述
  • 7寸工业模组 XA070Y2-L01芯显科技详细参数资料
  • MCU中的外设总线是什么?
  • 带 USB 接口的多功能 AI 降噪消回音模组 A-59P:革新语音处理体验​
  • 基于Flask的智能停车场管理系统开发实践
  • Java基础-IO流
  • Python day27
  • GoLand 项目从 0 到 1:第三天 —— 图数据库版本管理方案调研与中间件部署
  • 064_不可变集合与同步集合
  • python列表与元组--python005
  • 《中小学音乐教育》是什么级别的期刊?是正规期刊吗?能评职称吗?
  • c++: 尾置返回类型(Trailing Return Type)
  • 深度解析Manus:从多智能体架构到通用AI Agent的技术革命
  • Unity教程(二十五)技能系统 掷剑技能(下)冻结时间实现
  • PostgreSQL 详解
  • java每日精进 7.28【流程设计6.0(泳池和泳道)】
  • V-Ray 7.00.08 for 3ds Max 2021-2026 安装与配置教程(含语言补丁)
  • HTML5 `<figure>` 标签:提升网页语义化与可访问性的利器
  • 【2025/07/28】GitHub 今日热门项目
  • Solidity基础(教程①-简单数字存储)
  • 第二十一章:AI的“视觉压缩引擎”与“想象力温床”
  • AIBOX硬件设计概述
  • 什么是 LoRA 学习笔记
  • 项目执行标准流程是什么样的,如何制定
  • Java 接口入门学习笔记:从概念到简单实践
  • ts学习3
  • Microsoft 365中的Compromised User Detection功能深度解析:智能识别与防护用户账户安全的利器