#pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qscrollbar.h" #include "global.h" #include #include #include #include #include #include #include #include #include #include class ImageWidgetManager; // 后台图像处理(压缩 + 颜色空间转换)线程工作对象 class ImageProcessWorker : public QObject { Q_OBJECT public: explicit ImageProcessWorker(int threshold, QObject* parent = nullptr) : QObject(parent), threshold(threshold) {} public slots: void process(const cv::Mat& img, const QString& type) { if (img.empty()) return; QImage qimg = matToQImageWithCompress(img, threshold); emit imageReady(type, qimg); } signals: void imageReady(const QString& type, const QImage& img); private: int threshold = 0; QImage matToQImageWithCompress(const cv::Mat& srcIn, int thr) { if (srcIn.empty()) return QImage(); cv::Mat src = srcIn; while ((thr > 0) && (src.cols > thr || src.rows > thr)) { cv::Mat half; cv::resize(src, half, cv::Size(src.cols / 2, src.rows / 2), 0, 0, cv::INTER_AREA); src = half; } cv::Mat display; QImage img; switch (src.type()) { case CV_8UC3: cv::cvtColor(src, display, cv::COLOR_BGR2RGB); img = QImage(display.data, display.cols, display.rows, display.step, QImage::Format_RGB888).copy(); break; case CV_8UC1: img = QImage(src.data, src.cols, src.rows, src.step, QImage::Format_Grayscale8).copy(); break; case CV_8UC4: cv::cvtColor(src, display, cv::COLOR_BGRA2RGBA); img = QImage(display.data, display.cols, display.rows, display.step, QImage::Format_RGBA8888).copy(); break; default: src.convertTo(display, CV_8UC3); cv::cvtColor(display, display, cv::COLOR_BGR2RGB); img = QImage(display.data, display.cols, display.rows, display.step, QImage::Format_RGB888).copy(); break; } return img; } }; //单画幅 class SonGraphicsView : public QGraphicsView{ public: explicit SonGraphicsView(const QString& nameIn) : name(nameIn), scene(new QGraphicsScene(this)), //rectGroup(new QGraphicsItemGroup()), imageWidth(1), imageHeight(1), m_isPan(false), rightClicked(false){ QMutexLocker locker(&mutex); setDragMode(QGraphicsView::NoDrag); setInteractive(false); setEnabled(false); setObjectName("SonGraphicsView"); //scene->addItem(rectGroup); setScene(scene); #if switchToTongYongUi == true setStyleSheet(R"( QGraphicsView#SonGraphicsView { background: rgb(32, 36, 40); background-repeat: no-repeat; background-position: center center; border-radius: 10px; /* 圆角半径 */ } )"); #else setStyleSheet(R"( QGraphicsView#SonGraphicsView { background: transparent; background-image: url(:/icon/icon/waitting.png); background-repeat: no-repeat; background-position: center center; })"); #endif } void setImg(const QPixmap& pixmap) { showImg(pixmap); } void resetImage() { QMutexLocker locker(&mutex); scene->clear(); pixmapItem = nullptr; setEnabled(false); } protected: void wheelEvent(QWheelEvent* event) override { if (scene->items().isEmpty()) return; QPoint numDegrees = event->angleDelta() / 8; if (!numDegrees.isNull()) { zoom(numDegrees / 15); centerOn(currentPos); } event->accept(); } void keyPressEvent(QKeyEvent* event) override { // 可以添加快捷键重置视图等操作 } void mousePressEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { togglePan(true, event->pos()); event->accept(); } else if (event->button() == Qt::RightButton) { startPos = mapToScene(event->pos()); rightClicked = true; //rectItem = new QGraphicsRectItem(QRectF(startPos, QSizeF(0, 0))); //rectItem->setPen(QPen(Qt::red, 5, Qt::DashLine)); //rectGroup->addToGroup(rectItem); update(); } else { event->ignore(); } } void mouseMoveEvent(QMouseEvent* event) override { currentPos = mapToScene(event->pos()); /* if (rightClicked && rectItem) { QPointF endPos = currentPos; qreal width = endPos.x() - startPos.x(); qreal height = endPos.y() - startPos.y(); rectItem->setRect(startPos.x(), startPos.y(), width, height); }*/ if (m_isPan) { pan(event->pos()); event->accept(); } else { event->ignore(); } } void mouseReleaseEvent(QMouseEvent* event) override { if (event->button() == Qt::LeftButton) { togglePan(false); event->accept(); } //else if (event->button() == Qt::RightButton) { // rightClicked = false; // QPointF endPos = mapToScene(event->pos()); // qreal width = endPos.x() - startPos.x(); // qreal height = endPos.y() - startPos.y(); // deleteAllRect(); // if (width != 0 && height != 0) { // double scaleRate = getMax(width == 0 ? 1.0 : rect().width() / width,height == 0 ? 1.0 : rect().height() / height); // scale(scaleRate, scaleRate); // centerOn(startPos + QPointF(width / 2, height / 2)); // } //} else { event->ignore(); } } private: QString name; QGraphicsScene* scene = nullptr; //QGraphicsItemGroup* rectGroup = nullptr; //QGraphicsRectItem* rectItem = nullptr; // QLabel* imgLabel = nullptr; // 删除 QGraphicsPixmapItem* pixmapItem = nullptr; int imageWidth = 1; int imageHeight = 1; QPointF currentPos; QPointF startPos; bool rightClicked = false; bool m_isPan = false; QPoint m_prevPan; QMutex mutex; void showImg(const QPixmap& img) { QMutexLocker locker(&mutex); setEnabled(true); setMouseTracking(true); imageWidth = img.width(); imageHeight = img.height(); if (pixmapItem) { scene->removeItem(pixmapItem); delete pixmapItem; pixmapItem = nullptr; } pixmapItem = new QGraphicsPixmapItem(img); scene->addItem(pixmapItem); setSceneRect(img.rect()); fitInView(scene->itemsBoundingRect(), Qt::KeepAspectRatio); } void zoom(const QPoint& factor) { QRectF FOV = mapToScene(rect()).boundingRect(); float scaleX = static_cast(imageWidth) / FOV.width(); float scaleY = static_cast(imageHeight) / FOV.height(); double currentScale = transform().m11(); if ((factor.y() > 0 && currentScale > 10.0) || (factor.y() < 0 && currentScale < 0.1)) return; scale(factor.y() > 0 ? 1.2 : 0.8, factor.y() > 0 ? 1.2 : 0.8); } void togglePan(bool pan, const QPoint& startPos = QPoint()) { if (pan == m_isPan) return; m_isPan = pan; m_prevPan = pan ? startPos : QPoint(0, 0); setCursor(pan ? Qt::ClosedHandCursor : Qt::ArrowCursor); } void pan(const QPoint& panTo) { QPoint delta = panTo - m_prevPan; m_prevPan = panTo; horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x()); verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y()); } void deleteAllRect() { /*if (!rectGroup) return; for (QGraphicsItem* item : rectGroup->childItems()) { QGraphicsRectItem* rect = qgraphicsitem_cast(item); if (rect) { rectGroup->removeFromGroup(rect); scene->removeItem(rect); delete rect; } }*/ } }; //切换窗口 在切换时用pixmap替换现有的以实现 class ImageWidget : public QWidget { Q_OBJECT public: QComboBox switchBox; QMutex switchViewerMutex; //缩放状态相关 QPushButton* zoombtn; bool isZoomed = false; QSize originalSize; QPoint originalPosition; QGridLayout* parentGridLayout = nullptr; int originalRow = -1; int originalColumn = -1; int originalRowSpan = 1; int originalColumnSpan = 1; ImageWidget(QWidget* parent = nullptr) : QWidget(parent) { qRegisterMetaType("cv::Mat"); QVBoxLayout* layout = new QVBoxLayout(this); setObjectName("ImageWidget"); setStyleSheet(R"( #ImageWidget{ background: rgb(50, 55, 60); })"); layout->setContentsMargins(2, 2, 2, 2); viewer = new SonGraphicsView(""); this->setMaximumSize(9999, 9999); QPalette palette; palette.setColor(QPalette::WindowText, Qt::white); QFont font("微软雅黑", 8 , QFont::Bold); QWidget* hWidget = new QWidget(this); QHBoxLayout* hLayout = new QHBoxLayout(hWidget); hWidget->setFixedHeight(16); /* auto spaceItem = new QSpacerItem(100, 1); hLayout->addSpacerItem(spaceItem);*/ zoombtn = new QPushButton(); zoombtn->setFixedSize(16, 16); zoombtn ->setStyleSheet( R"( QPushButton { background-color: rgba(0, 0, 0, 0.5); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 3px; } QPushButton:hover { background-color: rgba(0, 0, 0, 0.7); border: 1px solid rgba(255, 255, 255, 0.5); } )" ); zoombtn->setIcon(QPixmap(":icon/icon/fullscreen.png")); zoombtn->setToolTip("放大窗口"); hLayout->addWidget(zoombtn); connect(zoombtn, &QPushButton::clicked, this, &ImageWidget::toggleZoom); label = new QLabel(u8"相机标签"); label->setAlignment(Qt::AlignLeft); label->setFont(font); label->setPalette(palette); label->setFixedHeight(21); switchBox.setFont(font); switchBox.setPalette(palette); switchBox.setFixedHeight(21); switchBox.setFixedWidth(100); connect(&switchBox, &QComboBox::currentTextChanged, [=](QString type) { switchViewType(type); }); switchBox.hide(); label->setAlignment(Qt::AlignCenter); hLayout->addWidget(label); hLayout->addWidget(&switchBox); hLayout->setContentsMargins(0, 0, 0, 0); //hLayout->addWidget(switchBtn); layout->addWidget(hWidget); layout->addWidget(viewer); viewer->setDragMode(QGraphicsView::ScrollHandDrag); viewer->setTransformationAnchor(QGraphicsView::AnchorUnderMouse); viewer->setRenderHint(QPainter::Antialiasing); viewer->setRenderHint(QPainter::SmoothPixmapTransform); viewer->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); viewer->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); //view->fitInView(item, Qt::KeepAspectRatio); //view->setBackgroundBrush(QBrush(QColor(0, 0, 0))); //QPixmap backgroundImage(".//icon//watting.png"); //QGraphicsPixmapItem* backgroundItem = new QGraphicsPixmapItem(backgroundImage); //view->scene->addItem(backgroundItem); // //setContextMenuPolicy(Qt::CustomContextMenu); //(this, &QWidget::customContextMenuRequested, this, &ImageWidget::showContextMenu); // 后台线程初始化 workerThread = new QThread(this); worker = new ImageProcessWorker(uiShowImgCompressThreshold); worker->moveToThread(workerThread); connect(this, &ImageWidget::requestProcess, worker, &ImageProcessWorker::process, Qt::QueuedConnection); connect(worker, &ImageProcessWorker::imageReady, this, &ImageWidget::onImageReady, Qt::QueuedConnection); workerThread->start(); } ~ImageWidget(){ if (workerThread) { workerThread->quit(); workerThread->wait(); } } void setTitle(const QString& text) { label->setText(text); } void createViewer(QString name){ map.insert(name, QPixmap()); switchBox.addItem(name); } //设置父网格布局和位置信息 void setParentGridLayout(QGridLayout* gridLayout, int row, int column, int rowSpan, int columnSpan) { parentGridLayout = gridLayout; originalRow = row; originalColumn = column; originalRowSpan = rowSpan; originalColumnSpan = columnSpan; originalSize = this->size(); } void updateZoomButtonVisibility() { if (parentGridLayout) { int rowCount = parentGridLayout->rowCount(); int columnCount = parentGridLayout->columnCount(); if (rowCount < 3 && columnCount < 3) { zoombtn->hide(); } else { zoombtn->show(); } } } public slots: //combobox切换槽 void switchViewType(QString name) { QMutexLocker locker(&switchViewerMutex); auto& it = map.find(name); if (it == map.end()) { QString listName; for (auto it = map.begin(); it != map.end(); ++it)listName.append(it.key() + ","); qDebug() << u8"未找到对应的viewer,输入:" << name << u8" 现有:"<< listName; return; } if (it.value().isNull() == false) viewer->setImg(it.value()); else viewer->resetImage(); } //void sendImg(QString type,QPixmap resImgInput) //{ // std::string typer = type.toStdString(); // auto& it = map.find(type); // if (it == map.end()) // { // QString listName; // for (auto it = map.begin(); it != map.end(); ++it)listName.append(it.key() + ","); // qDebug() << u8"未找到对应的viewer,输入:" << type << u8" 现有:" << listName; // return; // } // if (resImgInput.isNull()) { // return; // } // it.value() = resImgInput; // QMutexLocker locker(&switchViewerMutex); // if (type == switchBox.currentText()) // showImgOnGraphicsView(resImgInput); //} ////无视当前类型直接显示 //void showImgOnGraphicsView(QPixmap imageMat) { // viewer->setImg(imageMat); //} void accpectImg(cv::Mat img, QString type){ auto& it = map.find(type); if (it == map.end()) { QString listName; for (auto it = map.begin(); it != map.end(); ++it)listName.append(it.key() + ","); qDebug() << u8"未找到对应的viewer,输入:" << type << u8" 现有:" << listName; return; } // 异步处理(仅发送到后台线程) emit requestProcess(img, type); } void onImageReady(const QString& type, const QImage& qimg) { if (qimg.isNull()) return; QPixmap px = QPixmap::fromImage(qimg); { QMutexLocker locker(&mapMutex); map[type] = px; } if (type == switchBox.currentText()) { viewer->setImg(px); } else if (type == "null") { viewer->setImg(px); } } //缩放 void toggleZoom() { if (!parentGridLayout) return; if (!isZoomed) { zoomIn();// 放大 } else { zoomOut();//缩小 } } // 放大窗口 void zoomIn() { if (isZoomed) return; if (!parentGridLayout) return; // 保存原始状态 originalSize = this->size(); // 在网格布局中重新放置,占据更多空间 parentGridLayout->removeWidget(this); //计算父布局的总行数和总列数 int maxRowSpan = parentGridLayout->rowCount();// 父布局总行数 int maxColSpan = parentGridLayout->columnCount();// 父布局总列数 //放大后的跨度 int zoomColSize = maxColSpan - 1; int zoomRowSize = maxRowSpan - 1; if (originalRow >= zoomRowSize -1 || originalColumn >= zoomColSize -1) { int x = qMax(0, originalRow - (zoomRowSize - 1)); int y = qMax(0, originalColumn - (zoomColSize - 1)); parentGridLayout->addWidget(this, x, y, zoomRowSize, zoomColSize); } else{ parentGridLayout->addWidget(this, originalRow, originalColumn, zoomRowSize, zoomColSize); } this->raise(); // 更新按钮状态 isZoomed = true; zoombtn->setIcon(QIcon(":/icon/icon/fullscreen-exit.png")); zoombtn->setToolTip("缩小窗口"); // 强制更新布局 parentGridLayout->update(); this->update(); } // 缩小窗口 void zoomOut() { if (!isZoomed) return; // 恢复原始布局位置 parentGridLayout->removeWidget(this); parentGridLayout->addWidget(this, originalRow, originalColumn, originalRowSpan, originalColumnSpan); //更新按钮状态 isZoomed = false; zoombtn->setIcon(QIcon(":/icon/icon/fullscreen.png")); zoombtn->setToolTip("放大窗口"); //强制更新布局 parentGridLayout->update(); this->update(); } protected: void wheelEvent(QWheelEvent* event) override { if (event->modifiers() & Qt::ControlModifier) { double scaleFactor = 1.15; if (event->angleDelta().y() < 0) { scaleFactor = 1.0 / scaleFactor; } viewer->scale(scaleFactor, scaleFactor); event->accept(); } else { QWidget::wheelEvent(event); } } private: #if switchToTongYongUi == 0 void paintEvent(QPaintEvent* event) override { QWidget::paintEvent(event); QPainter painter(this); painter.setPen(QPen(Qt::white, 1)); painter.drawRect(rect().adjusted(1, 1, -2, -2)); } #else void paintEvent(QPaintEvent* event) override { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); int radius = 10; // 圆角半径 QRect r = rect(); // 填充圆角背景 painter.setBrush(QColor(50, 55, 60)); painter.setPen(Qt::NoPen); painter.drawRoundedRect(r, radius, radius); } #endif int in = 0; QLabel* label; SonGraphicsView* viewer; QMap map; QMutex mapMutex; QThread* workerThread = nullptr; ImageProcessWorker* worker = nullptr; // 压缩: 如果任一边 > threshold 则按 1/2 缩,循环直到满足 QPixmap matToQPixmapWithCompress(const cv::Mat& srcIn, int threshold) { if (srcIn.empty()) return QPixmap(); cv::Mat src = srcIn; while ((threshold > 0) && (src.cols > threshold || src.rows > threshold)) { cv::Mat half; cv::resize(src, half, cv::Size(src.cols / 2, src.rows / 2), 0, 0, cv::INTER_AREA); src = half; } cv::Mat display; QImage img; switch (src.type()) { case CV_8UC3: cv::cvtColor(src, display, cv::COLOR_BGR2RGB); img = QImage(display.data, display.cols, display.rows, display.step, QImage::Format_RGB888).copy(); break; case CV_8UC1: img = QImage(src.data, src.cols, src.rows, src.step, QImage::Format_Grayscale8).copy(); break; case CV_8UC4: cv::cvtColor(src, display, cv::COLOR_BGRA2RGBA); img = QImage(display.data, display.cols, display.rows, display.step, QImage::Format_RGBA8888).copy(); break; default: src.convertTo(display, CV_8UC3); cv::cvtColor(display, display, cv::COLOR_BGR2RGB); img = QImage(display.data, display.cols, display.rows, display.step, QImage::Format_RGB888).copy(); break; } return QPixmap::fromImage(std::move(img)); } int uiShowImgCompressThreshold = 5000; // 超过该宽或高就压缩 signals: void requestProcess(const cv::Mat& img, const QString& type); }; //总 class ImageWidgetManager :public QWidget { Q_OBJECT public: ImageWidget* createViewer(QString windowName, std::vector viewers, int xPos = -1, int yPos = -1) { ImageWidget* ImageView = new ImageWidget(); ImageWidgetList.push_back(ImageView); map.insert({ windowName,ImageView }); ImageView->setTitle(windowName); if (xPos < 0 || yPos < 0){ Layout->addWidget(ImageView); } else { // 指定位置布局 Layout->addWidget(ImageView, yPos, xPos); ImageView ->setParentGridLayout(Layout, yPos, xPos, 1, 1); } for (auto& viewer : viewers) ImageView->createViewer(QString::fromStdString(viewer)); return ImageView; } void updateAllZoomButtonsVisibility() { for (auto* widget : ImageWidgetList) { widget->updateZoomButtonVisibility(); } } QGridLayout* Layout = new QGridLayout(); ImageWidgetManager() { this->setLayout(Layout); Layout->setContentsMargins(0, 0, 0, 0); // 设置布局属性,允许窗口缩放 /*Layout->setColumnStretch(0, 1); Layout->setColumnStretch(1, 1); Layout->setColumnStretch(2, 1);*/ } ~ImageWidgetManager() { } bool showImg(const cv::Mat& img, const QString& name, const QString& type ="null"){ if (map.find(name) == map.end()) { qCritical() << "图像接收方与现有不符=>" << name; return false; } map[name]->accpectImg(img, type); return true; } void adjustImageWidget() { if (ImageWidgetList.isEmpty()) return; std::vector> vec(Layout->rowCount(), std::vector(Layout->columnCount(),nullptr)); for (auto& data : map) { vec[data.second->originalRow][data.second->originalColumn] = data.second; } for (int row = 0; row < vec.size(); row++) { for (int col = 0; col < vec[row].size(); col++) { if (vec[row][col] == nullptr) { if (col > 0) { if (vec[row][col - 1] != nullptr) { vec[row][col - 1]->originalColumnSpan++; Layout->removeWidget(vec[row][col - 1]); Layout->addWidget(vec[row][col - 1], vec[row][col - 1]->originalRow, vec[row][col - 1]->originalColumn, vec[row][col - 1]->originalRowSpan, vec[row][col - 1]->originalColumnSpan); vec[row][col] = vec[row][col - 1]; } } else if (col < vec[row].size() - 1) { if (vec[row][col + 1] != nullptr) { vec[row][col + 1]->originalRow--; vec[row][col + 1]->originalColumnSpan++; Layout->removeWidget(vec[row][col + 1]); Layout->addWidget(vec[row][col + 1], vec[row][col + 1]->originalRow, vec[row][col + 1]->originalColumn, vec[row][col + 1]->originalRowSpan, vec[row][col + 1]->originalColumnSpan); vec[row][col] = vec[row][col + 1]; } } } } } // 更新布局 Layout->update(); updateAllZoomButtonsVisibility(); } private: QMutex mutex; QList< ImageWidget*>ImageWidgetList; std::unordered_mapmap; };