448 lines
14 KiB
C++
448 lines
14 KiB
C++
#include "DetectImageWidget.h"
|
|
#include <QVBoxLayout>
|
|
#include <QHBoxLayout>
|
|
#include <QPushButton>
|
|
#include <QScrollBar>
|
|
#include <QWheelEvent>
|
|
#include <QMouseEvent>
|
|
#include <QKeyEvent>
|
|
#include <QResizeEvent>
|
|
#include <QShowEvent>
|
|
#include <QEvent>
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QFileDialog>
|
|
#include <QStandardPaths>
|
|
#include <QDir>
|
|
#include <QDateTime>
|
|
#include <cmath>
|
|
|
|
// ============================================================================
|
|
// ImageZoomView 实现
|
|
// ============================================================================
|
|
ImageZoomView::ImageZoomView(QWidget* parent)
|
|
: QWidget(parent)
|
|
{
|
|
setStyleSheet("background-color: rgb(25, 26, 28);");
|
|
|
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(10, 10, 10, 10);
|
|
mainLayout->setSpacing(10);
|
|
|
|
// 顶部标题栏
|
|
QHBoxLayout* topLayout = new QHBoxLayout();
|
|
|
|
m_titleLabel = new QLabel(this);
|
|
m_titleLabel->setStyleSheet("QLabel { color: rgb(221, 225, 233); font-size: 16px; font-weight: bold; background: transparent; }");
|
|
topLayout->addWidget(m_titleLabel);
|
|
|
|
topLayout->addStretch();
|
|
|
|
// 返回按钮
|
|
QPushButton* btnBack = new QPushButton(QString::fromUtf8("返回"), this);
|
|
btnBack->setFixedSize(80, 30);
|
|
btnBack->setStyleSheet(
|
|
"QPushButton { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); "
|
|
"border: 1px solid rgb(80, 82, 88); border-radius: 5px; font-size: 14px; }"
|
|
"QPushButton:hover { background-color: rgb(80, 82, 88); }"
|
|
"QPushButton:pressed { background-color: rgb(50, 52, 58); }"
|
|
);
|
|
connect(btnBack, &QPushButton::clicked, this, &ImageZoomView::exitRequested);
|
|
topLayout->addWidget(btnBack);
|
|
|
|
mainLayout->addLayout(topLayout);
|
|
|
|
// 滚动区域
|
|
m_scrollArea = new QScrollArea(this);
|
|
m_scrollArea->setWidgetResizable(false);
|
|
m_scrollArea->setAlignment(Qt::AlignCenter);
|
|
m_scrollArea->setStyleSheet(
|
|
"QScrollArea { background-color: rgb(38, 40, 47); border: none; }"
|
|
"QScrollBar:vertical { background-color: rgb(38, 40, 47); width: 12px; }"
|
|
"QScrollBar::handle:vertical { background-color: rgb(80, 82, 88); border-radius: 6px; min-height: 20px; }"
|
|
"QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; }"
|
|
"QScrollBar:horizontal { background-color: rgb(38, 40, 47); height: 12px; }"
|
|
"QScrollBar::handle:horizontal { background-color: rgb(80, 82, 88); border-radius: 6px; min-width: 20px; }"
|
|
"QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0px; }"
|
|
);
|
|
|
|
m_imageLabel = new QLabel(m_scrollArea);
|
|
m_imageLabel->setAlignment(Qt::AlignCenter);
|
|
m_imageLabel->setStyleSheet("background-color: rgb(38, 40, 47);");
|
|
m_imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
|
|
m_imageLabel->setScaledContents(false);
|
|
m_scrollArea->setWidget(m_imageLabel);
|
|
|
|
mainLayout->addWidget(m_scrollArea, 1);
|
|
|
|
// 底部信息栏
|
|
m_infoLabel = new QLabel(this);
|
|
m_infoLabel->setStyleSheet("QLabel { color: rgb(180, 180, 180); font-size: 12px; background: transparent; }");
|
|
m_infoLabel->setText(QString::fromUtf8("滚轮缩放 | 拖拽平移 | 双击还原 | 点击返回按钮退出"));
|
|
mainLayout->addWidget(m_infoLabel);
|
|
|
|
setFocusPolicy(Qt::StrongFocus);
|
|
}
|
|
|
|
void ImageZoomView::setImage(const QImage& image, const QString& title)
|
|
{
|
|
m_originalImage = image;
|
|
m_titleLabel->setText(title);
|
|
m_needResetZoom = true;
|
|
|
|
updateImageDisplay();
|
|
}
|
|
|
|
void ImageZoomView::showEvent(QShowEvent* event)
|
|
{
|
|
QWidget::showEvent(event);
|
|
if (m_needResetZoom && !m_originalImage.isNull()) {
|
|
m_needResetZoom = false;
|
|
QMetaObject::invokeMethod(this, &ImageZoomView::resetZoom, Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
void ImageZoomView::updateImageDisplay()
|
|
{
|
|
if (m_originalImage.isNull()) {
|
|
m_imageLabel->setText(QString::fromUtf8("无图像"));
|
|
return;
|
|
}
|
|
|
|
int newWidth = static_cast<int>(m_originalImage.width() * m_scaleFactor);
|
|
int newHeight = static_cast<int>(m_originalImage.height() * m_scaleFactor);
|
|
|
|
QImage scaledImage = m_originalImage.scaled(newWidth, newHeight,
|
|
Qt::KeepAspectRatio,
|
|
Qt::SmoothTransformation);
|
|
m_imageLabel->setPixmap(QPixmap::fromImage(scaledImage));
|
|
m_imageLabel->resize(scaledImage.size());
|
|
|
|
QString info = QString::fromUtf8("缩放: %1% | 尺寸: %2x%3 | 滚轮缩放 | 拖拽平移 | 双击还原")
|
|
.arg(static_cast<int>(m_scaleFactor * 100))
|
|
.arg(m_originalImage.width())
|
|
.arg(m_originalImage.height());
|
|
m_infoLabel->setText(info);
|
|
}
|
|
|
|
void ImageZoomView::resetZoom()
|
|
{
|
|
if (m_originalImage.isNull()) return;
|
|
|
|
QSize viewSize = m_scrollArea->viewport()->size();
|
|
double scaleX = static_cast<double>(viewSize.width() - 20) / m_originalImage.width();
|
|
double scaleY = static_cast<double>(viewSize.height() - 20) / m_originalImage.height();
|
|
m_scaleFactor = qMin(scaleX, scaleY);
|
|
m_scaleFactor = qMax(m_scaleFactor, MIN_SCALE);
|
|
|
|
updateImageDisplay();
|
|
|
|
m_scrollArea->horizontalScrollBar()->setValue(
|
|
(m_imageLabel->width() - m_scrollArea->viewport()->width()) / 2);
|
|
m_scrollArea->verticalScrollBar()->setValue(
|
|
(m_imageLabel->height() - m_scrollArea->viewport()->height()) / 2);
|
|
}
|
|
|
|
void ImageZoomView::wheelEvent(QWheelEvent* event)
|
|
{
|
|
if (m_originalImage.isNull()) return;
|
|
|
|
double delta = event->angleDelta().y() > 0 ? SCALE_STEP : -SCALE_STEP;
|
|
|
|
double newScale = m_scaleFactor + delta;
|
|
newScale = qMax(newScale, MIN_SCALE);
|
|
newScale = qMin(newScale, MAX_SCALE);
|
|
|
|
if (qAbs(newScale - m_scaleFactor) > 0.001) {
|
|
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
|
|
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
|
|
|
|
double hRatio = hBar->maximum() > 0 ? static_cast<double>(hBar->value()) / hBar->maximum() : 0.5;
|
|
double vRatio = vBar->maximum() > 0 ? static_cast<double>(vBar->value()) / vBar->maximum() : 0.5;
|
|
|
|
m_scaleFactor = newScale;
|
|
updateImageDisplay();
|
|
|
|
hBar->setValue(static_cast<int>(hRatio * hBar->maximum()));
|
|
vBar->setValue(static_cast<int>(vRatio * vBar->maximum()));
|
|
}
|
|
|
|
event->accept();
|
|
}
|
|
|
|
void ImageZoomView::mousePressEvent(QMouseEvent* event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
m_dragging = true;
|
|
m_lastPos = event->pos();
|
|
setCursor(Qt::ClosedHandCursor);
|
|
}
|
|
QWidget::mousePressEvent(event);
|
|
}
|
|
|
|
void ImageZoomView::mouseMoveEvent(QMouseEvent* event)
|
|
{
|
|
if (m_dragging) {
|
|
QPoint delta = event->pos() - m_lastPos;
|
|
m_lastPos = event->pos();
|
|
|
|
QScrollBar* hBar = m_scrollArea->horizontalScrollBar();
|
|
QScrollBar* vBar = m_scrollArea->verticalScrollBar();
|
|
|
|
hBar->setValue(hBar->value() - delta.x());
|
|
vBar->setValue(vBar->value() - delta.y());
|
|
}
|
|
QWidget::mouseMoveEvent(event);
|
|
}
|
|
|
|
void ImageZoomView::mouseReleaseEvent(QMouseEvent* event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
m_dragging = false;
|
|
setCursor(Qt::ArrowCursor);
|
|
}
|
|
QWidget::mouseReleaseEvent(event);
|
|
}
|
|
|
|
void ImageZoomView::mouseDoubleClickEvent(QMouseEvent* event)
|
|
{
|
|
if (event->button() == Qt::LeftButton) {
|
|
resetZoom();
|
|
}
|
|
QWidget::mouseDoubleClickEvent(event);
|
|
}
|
|
|
|
void ImageZoomView::keyPressEvent(QKeyEvent* event)
|
|
{
|
|
switch (event->key()) {
|
|
case Qt::Key_Escape:
|
|
emit exitRequested();
|
|
break;
|
|
case Qt::Key_Plus:
|
|
case Qt::Key_Equal:
|
|
m_scaleFactor = qMin(m_scaleFactor + SCALE_STEP, MAX_SCALE);
|
|
updateImageDisplay();
|
|
break;
|
|
case Qt::Key_Minus:
|
|
m_scaleFactor = qMax(m_scaleFactor - SCALE_STEP, MIN_SCALE);
|
|
updateImageDisplay();
|
|
break;
|
|
case Qt::Key_0:
|
|
m_scaleFactor = 1.0;
|
|
updateImageDisplay();
|
|
break;
|
|
case Qt::Key_F:
|
|
resetZoom();
|
|
break;
|
|
default:
|
|
QWidget::keyPressEvent(event);
|
|
}
|
|
}
|
|
|
|
void ImageZoomView::resizeEvent(QResizeEvent* event)
|
|
{
|
|
QWidget::resizeEvent(event);
|
|
if (!m_originalImage.isNull()) {
|
|
resetZoom();
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// DetectImageWidget 实现
|
|
// ============================================================================
|
|
DetectImageWidget::DetectImageWidget(QWidget* parent)
|
|
: QWidget(parent)
|
|
{
|
|
setupUI();
|
|
}
|
|
|
|
DetectImageWidget::~DetectImageWidget()
|
|
{
|
|
}
|
|
|
|
void DetectImageWidget::setupUI()
|
|
{
|
|
QVBoxLayout* mainLayout = new QVBoxLayout(this);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
mainLayout->setSpacing(0);
|
|
|
|
// 使用 QStackedWidget 切换普通视图和放大视图
|
|
m_stackedWidget = new QStackedWidget(this);
|
|
|
|
// 普通视图 - QGraphicsView
|
|
m_graphicsView = new QGraphicsView(this);
|
|
m_scene = new QGraphicsScene(this);
|
|
m_graphicsView->setScene(m_scene);
|
|
m_graphicsView->setRenderHint(QPainter::Antialiasing);
|
|
m_graphicsView->setRenderHint(QPainter::SmoothPixmapTransform);
|
|
m_graphicsView->setDragMode(QGraphicsView::NoDrag);
|
|
m_graphicsView->setStyleSheet("QGraphicsView { background-color: rgb(47, 48, 52); border: none; }");
|
|
m_graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
m_graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
|
m_graphicsView->setCursor(Qt::PointingHandCursor);
|
|
|
|
// 设置点击事件过滤
|
|
m_graphicsView->viewport()->installEventFilter(this);
|
|
|
|
m_stackedWidget->addWidget(m_graphicsView);
|
|
|
|
// 放大视图
|
|
m_zoomView = new ImageZoomView(this);
|
|
connect(m_zoomView, &ImageZoomView::exitRequested, this, &DetectImageWidget::showNormalView);
|
|
m_stackedWidget->addWidget(m_zoomView);
|
|
|
|
mainLayout->addWidget(m_stackedWidget);
|
|
|
|
// 默认显示普通视图
|
|
m_stackedWidget->setCurrentIndex(0);
|
|
|
|
// 设置右键菜单
|
|
m_graphicsView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(m_graphicsView, &QGraphicsView::customContextMenuRequested,
|
|
this, &DetectImageWidget::showContextMenu);
|
|
|
|
// 创建右键菜单
|
|
m_contextMenu = new QMenu(this);
|
|
m_saveDataAction = new QAction(QString::fromUtf8("保存检测数据"), this);
|
|
m_saveDataAction->setEnabled(false);
|
|
|
|
// 设置菜单样式
|
|
m_contextMenu->setStyleSheet(
|
|
"QMenu { background-color: rgb(60, 62, 68); color: rgb(221, 225, 233); border: 1px solid rgb(80, 82, 88); }"
|
|
"QMenu::item { padding: 8px 25px; }"
|
|
"QMenu::item:selected { background-color: rgb(80, 82, 88); }"
|
|
"QMenu::item:disabled { color: rgb(120, 120, 120); }"
|
|
);
|
|
|
|
m_contextMenu->addAction(m_saveDataAction);
|
|
connect(m_saveDataAction, &QAction::triggered, this, &DetectImageWidget::onSaveDataTriggered);
|
|
}
|
|
|
|
bool DetectImageWidget::eventFilter(QObject* obj, QEvent* event)
|
|
{
|
|
if (obj == m_graphicsView->viewport()) {
|
|
if (event->type() == QEvent::MouseButtonPress) {
|
|
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
|
if (mouseEvent->button() == Qt::LeftButton) {
|
|
// 点击放大
|
|
if (!m_image.isNull()) {
|
|
showZoomView();
|
|
emit clicked();
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return QWidget::eventFilter(obj, event);
|
|
}
|
|
|
|
void DetectImageWidget::setImage(const QImage& image)
|
|
{
|
|
m_image = image;
|
|
updateImageDisplay();
|
|
}
|
|
|
|
QImage DetectImageWidget::getImage() const
|
|
{
|
|
return m_image;
|
|
}
|
|
|
|
void DetectImageWidget::clearImage()
|
|
{
|
|
m_image = QImage();
|
|
m_scene->clear();
|
|
}
|
|
|
|
void DetectImageWidget::setTitle(const QString& title)
|
|
{
|
|
m_title = title;
|
|
}
|
|
|
|
void DetectImageWidget::setSaveDataEnabled(bool enabled)
|
|
{
|
|
m_saveDataEnabled = enabled;
|
|
m_saveDataAction->setEnabled(enabled);
|
|
}
|
|
|
|
void DetectImageWidget::setSaveDataCallback(const SaveDataCallback& callback)
|
|
{
|
|
m_saveDataCallback = callback;
|
|
}
|
|
|
|
void DetectImageWidget::setSaveDataMenuText(const QString& text)
|
|
{
|
|
m_saveDataAction->setText(text);
|
|
}
|
|
|
|
void DetectImageWidget::showZoomView()
|
|
{
|
|
if (m_image.isNull()) {
|
|
return;
|
|
}
|
|
|
|
m_zoomView->setImage(m_image, m_title);
|
|
m_stackedWidget->setCurrentIndex(1);
|
|
m_zoomView->setFocus();
|
|
}
|
|
|
|
void DetectImageWidget::showNormalView()
|
|
{
|
|
m_stackedWidget->setCurrentIndex(0);
|
|
}
|
|
|
|
void DetectImageWidget::resizeEvent(QResizeEvent* event)
|
|
{
|
|
QWidget::resizeEvent(event);
|
|
updateImageDisplay();
|
|
}
|
|
|
|
void DetectImageWidget::updateImageDisplay()
|
|
{
|
|
if (m_image.isNull()) {
|
|
return;
|
|
}
|
|
|
|
m_scene->clear();
|
|
|
|
QPixmap pixmap = QPixmap::fromImage(m_image);
|
|
m_scene->addPixmap(pixmap);
|
|
m_scene->setSceneRect(pixmap.rect());
|
|
|
|
// 自适应显示
|
|
m_graphicsView->fitInView(m_scene->sceneRect(), Qt::KeepAspectRatio);
|
|
}
|
|
|
|
void DetectImageWidget::showContextMenu(const QPoint& pos)
|
|
{
|
|
// 检查是否有数据可保存
|
|
if (!m_saveDataEnabled) {
|
|
return;
|
|
}
|
|
|
|
m_contextMenu->exec(m_graphicsView->mapToGlobal(pos));
|
|
}
|
|
|
|
void DetectImageWidget::onSaveDataTriggered()
|
|
{
|
|
// 发射信号,让外部处理保存逻辑
|
|
emit saveDataRequested(this);
|
|
|
|
// 如果设置了回调,也调用回调
|
|
if (m_saveDataCallback) {
|
|
// 获取默认保存路径
|
|
QString defaultPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
|
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
|
|
QString defaultFileName = QString("%1/detect_data_%2.pcd").arg(defaultPath).arg(timestamp);
|
|
|
|
QString filePath = QFileDialog::getSaveFileName(
|
|
this,
|
|
QString::fromUtf8("保存检测数据"),
|
|
defaultFileName,
|
|
QString::fromUtf8("点云文件 (*.pcd);;所有文件 (*.*)")
|
|
);
|
|
|
|
if (!filePath.isEmpty()) {
|
|
m_saveDataCallback(filePath);
|
|
}
|
|
}
|
|
}
|