1、背景
在视觉我的项目开发过程中碰到了图像显示和 ROI 矩形框或其余框体的显示的需要,最早我在开发过程中间接将 Halcon 的显示窗口间接贴在 Qt 的控件上,这样就省去了图像转换后再绘图的操作(Halcon 具备独特的图像格式 HObject),然而 Halcon 没有图层的概念,只有 create_drawing_object_circle 这些算子能够应用,但这些在图像实时刷新的时候比拟耗时且也没有图层能够操作(Win 环境实时成果还行,Linux 下较难实现实时成果),采纳 Qpixmap 显示在 UI 端,并应用 QGraphicsItem 来实现自定义的图形显示需要,成果比应用 Halcon 窗口显示要好很多,本篇就如何实现自定义的 QGraphicsItem 开发实现各种图形的显示进行开展。
2、成果展现
目前依据需要,给出了如下图所示的图形的自定义成果,能够依据须要创立不同形态的图形框:
3、自定义创立同心圆
3.1 同心圆的创立
首先在创立的同心圆结构类里,有中心点,两个圆半径,以及两个圆上的 Edge 点(用于拖动扭转圆大小),其类的定义如下
// 同心圆
class BConcentricCircle : public BCircle
{
public:
BConcentricCircle(qreal x, qreal y, qreal radius1, qreal radius2, ItemType type);
enum {Type = 22};
int type() const
{return Type;}
void updateOtherRadius();
void setAnotherEdge(QPointF p);
protected:
virtual QRectF boundingRect() const override;
virtual void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
public:
QPointF m_another_edge;
qreal m_another_radius;
};
同心圆的策略是在圆的根底上再画一个圆,所以在其构造函数中要先去定义同心圆的几个点 - 圆心、圆上边缘点。
BConcentricCircle::BConcentricCircle(qreal x, qreal y, qreal radius1, qreal radius2, ItemType type)
: BCircle(x, y, radius1, type), m_another_edge(x+radius2*sqrt(2)/2, y+radius2*sqrt(2)/2)
{BPointItem *point = new BPointItem(this, m_another_edge, BPointItem::Special);
point->setParentItem(this);
m_pointList.append(point);
m_pointList.setRandColor();
updateOtherRadius();}
由构造函数可知,同心圆是由一个圆和另一个圆组成,其蕴含 BCircle(x, y, radius1, type),再以圆心和 m_another_edge(x+radius2sqrt(2)/2, y+radius2sqrt(2)/2) 去画另一个圆。其余局部实现如下:
void BConcentricCircle::updateOtherRadius()
{m_another_radius = sqrt(pow(m_center.x() - m_another_edge.x(), 2) +
pow(m_center.y() - m_another_edge.y(), 2));
}
void BConcentricCircle::setAnotherEdge(QPointF p)
{m_another_edge = p;}
QRectF BConcentricCircle::boundingRect() const
{
qreal temp = m_radius > m_another_radius ? m_radius : m_another_radius;
return QRectF(m_center.x() - temp, m_center.y() - temp, temp * 2, temp * 2);
}
void BConcentricCircle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{painter->setPen(this->pen());
painter->setBrush(this->brush());
QRectF ret(m_center.x() - m_another_radius, m_center.y() - m_another_radius, m_another_radius * 2, m_another_radius * 2);
painter->drawEllipse(ret);
BCircle::paint(painter, option, widget);
}
3.2 同心圆扭转大小
创立同心圆时为咱们创立了 3 个点,一个圆心点,一个内圆边缘点,一个外圆边缘点,拖动圆心点时,可实现 Item 整体的挪动,而拖动内圆或外圆上的点时能够扭转圆的大小。这里是创立一个点的类来实现拖动扭转的成果。
class BPointItem : public QObject, public QAbstractGraphicsShapeItem
{
Q_OBJECT
public:
enum PointType {
Center = 0, // 中心点
Edge, // 边缘点(可拖动扭转图形的形态、大小)Special // 非凡性能点
};
BPointItem(QAbstractGraphicsShapeItem* parent, QPointF p, PointType type);
QPointF getPoint() { return m_point;}
void setPoint(QPointF p) {m_point = p;}
protected:
virtual QRectF boundingRect() const override;
virtual void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
private:
QPointF m_point;
PointType m_type;
};
其中 Paint 函数用于画点,其实现函数如下:
void BPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{Q_UNUSED(option);
Q_UNUSED(widget);
painter->setPen(this->pen());
painter->setBrush(this->brush());
this->setPos(m_point);
switch (m_type) {
case Center:
painter->drawEllipse(-4, -4, 8, 8);
break;
case Edge:
painter->drawRect(QRectF(-4, -4, 8, 8));
break;
case Special:
painter->drawRect(QRectF(-4, -4, 8, 8));
break;
default: break;
}
}
而后通过 mouseMoveEvent 事件函数批改边缘点地位后驱动同心圆类里的 Paint 函数进行重绘
void BPointItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{if ( event->buttons() == Qt::LeftButton ) {qreal dx = event->scenePos().x() - event->lastScenePos().x();
qreal dy = event->scenePos().y() - event->lastScenePos().y();
BGraphicsItem* item = static_cast<BGraphicsItem *>(this->parentItem());
BGraphicsItem::ItemType itemType = item->getType();
switch (m_type) {
case Center: {item->moveBy(dx, dy);
this->scene()->update();
} break;
case Edge: {switch (itemType) {
case BGraphicsItem::ItemType::Ellipse: {BEllipse *ellipse = dynamic_cast<BEllipse *>(item);
ellipse->setEdge(m_point);
} break;
case BGraphicsItem::ItemType::Circle: {BCircle *circle = dynamic_cast<BCircle *>(item);
circle->setEdge(m_point);
circle->updateRadius();} break;
case BGraphicsItem::ItemType::Concentric_Circle: {BCircle *circle = dynamic_cast<BCircle *>(item);
circle->setEdge(m_point);
circle->updateRadius();} break;
case BGraphicsItem::ItemType::Pie: {BPie *pie = dynamic_cast<BPie *>(item);
pie->setEdge(m_point);
pie->updateRadius();
pie->updateAngle();} break;
case BGraphicsItem::ItemType::Chord: {BChord *chord = dynamic_cast<BChord *>(item);
chord->setEdge(m_point);
chord->updateRadius();
chord->updateEndAngle();} break;
case BGraphicsItem::ItemType::Rectangle: {BRectangle *rectangle = dynamic_cast<BRectangle *>(item);
rectangle->setEdge(m_point);
} break;
default: break;
}
} break;
default: break;
}
4、自定义创立箭头
这里基本操作跟步骤 3 差不多,这里我在应用的时候不须要批改箭头直线的长短,所以我只进行了箭头的创立类:
// 箭头
class BArrow : public QGraphicsItem
{
public:
BArrow();
BArrow(QPointF startPoint,QPointF endPoint);
void setLineItem(QPointF startP, QPointF endP);
void setColor(QColor color);
enum {Type = 33};
int type() const
{return Type;}
protected:
virtual QRectF boundingRect() const override;
virtual void paint(QPainter *painter,
const QStyleOptionGraphicsItem *option,
QWidget *widget) override;
private:
void CreatePointNodes(void);
private:
QPointF m_EndP;
QPointF m_points[3]; // 保留箭头的顶点
QColor m_Color; // 设置箭头色彩
};
这里咱们通过终点和起点来创立一个箭头直线,应用 setLineItem 函数来批改箭头直线的尺寸,应用 setColor 函数来设置箭头的色彩。实现代码如下:
BArrow::BArrow()
{
}
BArrow::BArrow(QPointF startPoint, QPointF endPoint)
{setFlag(ItemIsSelectable);
setAcceptHoverEvents(true);
m_Color = Qt::green;
setLineItem(startPoint,endPoint);
}
void BArrow::setLineItem(QPointF startP, QPointF endP)
{
m_EndP = endP - startP;
CreatePointNodes();}
void BArrow::setColor(QColor color)
{m_Color = color;}
void BArrow::CreatePointNodes()
{
// 箭头直线与程度方向的夹角再加 pi
float angle = atan2(m_EndP.y(), m_EndP.x()) + 3.1415926;
// 这两个值须要依据理论场景的坐标大小进行调整,float ExtRefArrowLenght = 4;// 箭头末端大小的长度,float ExtRefArrowDegrees = 1.047;// 箭头末端顶角的一半
m_points[0] = m_EndP;
// 求得箭头点 1 坐标
m_points[1].setX(m_EndP.x() + ExtRefArrowLenght * cos(angle - ExtRefArrowDegrees));
m_points[1].setY(m_EndP.y() + ExtRefArrowLenght * sin(angle - ExtRefArrowDegrees));
// 求得箭头点 2 坐标
m_points[2].setX(m_EndP.x() + ExtRefArrowLenght * cos(angle + ExtRefArrowDegrees));
m_points[2].setY(m_EndP.y() + ExtRefArrowLenght * sin(angle + ExtRefArrowDegrees));
}
QRectF BArrow::boundingRect() const
{return QRectF(0, 0, m_EndP.x(), m_EndP.y());
}
void BArrow::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{painter->setRenderHint(QPainter::Antialiasing, true); // 设置反走样,防锯齿
QPen pen(m_Color, 2, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin);
QBrush brush(m_Color, Qt::SolidPattern);
painter->setPen(pen);
painter->setBrush(brush);
QLineF line(0, 0, m_EndP.x(), m_EndP.y());
painter->drawLine(line);
painter->drawPolygon(m_points, 3);
}
5、函数调用
在 Mainwindow 中间接应用指针来调用
BConcentricCircle *m_conCircle = new BConcentricCircle(0, 0, 50, 80, BGraphicsItem::ItemType::Concentric_Circle);
m_scene->addItem(m_conCircle);
BArrow *m_Arrow = new BArrow(QPointF(0,0),QPointF(100,100));
m_Arrow->setPos(50,50);
m_Arrow->setColor(Qt::red);
m_scene->addItem(m_Arrow);
6、总结
这个自定义的 Item 构建自身并不难,次要是要理解 QGraphicsView 中的 View、Scene、Item 以及控件之间的坐标对应关系,有机会我也会针对这个进行具体的学习和回顾。本文中波及了其余的框体创立未具体介绍,如果感兴趣能够查看代码自行理解,具体代码如下:
本节代码仓库地址