自定義可拖動多邊形控件,原創做者是趙彥博(QQ:408815041 zyb920@hotmail.com),創做之初主要是爲了可以在視頻區域內用戶自定義可拖動的多個區域,便可用來做爲警惕區域,也可用來其餘的處理,拿到對應的多邊形座標集合,本控件的主要難點是如何計算一個點在一個多邊形區域內,什麼時候完成一個多邊形區域,支持多個多邊形。c++
#ifndef CUSTOMGRAPHICS_H #define CUSTOMGRAPHICS_H /** * 自定義多邊形控件 做者:趙彥博(QQ:408815041 zyb920@hotmail.com) 2019-3-28 * 1:自定義隨意繪製多邊形 * 2:產生閉合形狀後可單擊選中移動整個多邊形 * 3:可拉動某個點 * 4:支持多個多邊形 * 5:鼠標右鍵退出繪製 * 6:可設置各類顏色 */ #include <QWidget> #ifdef quc #if (QT_VERSION < QT_VERSION_CHECK(5,7,0)) #include <QtDesigner/QDesignerExportWidget> #else #include <QtUiPlugin/QDesignerExportWidget> #endif class QDESIGNER_WIDGET_EXPORT CustomGraphics : public QWidget #else class CustomGraphics : public QWidget #endif { Q_OBJECT Q_PROPERTY(bool selectDotVisible READ getSelectDotVisible WRITE setSelectDotVisible) Q_PROPERTY(int dotRadius READ getDotRadius WRITE setDotRadius) Q_PROPERTY(int lineWidth READ getLineWidth WRITE setLineWidth) Q_PROPERTY(QColor dotColor READ getDotColor WRITE setDotColor) Q_PROPERTY(QColor lineColor READ getLineColor WRITE setLineColor) Q_PROPERTY(QColor polygonColor READ getPolygonColor WRITE setPolygonColor) Q_PROPERTY(QColor selectColor READ getSelectColor WRITE setSelectColor) public: typedef struct { QVector<QPoint> pos; bool selected; } Polygon; explicit CustomGraphics(QWidget *parent = 0); protected: void mousePressEvent(QMouseEvent *e); void mouseMoveEvent(QMouseEvent *e); void mouseReleaseEvent(QMouseEvent *e); void paintEvent(QPaintEvent *); void drawPolygon(QPainter *p, const Polygon &v); void drawLines(QPainter *p, const QList<QPoint> &list, bool isFirst = true); private: bool selectDotVisible; //選中點可見 int dotRadius; //點的半徑 int lineWidth; //線條寬度 QColor dotColor; //點的顏色 QColor lineColor; //線條顏色 QColor polygonColor; //多邊形顏色 QColor selectColor; //選中顏色 QPoint tempPoint; //臨時點 QList<QPoint> tempPoints; //點集合 QList<Polygon> tempPolygons;//多邊形集合 bool pressed; //鼠標是否按下 QPoint lastPoint; //鼠標按下處的座標 QPoint ellipsePos; //保存按下點的座標 int selectedEllipseIndex; //選中點的index Polygon pressedPolygon; //保存按下時多邊形的原始座標 int selectedIndex; //選中多邊形的index private: //計算兩點間的距離 double length(const QPoint &p1, const QPoint &p2); //檢測是否選中多邊形 bool checkPoint(const QVector<QPoint> &points, int x, int y); public: bool getSelectDotVisible() const; int getDotRadius() const; int getLineWidth() const; QColor getDotColor() const; QColor getLineColor() const; QColor getPolygonColor() const; QColor getSelectColor() const; QSize sizeHint() const; QSize minimumSizeHint() const; public Q_SLOTS: void setSelectDotVisible(bool selectDotVisible); void setDotRadius(int dotRadius); void setLineWidth(int lineWidth); void setDotColor(const QColor &dotColor); void setLineColor(const QColor &lineColor); void setPolygonColor(const QColor &polygonColor); void setSelectColor(const QColor &selectColor); //清除臨時繪製的 void clearTemp(); //清除全部 void clearAll(); }; #endif // CUSTOMGRAPHICS_H
void CustomGraphics::mousePressEvent(QMouseEvent *e) { QPoint p = e->pos(); pressed = true; lastPoint = this->mapToGlobal(p); //連線模式下不選中 if(tempPoints.isEmpty()) { //若是選中了,檢測是否點到點上 bool selectedPot = false; selectedEllipseIndex = -1; if (selectedIndex != -1) { for(int i = tempPolygons.at(selectedIndex).pos.size() - 1; i >= 0; --i) { if(length(p, tempPolygons.at(selectedIndex).pos[i]) <= 36) { selectedPot = true; selectedEllipseIndex = i; ellipsePos = tempPolygons.at(selectedIndex).pos[i]; break; } } } //當前選中了點則不用重繪 if(selectedPot) { return; } //判斷是否選中一個 selectedIndex = -1; for(int i = tempPolygons.size() - 1; i >= 0; --i) { tempPolygons[i].selected = checkPoint(tempPolygons.at(i).pos, p.x(), p.y()); if(tempPolygons.at(i).selected) { //防止重疊部分 if(selectedIndex == -1) { selectedIndex = i; pressedPolygon = tempPolygons.at(i); } else { tempPolygons[i].selected = false; } } } this->update(); } } void CustomGraphics::mouseMoveEvent(QMouseEvent *e) { tempPoint = e->pos(); if(pressed && selectedIndex != -1) { //總體偏移座標 QPoint delta = this->mapToGlobal(tempPoint) - lastPoint; int len = tempPolygons.at(selectedIndex).pos.size(); if(selectedEllipseIndex != -1) { //移動點 tempPolygons[selectedIndex].pos[selectedEllipseIndex] = ellipsePos + delta; } else if(selectedIndex != -1) { //移動面 for(int i = 0; i < len; ++i) { tempPolygons[selectedIndex].pos[i] = pressedPolygon.pos.at(i) + delta; } } } this->update(); } void CustomGraphics::mouseReleaseEvent(QMouseEvent *e) { //鼠標右鍵清空臨時的 if (e->button() == Qt::RightButton) { clearTemp(); return; } //檢測再次點擊與最後個點 - 還沒寫 pressed = false; if(selectedIndex != -1) { return; } QPoint point = e->pos(); if(tempPoints.count() > 0) { qreal len = (qPow(tempPoints.first().x() - point.x() , 2.0) + qPow(tempPoints.first().y() - point.y() , 2.0) ); if(len < 100) { //完成一個多邊形 if(tempPoints.size() >= 3) { Polygon pol; pol.pos = tempPoints.toVector(); pol.selected = false; tempPolygons.append(pol); } tempPoints.clear(); this->update(); return; } } tempPoints.append(point); this->update(); } void CustomGraphics::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing, true); //繪製多邊形 foreach(const Polygon &p, tempPolygons) { drawPolygon(&painter, p); } //繪製點集合 drawLines(&painter, tempPoints, false); } void CustomGraphics::drawPolygon(QPainter *p, const Polygon &v) { p->save(); //繪製多邊形 p->setPen(QPen(lineColor, lineWidth)); v.selected ? p->setBrush(selectColor) : p->setBrush(polygonColor); p->drawPolygon(v.pos.data(), v.pos.size()); //繪製圓點 if(selectDotVisible && v.selected) { p->setPen(Qt::NoPen); p->setBrush(dotColor); foreach(const QPoint &point, v.pos) { p->drawEllipse(point, dotRadius, dotRadius); } } p->restore(); } void CustomGraphics::drawLines(QPainter *p, const QList<QPoint> &list, bool isFirst) { p->save(); int count = list.count(); if (count > 0) { //繪製點集合 p->setPen(Qt::NoPen); p->setBrush(dotColor); for(int i = 0; i < count; ++i) { p->drawEllipse(list.at(i), dotRadius, dotRadius); } //繪製線條集合 p->setPen(QPen(lineColor, lineWidth)); p->setBrush(Qt::NoBrush); for(int i = 0; i < count - 1; ++i) { p->drawLine(list.at(i), list.at(i + 1)); } //繪製最後一條線條 p->drawLine(list.last(), isFirst ? list.first() : tempPoint); } p->restore(); } double CustomGraphics::length(const QPoint &p1, const QPoint &p2) { //平方和 return qPow(p1.x() - p2.x(), 2.0) + qPow(p1.y() - p2.y(), 2.0); } bool CustomGraphics::checkPoint(const QVector<QPoint> &points, int testx, int testy) { //最少保證3個點 const int count = points.size(); if(count < 3) { return false; } QList<int> vertx, verty; for(int i = 0; i < count; ++i) { vertx << points.at(i).x(); verty << points.at(i).y(); } //核心算法,計算座標是否在多邊形內部 int i = 0, j, c = 0; for (i = 0, j = count - 1; i < count; j = i++) { bool b1 = (verty.at(i) > testy) != (verty.at(j) > testy); bool b2 = (testx < (vertx.at(j) - vertx.at(i)) * (testy - verty.at(i)) / (verty.at(j) - verty.at(i)) + vertx.at(i)); if (b1 && b2) { c = !c; } } return c; }