需要对label进行提升,新建MyLabel类,并将其提升到label控件上,详见上篇控件提升
mylabelmouse.h
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_mylabelmouse.h"
#include <QMenu>
#include "MyLabel.h" // 引入 MyLabel 类
class mylabelmouse : public QMainWindow
{
Q_OBJECT
public:
mylabelmouse(QWidget *parent = nullptr);
~mylabelmouse();
void openImage();
protected:
void contextMenuEvent(QContextMenuEvent* event) override;
private:
Ui::mylabelmouseClass ui;
QMenu* m_pMenu;
double scaleX, scaleY;
};
mylabelmouse.cpp
#include "mylabelmouse.h"
#include <QMenu>
#include <QAction>
#include <QFileDialog>
#include "MyLabel.h"
mylabelmouse::mylabelmouse(QWidget* parent)
: QMainWindow(parent)
{
ui.setupUi(this);
// 创建 MyLabel 实例,并替换 ui.label
ui.label = new MyLabel(this);
ui.label->setGeometry(20, 40, 800, 800); // 初始设置label的大小和位置
// 设置label的黑色边框
ui.label->setStyleSheet("border: 2px solid black;");
// 设置右键菜单
m_pMenu = new QMenu(this);
QAction* pAc1 = new QAction(QString::fromLocal8Bit("结束绘制"), this);
QAction* pAc2 = new QAction(QString::fromLocal8Bit("清除"), this);
QAction* pAc3 = new QAction(QString::fromLocal8Bit("删除上一路径"), this);
QAction* pAc4 = new QAction(QString::fromLocal8Bit("退出菜单"), this);
m_pMenu->addAction(pAc1);
m_pMenu->addAction(pAc2);
m_pMenu->addAction(pAc3);
m_pMenu->addAction(pAc4);
m_pMenu->setStyleSheet("QMenu{font:18px;}");
// 连接动作
connect(pAc1, &QAction::triggered, [=] {
ui.label->endDraw();
});
connect(pAc2, &QAction::triggered, [=] {
ui.label->clearPath();
});
connect(pAc3, &QAction::triggered, [=] {
ui.label->deleteLastPath();
});
connect(ui.openButton, &QPushButton::clicked, this, &mylabelmouse::openImage);
connect(ui.savedropButton, &QPushButton::clicked, ui.label, &MyLabel::savePath);
connect(ui.saveimageButton, &QPushButton::clicked, ui.label, &MyLabel::saveImageWithPaths);
}
mylabelmouse::~mylabelmouse() {}
void mylabelmouse::openImage()
{
// 在切换图像之前,先清除已有的路径
ui.label->clearPath();
QString fileName = QFileDialog::getOpenFileName(this, QStringLiteral("打开图片"), "", QStringLiteral("Images (*.png *.xpm *.jpg *.bmp)"));
if (!fileName.isEmpty()) {
QPixmap pixmap(fileName);
if (!pixmap.isNull()) {
int imgWidth = pixmap.width();
int imgHeight = pixmap.height();
// 设置目标宽度和高度
int targetWidth = 1000;
int targetHeight = 800;
// 根据图片的宽高比来选择缩放方式
if (imgWidth > imgHeight) {
// 图片是横向的,优先缩放宽度
double scaleX = targetWidth / static_cast<double>(imgWidth);
double scaleY = scaleX; // 根据宽度缩放,保持比例
// 设置图片缩放
ui.label->setPixmap(pixmap.scaled(targetWidth, targetHeight, Qt::KeepAspectRatio));
ui.label->resize(targetWidth, targetHeight); // 根据缩放后的尺寸设置标签大小
// 计算缩放系数
ui.label->setScaleFactors(scaleX, scaleY);
}
else {
// 图片是纵向的,优先缩放高度
double scaleY = targetHeight / static_cast<double>(imgHeight);
double scaleX = scaleY; // 根据高度缩放,保持比例
// 设置图片缩放
ui.label->setPixmap(pixmap.scaled(targetWidth, targetHeight, Qt::KeepAspectRatio));
ui.label->resize(targetWidth, targetHeight); // 根据缩放后的尺寸设置标签大小
// 计算缩放系数
ui.label->setScaleFactors(scaleX, scaleY);
}
// 设置label的对齐方式为左上角对齐
ui.label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
ui.label->setScaledContents(false); // 保持图像原始比例
}
}
}
void mylabelmouse::contextMenuEvent(QContextMenuEvent* event)
{
m_pMenu->move(cursor().pos());
m_pMenu->show();
}
MyLabel.h
#ifndef MYLABEL_H
#define MYLABEL_H
#include <QLabel>
#include <QList>
#include <QPointF>
#include <QVector>
#include <QPen>
#include <QBrush>
class MyLabel : public QLabel
{
Q_OBJECT
public:
explicit MyLabel(QWidget* parent = nullptr);
void endDraw(); // 结束绘制
void clearPath(); // 清空所有路径
void deleteLastPath(); // 删除上一路径
void savePath();
void saveImageWithPaths();
void setScaleFactors(double scaleX, double scaleY);// 设置缩放系数
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* e) override;
void mouseMoveEvent(QMouseEvent* e) override;
void mouseReleaseEvent(QMouseEvent* e) override;
void mouseDoubleClickEvent(QMouseEvent* event) override;
private:
double m_scaleX, m_scaleY;//记录缩放系数
bool m_bStartDraw; // 是否开始绘制
bool bMove; // 是否正在移动绘制
QPoint movePoint; // 鼠标移动的临时点
QPoint m_draggedPoint = QPoint(-1, -1); // (-1, -1)表示没有点在被拖动
QList<QList<QPointF>> allContours; // 存储多个轮廓的点集
QList<QPointF> currentContour; // 当前绘制的轮廓点集
};
#endif // MYLABEL_H
MyLabel.cpp
#include "MyLabel.h"
#include <QPainter>
#include <QMouseEvent>
#include <QLineF>
#include <QPainterPath>
#include <QVector2D>
#include <QMessageBox>
#include <QFileDialog>
#include <QTextStream>
#include <QDateTime>
#include <cmath> // 包含 std::round
using namespace Qt;
MyLabel::MyLabel(QWidget* parent) : QLabel(parent), m_bStartDraw(false), bMove(false)
{
setMouseTracking(true);//鼠标自动触发,默认任务需要按下才能开始
}
void MyLabel::endDraw()
{
if (!currentContour.isEmpty()) {
currentContour.push_back(currentContour[0]); // 闭合路径
allContours.push_back(currentContour); // 添加到轮廓集合
}
m_bStartDraw = false;
this->update(); // 更新视图
}
void MyLabel::clearPath()
{
allContours.clear(); // 清空所有轮廓
currentContour.clear(); // 清空当前路径
m_bStartDraw = false; // 重置绘制状态
bMove = false; // 重置鼠标移动状态
this->update(); // 触发重绘
}
void MyLabel::deleteLastPath()
{
allContours.removeLast(); // 删除最后一条轮廓路径
this->update(); // 更新视图
}
void MyLabel::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
const QPixmap* currentPixmap = pixmap();
if (currentPixmap && !currentPixmap->isNull()) {
painter.drawPixmap(0, 0, *currentPixmap);
}
QPen pen(red);
pen.setStyle(SolidLine);
pen.setWidth(2);
painter.setPen(pen);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制所有路径
for (const auto& contour : allContours) {
if (!contour.isEmpty()) {
QPainterPath path;
path.moveTo(contour[0]);
for (const auto& pt : contour)
path.lineTo(pt);
path.closeSubpath();
QBrush brush(QColor(255, 165, 0, 100)); // 半透明填充色
painter.setBrush(brush);
painter.fillPath(path, brush);
QVector<QLineF> lines;
for (int i = 0; i < contour.size() - 1; i++) {
lines.push_back(QLineF(contour[i], contour[i + 1]));
}
painter.drawLines(lines);
}
}
// 绘制当前正在绘制的路径
if (m_bStartDraw && !currentContour.isEmpty()) {
QPainterPath currentPath;
currentPath.moveTo(currentContour[0]);
for (const auto& pt : currentContour)
currentPath.lineTo(pt);
QBrush brush(QColor(255, 165, 0, 100)); // 半透明填充色
painter.setBrush(brush);
painter.fillPath(currentPath, brush);
QVector<QLineF> currentLines;
for (int i = 0; i < currentContour.size() - 1; i++) {
currentLines.push_back(QLineF(currentContour[i], currentContour[i + 1]));
}
painter.drawLines(currentLines);
// 绘制连接最后一点与鼠标位置的线
if (bMove) {
painter.drawLine(currentContour.last(), movePoint);
}
}
// 绘制首点为小红点
if (!currentContour.isEmpty()) {
painter.setBrush(red);
painter.setPen(NoPen);
painter.drawEllipse(currentContour[0], 5, 5); // 绘制一个半径为5的圆
}
}
void MyLabel::mousePressEvent(QMouseEvent* e)
{
const QPixmap* currentPixmap = pixmap();
if (currentPixmap && !currentPixmap->isNull()) {
QRect imageRect(0, 0, currentPixmap->width(), currentPixmap->height());
if (e->button() == LeftButton) {
if (!m_bStartDraw) {
// 只在图片区域内开始绘制
if (imageRect.contains(e->pos())) {
currentContour.clear(); // 清空当前轮廓点
// 将鼠标位置转换为图像真实像素坐标
QPoint realPoint(e->pos().x(), e->pos().y());
currentContour.push_back(realPoint); // 将转换后的坐标作为起点
m_bStartDraw = true;
}
}
}
}
}
void MyLabel::mouseMoveEvent(QMouseEvent* e)
{
// 处理鼠标左键按下时的绘制
if (e->buttons() & Qt::LeftButton) // 左键按下且移动时触发
{
if (m_bStartDraw) {
const QPixmap* currentPixmap = pixmap();
if (currentPixmap && !currentPixmap->isNull()) {
QRect imageRect(0, 0, currentPixmap->width(), currentPixmap->height());
if (imageRect.contains(e->pos())) {
movePoint = e->pos();
this->update(); // 更新视图
bMove = true; // 标记为正在移动
}
}
}
}
else // 左键松开或没有按下时的情况
{
bMove = false; // 重置鼠标移动状态
// 检查鼠标是否靠近已有的点,并改变鼠标形状
bool isNearPoint = false;
for (const auto& contour : allContours) {
for (const auto& point : contour) {
// 手动计算曼哈顿距离
qreal dx = qAbs(e->pos().x() - point.x());
qreal dy = qAbs(e->pos().y() - point.y());
// 判断距离是否小于等于 4
if (dx + dy <= 4) {
isNearPoint = true; // 标记为靠近某个点
break;
}
}
if (isNearPoint) break; // 如果已经找到靠近的点,跳出外层循环
}
if (isNearPoint) {
// 改变鼠标为手型
setCursor(PointingHandCursor);
}
else {
// 恢复为默认箭头
setCursor(ArrowCursor);
}
}
}
void MyLabel::mouseReleaseEvent(QMouseEvent* e)
{
const QPixmap* currentPixmap = pixmap();
if (currentPixmap && !currentPixmap->isNull()) {
QRect imageRect(0, 0, currentPixmap->width(), currentPixmap->height());
if (e->button() == LeftButton) {
if (m_bStartDraw) {
// 如果鼠标释放后添加最后的点,确保释放点在图片区域内
if (imageRect.contains(e->pos())) {
// 将释放点的坐标转换为图像真实像素坐标
QPoint realPoint(e->pos().x(), e->pos().y());
currentContour.push_back(realPoint);
bMove = false;
this->update();
}
}
}
}
}
void MyLabel::mouseDoubleClickEvent(QMouseEvent* event)
{
endDraw(); // 双击时结束绘制
}
void MyLabel::savePath()
{
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
QString fileName = QFileDialog::getSaveFileName(this, "Save Path", timestamp + ".txt", "Text Files (.txt);;All Files ()");
if (fileName.isEmpty()) {
return;
}
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Save Error", "Failed to open the file for saving.");
return;
}
QTextStream out(&file);
for (const auto& contour : allContours) {
for (const auto& point : contour) {
// 计算真实像素坐标,并四舍五入取整
int x = static_cast<int>(std::round(point.x() / m_scaleX));//如果需要保存图片真实像素位置就加 / m_scaleX获取真实坐标,如果保存改变后图像大小和像素就不需要加
int y = static_cast<int>(std::round(point.y() / m_scaleY));
// 保存四舍五入后的整数坐标
out << x << " " << y << "\n";
}
out << "\n"; // 每个轮廓之间用空行分隔
}
file.close();
QMessageBox::information(this, "Save Success", "Path saved successfully.");
}
void MyLabel::saveImageWithPaths()
{
const QPixmap* currentPixmap = pixmap();
if (currentPixmap && !currentPixmap->isNull()) {
// 复制当前图片到新的 QPixmap 上
QPixmap pixmapWithPaths = *currentPixmap;
QPainter painter(&pixmapWithPaths); // 使用 QPainter 在新图像上绘制
painter.setRenderHint(QPainter::Antialiasing);
painter.setPen(QPen(red, 2)); // 设置红色笔刷,2px宽
painter.setBrush(transparent); // 不填充路径
// 绘制所有的路径
for (const auto& contour : allContours) {
if (!contour.isEmpty()) {
QPainterPath path;
path.moveTo(contour[0]);
for (const auto& pt : contour)
path.lineTo(pt);
path.closeSubpath();
painter.drawPath(path); // 绘制路径
}
}
// 弹出保存对话框,选择保存路径和文件名
QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss");
QString fileName = QFileDialog::getSaveFileName(this, "Save Image with Paths", timestamp + ".png", "Images (.png *.jpg *.bmp)");
if (!fileName.isEmpty()) {
// 保存图像为文件
pixmapWithPaths.save(fileName);
QMessageBox::information(this, "Save Success", "Image saved successfully.");
}
}
}
void MyLabel::setScaleFactors(double scaleX, double scaleY) {
m_scaleX = scaleX;
m_scaleY = scaleY;
}
详见代码内部解析
运行状况