鼠标绘制轮廓

需要对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.labelui.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());// 判断距离是否小于等于 4if (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;
}

 详见代码内部解析

运行状况

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/60152.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言-详细讲解-冒泡排序与选择排序

1.冒泡排序 冒泡排序是一种比较简单的排序算法。它重复地走访要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换&#xff0c;也就是说该数列已经排序完成。这个名字的由来是因为越小&a…

MATLAB常见数学运算函数

MATLAB中含有许多有用的函数,可以随时调用。 a b s abs abs函数 a b s abs abs函数在MATLAB中可以求绝对值,也可以求复数的模长:c e i l ceil ceil函数 向正无穷四舍五入(如果有小数,就向正方向进一)f l o o r floor floor函数 向负无穷四舍五入(如果有小数,就向负方向…

SpringBoot 集成 Sharding-JDBC(一):数据分片

在深入探讨 Sharding-JDBC 之前&#xff0c;建议读者先了解数据库分库分表的基本概念和应用场景。如果您还没有阅读过相关的内容&#xff0c;可以先阅读我们之前的文章&#xff1a; 关系型数据库海量数据存储策略-CSDN博客 这篇文章将帮助您更好地理解分库分表的基本原理和实现…

go-zero(六) JWT鉴权

go-zero JWT鉴权 还记得我们之前登录功能&#xff0c;返回的信息是token吗&#xff1f; 这个token其实就是JSON Web Token简称JWT,它是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在网络应用环境间安全地传递声明信息。 它是一种基于 JSON 的令牌&#xf…

ZYNQ程序固化——ZYNQ学习笔记7

一、ZYNQ启动过程 二、 SD卡启动实操 1、对ZYNQ进行配置添加Flash 2、添加SD卡 3、重新生成硬件信息 4、创建vitis工程文件 5、勾选板级支持包 6、对系统工程进行整体编译&#xff0c;生成两个Debug文件&#xff0c;如图所示。 7、插入SD卡&#xff0c;格式化为 8、考入BOOT.…

进程其他知识点

/* #include <stdlib.h> void exit(int status); #include <unistd.h> void _exit(int status); status 参数&#xff1a;是进程退出时的一个状态信息。父进程回收子进程资源的时候可以获取到。 */ #include <stdio.h> #include <stdlib.h> #include &…

Android ART知多少?

Android 虚拟机 ART&#xff08;Android Runtime&#xff09;是 Android 平台上的应用程序运行时环境&#xff0c;用于执行应用程序的字节码。ART 自 Android 5.0&#xff08;Lollipop&#xff09;开始取代了 Dalvik&#xff0c;成为 Android 的默认运行时环境。本文将从以下几…

C++ —— 剑斩旧我 破茧成蝶—C++11

江河入海&#xff0c;知识涌动&#xff0c;这是我参与江海计划的第2篇。 目录 1. C11的发展历史 2. 列表初始化 2.1 C98传统的{} 2.2 C11中的{} 2.3 C11中的std::initializer_list 3. 右值引用和移动语义 3.1 左值和右值 3.2 左值引用和右值引用 3.3 引用延长生命周期…

推荐15个2024最新精选wordpress模板

以下是推荐的15个2024年最新精选WordPress模板&#xff0c;轻量级且SEO优化良好&#xff0c;适合需要高性能网站的用户。中文wordpress模板适合搭建企业官网使用。英文wordpress模板&#xff0c;适合B2C网站搭建&#xff0c;功能强大且兼容性好&#xff0c;是许多专业外贸网站的…

(计算机毕设)基于SpringBoot+Vue的房屋租赁系统的设计与实现

博主可接毕设设计&#xff01;&#xff01;&#xff01; 各种毕业设计源码只要是你有的题目我这里都有源码 摘 要 社会的发展和科学技术的进步&#xff0c;互联网技术越来越受欢迎。网络计算机的生活方式逐渐受到广大人民群众的喜爱&#xff0c;也逐渐进入了每个用户的使用。互…

python蓝桥杯刷题2

1.最短路 题解&#xff1a;这个采用暴力枚举&#xff0c;自己数一下就好了 2.门牌制作 题解&#xff1a;门牌号从1到2020&#xff0c;使用for循环遍历一遍&#xff0c;因为range函数无法调用最后一个数字&#xff0c;所以设置成1到2021即可&#xff0c;然后每一次for循环&…

深度学习中的Pixel Shuffle和Pixel Unshuffle:图像超分辨率的秘密武器

在深度学习的计算机视觉任务中&#xff0c;提升图像分辨率和压缩特征图是重要需求。Pixel Shuffle和Pixel Unshuffle是在超分辨率、图像生成等任务中常用的操作&#xff0c;能够通过转换空间维度和通道维度来优化图像特征表示。本篇文章将深入介绍这两种操作的原理&#xff0c;…

Unity类银河战士恶魔城学习总结(P132 Merge skill tree with skill Manager 把技能树和冲刺技能相组合)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址&#xff1a;https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了解锁技能后才可以使用技能&#xff0c;先完成了冲刺技能的锁定解锁 Dash_Skill.cs using System.Collections; using System…

正则表达式完全指南,总结全面通俗易懂

目录 元字符 连接符 限定符 定位符 修饰符&#xff08;标记&#xff09; 运算符优先级 普通字符集及其替换 零宽断言 正向先行断言 负向先行断言 正向后发断言 负向后发断言 捕获组 普通捕获组 命名捕获组 PS:非捕获组 正则表达式在线测试: 正则在线测试工具 …

qt之QFTP对文件夹(含嵌套文件夹和文件)、文件删除下载功能

一、前言 主要功能如下&#xff1a; 1.实现文件夹的下载和删除&#xff0c;网上很多资料都是单独对某个路径的文件操作的&#xff0c;并不能对文件夹操作 2.实现目标机中含中文名称自动转码&#xff0c;有些系统编码方式不同&#xff0c;下载出来的文件会乱码 3.实现ftp功能…

HCIP --OSI七层参考模型回顾、TCP/UDP协议复习

目录 一、OSI 二、重要的三个协议报头格式 名词注解 MTU 封装 解封装 PDU ARP DNS TCP/IP与OSI的区别 三、数据包转发过程 四、获取目标ip地址方式 五、获取目标mac地址方式 六、交换机的工作原理 七、TCP/UDP TCP&#xff08;Transmission Control Protocol&a…

物联网智能技术的深入探讨与案例分析

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

Keil基于ARM Compiler 5的工程迁移为ARM Compiler 6的工程

环境&#xff1a; keil版本为5.38&#xff0c;版本务必高于5.30 STM32F4的pack包版本要高于2.9 软件包下载地址&#xff1a;https://zhuanlan.zhihu.com/p/262507061 一、更改Keil中编译器 更改后编译&#xff0c;会报很多错&#xff0c;先不管。 二、更改头文件依赖 观察…

9.《滑动窗口篇》---①长度最小的子数组(中等)

滑动窗口推导过程 我们不能说一上来就知道这个题目用滑动窗口&#xff0c;然后就使用滑动窗口的方法来做这个题目。 首先我们想到的应该是暴力解法。 接着再优化为滑动窗口 由于数字都是 ≥ 0 的数。因此累加的数越多。和越大。 因此right往后遍历的时候。当发现sum > targe…

Marin说PCB之电源完整性之电源网络的PDN仿真CST---04

小编我最近都要忙疯了&#xff0c;好不容易去韩国出个差&#xff0c;打算不忙的时候去首尔看看韩国的美女们&#xff0c;说错了&#xff0c;是看美景啊。谁料想韩国分公司的SI同事的李相赫同志由于结婚请假了一个多月啊&#xff0c;他倒是挺爽啊&#xff0c;和老婆去度蜜月了&a…