鼠标绘制轮廓

需要对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.rhkb.cn/news/475141.html

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

相关文章

LLM( Large Language Models)典型应用介绍 1 -ChatGPT Large language models

ChatGPT 是基于大型语言模型&#xff08;LLM&#xff09;的人工智能应用。 GPT 全称是Generative Pre-trained Transformer。-- 生成式预训练变换模型&#xff1a; Generative&#xff08;生成式&#xff09;&#xff1a;可以根据输入生成新的文本内容&#xff0c;例如回答问题…

STM322完全学习——FSMC控制LCD显示屏

一、GPIO初始化 首先这个功能只有大容量的STM32系列有&#xff0c;C8T6是没有的。再就是FSMC这个使用的是GPIO的复用功能&#xff0c;下面先完成我们需要使用的GPIO的初始化 void TFTLCD_GPIO_Init(void) {GPIO_InitTypeDef GPIO_InitStructure;RCC_AHB1PeriphClockCmd(RCC_…

MongoDB数据备份与恢复(内含工具下载、数据处理以及常见问题解决方法)

一、工具准备 对MongoDB进行导入导出、备份恢复等操作时需要用到命令工具&#xff0c;我们要先检查一下MongoDB安装目录下是否有这些工具&#xff0c;正常情况下是没有的:)&#xff0c;因为新版本的MongoDB安装时不包含这些工具&#xff0c;需要我们手动下载安装。下载成功之后…

【C语言】volatile 防止编译的时候被优化

volatile 易变的 volatile是 C 和 C 中的一个类型修饰符&#xff0c;用于指示编译器该变量可能在程序之外被更改&#xff0c;因此不应对其进行优化。这在涉及硬件寄存器、信号处理或多线程编程时非常有用。 如果你做过单片机开发&#xff0c;你肯定写过这样的代码&#xff1a;…

el-table实现最后一行合计功能并合并指定单元格

效果图如下&#xff1a; 表格代码如下&#xff1a; <el-table width"100%"ref"tableRef" style"margin-bottom: 15px;":data"jlData"class"tableHeader6"header-row-class-name"headerStyleTr6":row-class-n…

【JavaSE】【网络编程】UDP数据报套接字编程

目录 一、网络编程简介二、Socket套接字三、TCP/UDP简介3.1 有连接 vs 无连接3.2 可靠传输 vs 不可靠传输3.3 面向字节流 vs 面向数据报3.4 双向工 vs 单行工 四、UDP数据报套接字编程4.1 API介绍4.1.1 DatagramSocket类4.1.1.1 构造方法4.1.1.2 主要方法 4.1.2 DatagramPocket…

web——sqliabs靶场——第十二关——(基于错误的双引号 POST 型字符型变形的注入)

判断注入类型 a OR 1 1# 发现没有报错 &#xff0c;说明单引号不是闭合类型 测试别的注入条件 a) OR 1 1# a)) OR 1 1# a" OR 11 发现可以用双引号闭合 发现是")闭合 之后的流程还是与11关一样 爆破显示位 先抓包 是post传参&#xff0c;用hackbar来传参 unam…

【Linux】开发工具make/Makefile、进度条小程序

Linux 1.make/Makefile1.什么是make和Makefile&#xff1f;2.stat命令3.Makefile单个文件的写法4.Makefile多个文件的写法 2.进度条1.回车\r、换行\n2.缓冲区3.进度条1.倒计时程序2.进度条程序 1.make/Makefile 1.什么是make和Makefile&#xff1f; 一个工程中的源文件不计其…

Ubuntu22.04配置强化学习环境及运行相关Demo

什么是强化学习 强化学习&#xff08;Reinforcement Learning&#xff0c;简称 RL&#xff09;是机器学习中的一个重要分支&#xff0c;属于一种基于试错机制的学习方法。它通过让智能体&#xff08;Agent&#xff09;与环境&#xff08;Environment&#xff09;进行交互&…

GitHub 开源项目 Puter :云端互联操作系统

每天面对着各种云盘和在线应用&#xff0c;我们常常会遇到这样的困扰。 文件分散在不同平台很难统一管理&#xff0c;付费订阅的软件越来越多&#xff0c;更不用说那些烦人的存储空间限制了。 最近在 GitHub 上发现的一个开源项目 Puter 彻底改变了我的在线办公方式。 让人惊…

深入解析小程序组件:view 和 scroll-view 的基本用法

深入解析小程序组件:view 和 scroll-view 的基本用法 引言 在微信小程序的开发中,组件是构建用户界面的基本单元。两个常用的组件是 view 和 scroll-view。这两个组件不仅功能强大,而且使用灵活,是开发者实现复杂布局和交互的基础。本文将深入探讨这两个组件的基本用法,…

河道水位流量一体化自动监测系统:航运安全的护航使者

在广袤的水域世界中&#xff0c;航运安全始终是至关重要的课题。而河道水位流量一体化自动监测系统的出现&#xff0c;如同一位强大的护航使者&#xff0c;为航运事业的稳定发展提供了坚实的保障。 水位传感器&#xff1a;负责实时监测河道的水位变化。这些传感器通常采用先进的…

开源可视化工具对比:JimuReport VS DataEase

在当今数据驱动的时代&#xff0c;高效的数据可视化工具成为企业洞察业务、做出决策的关键利器。那对于企业来讲如何选择BI产品呢&#xff1f; 在开源可视化工具的领域中&#xff0c;JimuReport和DataEase 以其独特的优势脱颖而出&#xff0c;究竟谁更胜一筹呢&#xff1f;让我…

jquery还有其应用场景,智慧慢慢地被边缘化,但不会消亡

一、jQuery 的辉煌过往 jQuery 的诞生与崛起 在前端开发的漫长历史中&#xff0c;2006 年诞生的 jQuery 犹如一颗耀眼的新星划破天际。它由 John Resig 创造&#xff0c;一出现便以其独特的魅力迅速吸引了广大开发者的目光。在那个前端技术发展相对缓慢的时期&#xff0c;jQue…

TSmaster 专栏索引

文章目录 软件下载官方中文手册和视频教程窗口对齐关闭窗体报文格式转换TSmaster 硬件配置及连接TSmaster Measurement setup&#xff08;测量设置&#xff09; 软件下载 下载路径&#xff1a;https://www.tosunai.com/downloads/ 官方中文手册和视频教程 窗口对齐 一个工作…

Java小白成长记(创作笔记一)

目录 序言 思维导图 开发流程 新建SpringBoot并整合MybatisPlus 新建SpringBoot 整合MybatisPlus 统一结果封装 全局异常处理 引入数据库 序言 在一个充满阳光的早晨&#xff0c;一位对编程世界充满好奇的年轻人小小白&#xff0c;怀揣着梦想与激情&#xff0c;踏上了学习…

SpringBoot+Vue 2 多方法实现(图片/视频/报表)文件上传下载,示例超详细 !

目录 一、主流方法介绍 1. Base 64 2. 二进制流传输 3. multipart/form-data 4. FTP/SFTP 5. 云存储服务API 二、multipart/form-data 方式上传单个文件 1、前端部分 2、后端部分 三、multipart/form-data 方式上传多个文件 1、前端部分 2、后端部分 四、Base 64 方…

小米顾此失彼:汽车毛利大增,手机却跌至低谷

科技新知 原创作者丨依蔓 编辑丨蕨影 三年磨一剑的小米汽车毛利率大增&#xff0c;手机业务毛利率却出现下滑景象。 11月18日&#xff0c;小米集团发布 2024年第三季度财报&#xff0c;公司实现营收925.1亿元&#xff0c;同比增长30.5%&#xff0c;预估902.8亿元&#xff1b;…

unity中:超低入门级显卡、集显(功耗30W以下)运行unity URP管线输出的webgl程序有那些地方可以大幅优化帧率

删除Global Volume&#xff1a; 删除Global Volume是一项简单且高效的优化措施。实测表明&#xff0c;这一改动可以显著提升帧率&#xff0c;甚至能够将原本无法流畅运行的场景变得可用。 更改前的效果&#xff1a; 更改后的效果&#xff1a; 优化阴影和材质&#xff1a; …

webgl threejs 云渲染(服务器渲染、后端渲染)解决方案

云渲染和流式传输共享三维模型场景 1、本地无需高端GPU设备即可提供三维项目渲染 云渲染和云流化媒体都可以让3D模型共享变得简单便捷。配备强大GPU的远程服务器早就可以处理密集的处理工作&#xff0c;而专有应用程序&#xff0c;用户也可以从任何个人设备查看全保真模型并与…