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_OBJECTpublic:    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以及控件之间的坐标对应关系,有机会我也会针对这个进行具体的学习和回顾。本文中波及了其余的框体创立未具体介绍,如果感兴趣能够查看代码自行理解,具体代码如下:
本节代码仓库地址