qt项目-《图像标注软件》源码阅读笔记-Label 2d绘制图片及标注类

目录

1. Command 概览   

1.1 功能

1.2 字段

1.3 方法

2. 源码细节

2.1 paintEvent

2.2 mousePressEvent

2.3 mouseMoveEvent

2.4 mouseReleaseEvent


 

1. Command 概览   

1.1 功能

2d绘制图片及标注类,继承QLabel

内部具体的形状的绘制均交由Shape类进行处理,
Shape类为形状基类,
具体的绘制则会调用对应形状类的虚函数。

1.2 字段

  1. color:默认标注形状颜色
  2. current:默认-1,current表示当前操作的标注形状的索引
  3. pixmap:中心图片
  4. size:默认标注形状大小
  5. zoomLevel:默认放大倍数
  6. MagniFier:是否开启放大镜
  7. manager:存放2d中心组件
  8. XOffsetSum:每次移动标注形状的总偏移量
  9. YOffsetSum:每次移动标注形状的总偏移量
  10. shapes:标注形状列表
  11. status:标注状态,默认为noshape

1.3 方法

  1. paintEvent:重写绘制事件的处理
  2. mousePressEvent:重写鼠标按压事件的处理
  3. mouseMoveEvent:重写鼠标移动事件的处理
  4. mouseReleaseEvent:重写释放鼠标事件的处理

2. 源码细节

构造函数,初始化字段:

color默认标注形状颜色, pixmap中心图片, magnifierArea右下角放大区域图片;

    /// \brief 构造函数
    Label(QWidget* parent):QLabel(parent){
        color.setRgb(100,255,0,100);
        setMouseTracking(true);
        pixmap=new QPixmap();
        setAlignment(Qt::AlignCenter);
        magnifierArea.load(":/icons/icons/temp.jpg"); // private: 右下角放大区域图片
    }

2.1 paintEvent

在处理放大图像问题。如何缩放图像。

/// \brief 重写绘制事件的处理
void My::Label::paintEvent(QPaintEvent *event){

    //空图片则返回
    if(pixmap->isNull()){return;}

    //根据放大倍数进行放大
    QPixmap tempPixMap(*pixmap);  // pixmap中心图片. 缩放图像
    tempPixMap=tempPixMap.scaled(tempPixMap.width()*zoomLevel,tempPixMap.height()*zoomLevel,Qt::KeepAspectRatio);

    //label进行响应的大小调整        // 根据缩放后的图片大小调整当前 Label 的大小,确保 Label 能容纳整个图片
    this->resize(tempPixMap.width(),tempPixMap.height());

    //绘制图片
    QPainter painter(this);       // 把缩放后的图像绘制到新label中
    painter.drawPixmap(0,0,tempPixMap.width(),tempPixMap.height(),tempPixMap);

    //绘制形状
    foreach(My::Shape2D* shape,shapes){  // 把所有shape绘制到label中。
        shape->draw(this);
    }

    //根据是否开启放大镜,进行绘制
    if(MagniFier){
        QPainter painter(this);
        painter.drawPixmap(this->width()-100,this->height()-100,100,100,magnifierArea);
    }
}

2.2 mousePressEvent

标注时,选择不同的shape,生成不同的shape对象并更新其成员。 

/// \brief 重写鼠标移动事件的处理
void My::Label::mousePressEvent(QMouseEvent *event){

    //更新当前鼠标所在位置
    float x=event->x();  // 在label中的位置,也就是真实像素坐标
    float y=event->y();
    cursorX=x;
    cursorY=y;

    //更新鼠标点位,该点位存储横纵坐标比例.   相对于label的比例位置。
    QPointF p(qreal(x/this->width()),qreal(y/this->height()));  // qreal表示浮点数

    //右键则直接返回,右键会弹出菜单,所以此处不进行处理
    if(event->buttons()&Qt::RightButton)return;

    //若为noshape状态
    if(status==My::NoShape){  // 鼠标还没选择标注shape
        if(current==-1)return;  // 没选择shape,也没选中已标注.current表示当前操作的标注形状的索引
        else{  // 没选择shape,但选中了已标注信息。

            //当前current不为-1,则发送信号
            emit(manager->selectedChanged(current,false));  // 告诉centralW已选中那个标注。
            current=-1;
        }
        return;
    }

    //若为inshape状态,即鼠标在标注形状内部
    if(status==My::InShape){

        //判断是否仍在标注形状内部,若在则更新current
        int index=shapes.length();  // 鼠标点位p所在的shape index
        for(int i=0;i<shapes.length();i++){
            if(shapes[i]->isInShape(p,this)){  // 鼠标点位p是在哪个shape内部
                index=i;
                break;
            }
        }
        if(index<shapes.length()){  // 说明鼠标确实在shapes[index]内部
            current=index;  // 更新当前操作的标注形状索引
        }

        //设置鼠标样式
        setCursor(Qt::ClosedHandCursor);  // 封闭手的形状,通常表示用户可以按住鼠标左键并拖动
        emit(manager->selectedChanged(current,true));  // 告诉centralW已选中那个标注。
    }

    
    //若为创建矩形状态
    if(status==My::RectangleShape){

        if(current==-1){  // 没有操作现有的标注
            My::Rectangle* rectangle=new My::Rectangle();  //新建一个矩形
            rectangle->points.append(p);  // 并设置其成员:points,color
            rectangle->color=color;
            shapes.append(rectangle);
            current=shapes.length()-1;
        }

        //若已添加形状,则根据鼠标所在位置更新矩形位置,且询问是否添加
        else{
            My::Rectangle* rectangle=dynamic_cast<My::Rectangle*>(shapes[current]);
            rectangle->width=(x-this->width()*rectangle->points[0].x())/this->width();
            rectangle->height=(y-this->height()*rectangle->points[0].y())/this->height();
            update();

            //询问是否添加
            bool isOk;
            QString text=QInputDialog::getText(this,"label me!","Please input the label",QLineEdit::Normal,"",&isOk);
            if(isOk){
                rectangle->label=text;

                //命令栈记录
                manager->command->logAdd(current);

                //发送信号
                emit(manager->labelAdded(rectangle,current));
                current=-1;
            }
        }
        return;
    }

    ...其他shape

}

2.3 mouseMoveEvent

放大镜功能。是否在shape内部。

/// \brief 重写鼠标移动事件的处理
void My::Label::mouseMoveEvent(QMouseEvent *event){

    //状态栏显示坐标
    MainWindow* w=qobject_cast<MainWindow*>(manager->parent());

    //获取更新鼠标点位
    float x=event->x();  // 真实像素坐标
    float y=event->y();
    int index=shapes.length();
    QPointF p(qreal(x/this->width()),qreal(y/this->height()));  // 相对label点位
    
    // 鼠标移动时,在状态栏显示真实像素坐标
    /*MainWindow* */w=qobject_cast<MainWindow*>(manager->parent());
    w->statusBar()->showMessage(QString("Pos: X %1 Y %2").arg(x).arg(y),1000);

    //是否有放大镜,若有则会实时获取鼠标当前的图片区域
    if(MagniFier){
        QPixmap tempPixMap;
        tempPixMap=tempPixMap.grabWidget(this,int(x),int(y),20,20);  // 获取以 (x, y) 为左上角的 20x20 区域的截图。
        magnifierArea=tempPixMap.scaled(100,100,Qt::KeepAspectRatio);  // 截图缩放为 100x100 的大小, 保持宽高比
        qDebug()<<"success"<<endl;
        update();
    }

    //noshape状态
    if(status==My::NoShape){

        //更新鼠标位置
        cursorX=x;
        cursorY=y;

        //判断是否鼠标在标注形状内,并更新
        for(int i=0;i<shapes.length();i++){
            if(shapes[i]->isInShape(p,this)){
                index=i;
                break;
            }
        }
        if(index<shapes.length()){  // 在标注形状内部,则
            setCursor(Qt::OpenHandCursor);  // 将光标设置为开放手光标,表示可以拖动。
            status=My::InShape;
            current=index;
            shapes[current]->isHover=true;
            update();
            return;
        }
        setCursor(Qt::ArrowCursor);
        return;
    }

    //inshape状态
    if(status==My::InShape){

        //若为左键,则为移动标注形状的位置
        if(event->buttons()&Qt::LeftButton){
            float xOffset=(x-cursorX)/this->width();
            float yOffset=(y-cursorY)/this->height();

            //更新总偏移量
            XOffsetSum+=xOffset;
            YOffsetSum+=yOffset;

            cursorX=x;
            cursorY=y;

            //标注形状进行偏移
            shapes[current]->offset(xOffset,yOffset);
            update();
            return;
        }

        //判断是否鼠标是否在标注内并更新
        for(int i=0;i<shapes.length();i++){
            if(shapes[i]->isInShape(p,this)){
                index=i;
                break;
            }
        }

        if(index<shapes.length()){
            current=index;
            shapes[current]->isHover=true;
            update();
        }

        if(index>=shapes.length()){
            status=My::NoShape;
            setCursor(Qt::ArrowCursor);
            if(current!=-1)shapes[current]->isHover=false;
            update();
            return;
        }
        cursorX=x;
        cursorY=y;
        return;

    }


    //创建矩形状态
    if(status==My::RectangleShape){

        cursorX=x;
        cursorY=y;

        //同步更新矩形的位置
        if(current!=-1){
            My::Rectangle* rectangle=dynamic_cast<My::Rectangle*>(shapes[current]);
            rectangle->color=color;
            rectangle->width=(x-rectangle->points[0].x()*this->width())/this->width();
            rectangle->height=(y-rectangle->points[0].y()*this->height())/this->height();
            update();
        }
        return;
    }
}

2.4 mouseReleaseEvent

shape内部拖动。

/// \brief 重写释放鼠标事件的处理
void My::Label::mouseReleaseEvent(QMouseEvent *event){
    if(status==My::NoShape){
        return;
    }
    if(status==My::InShape){  // 在shape内部
        setCursor(Qt::OpenHandCursor);  // 为开放手光标,表示可以拖动。

        //命令栈记录总偏移量
        if(event->button()==Qt::LeftButton){  // 如果是左键,则是拖动功能。
            manager->command->logMove(current,XOffsetSum,YOffsetSum);
            XOffsetSum=0;
            YOffsetSum=0;
        }
        return;
    }
    if(status==My::BrushShape){  // 目前在shape内部只有拖动功能,其他各自的功能可以在此添加。
        return;
    }
    if(status==My::RectangleShape){
        return;
    }
    if(status==My::PolygonsShape){
        return;
    }
    if(status==My::CircleShape){
        return;
    }
    if(status==My::CurveShape){
        return;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

GPU性能实时监测的实用工具

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Selenium自动化测试框架(附教程+源码)

说起自动化测试&#xff0c;我想大家都会有个疑问&#xff0c;要不要做自动化测试&#xff1f; 自动化测试给我们带来的收益是否会超出在建设时所投入的成本&#xff0c;这个嘛别说是我&#xff0c;即便是高手也很难回答&#xff0c;自动化测试的初衷是美好的&#xff0c;而测试…

游戏行业变天,游戏股遭暴击,腾讯网易等股票还能投资吗?

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 国家新闻出版署发布游戏新规 12月22日国家新闻出版署发布了《网络游戏管理办法》&#xff08;草案征求意见稿&#xff09;&#xff0c;其中提到网络游戏不得设置每日登陆、首次充值、连续充值等诱导性奖励&#xff0c;而且…

Ubuntu 22.04.3 Server 设置静态IP 通过修改yaml配置文件方法

目录 1.查看网卡信息 2.修改yaml配置文件 3.应用新的网络配置 4.重新启动网络服务 文章内容 本文介绍Ubuntu 22.04.3 Server系统通过修改yaml配置文件配置静态 ip 的方法。 1.查看网卡信息 使用ifconfig命令查看网卡信息获取网卡名称​ 如果出现Command ifconfig not fo…

A股风格因子看板 (2023.12第14期)

该因子看板跟踪A股风格因子&#xff0c;该因子主要解释沪深两市的市场收益、刻画市场风格趋势的系列风格因子&#xff0c;用以分析市场风格切换、组合风格暴露等。 今日为该因子跟踪第14期&#xff0c;指数组合数据截止日2023-11-30&#xff0c;要点如下 近1年A股风格因子检验统…

Gradle下载地址

Gradle下载地址 Gradle是一个基于JVM的构建工具&#xff0c;是一款通用灵活的构建工具&#xff0c;Gradle也是第一个构建集成工具&#xff0c;与ant、maven、ivy有良好的相容相关性。支持maven&#xff0c; Ivy仓库&#xff0c;支持传递性依赖管理&#xff0c;而不需要远程仓库…

涵盖多种功能,龙讯旷腾Module第七期:超快动力学过程

Module是什么 在PWmat的基础功能上&#xff0c;我们针对用户的使用需求开发了一些顶层模块&#xff08;Module&#xff09;。这些Module中的一部分是与已有的优秀工具的接口&#xff0c;一部分是以PWmat的计算结果为基础得到实际需要的物理量&#xff0c;一部分则是为特定的计…

SSRF中Redis的利用

1. SSRF 1.1 什么是SSRF SSRF(Server-Side Request Forgery,服务器请求伪造)是一种由攻击者构造请求,由服务端发起请求的安全漏洞,一般情况下,SSRF攻击的目标是外网无法访问的内网系统(因为请求是由服务端帮我们发起的&#xff0c;所以我们可以通过它来向其所在的内网机器发起…

[音视频]H264码流分析工具

[音视频]H264码流分析工具 CTI-TS EasyICE Elecardstreameyetools VideoEye H264VideoESViewer 学习H264码流&#xff0c;H264码流进行分析 http://blog.csdn.net/leixiaohua1020/article/details/17933821 H264BSAnalyzer https://github.com/latelee/H264BSAnalyzer.g…

基于SpringBoot实现一个可扩展的事件总线

基于SpringBoot实现一个可扩展的事件总线 前言 在日常开发中&#xff0c;我们经常会用到事件总线&#xff0c;SpringBoot通过事件多播器的形式为我们提供了一个事件总线&#xff0c;但是在开发中我们经常会用到其他的实现&#xff0c;比如Guava、Disruptor的。我们将基于Spri…

社交媒体的力量:独立站如何利用海外社媒进行引流

随着全球数字化的浪潮&#xff0c;社交媒体已经成为连接世界的纽带&#xff0c;为企业和个人提供了无限的可能性。对于独立站而言&#xff0c;通过善用海外社交媒体平台&#xff0c;不仅能够拓展用户群体&#xff0c;还能够实现更广泛的品牌曝光和业务引流。本文Nox聚星将和大家…

labelme目标检测数据类型转换

1. labelme数据类型 LabelMe是一个开源的在线图像标注工具&#xff0c;旨在帮助用户创建和标记图像数据集。它提供了一个用户友好的界面&#xff0c;让用户可以直观地在图像上绘制标记框、多边形、线条等&#xff0c;以标识和注释图像中的对象或区域。 GitHub&#xff1a;http…

孔夫子二手书采集

文章目录 项目演示软件采集单本数据网页搜索数据对比 使用场景概述部分核心逻辑Vb工程图数据导入与读取下拉框选择参数设置线程 使用方法下载软件授权导入文件预览处理后的数据 项目结构附件说明 项目演示 操作视频详见演示视频&#xff0c;以下为图文演示 软件采集单本数据 …

unity中使用protobuf工具将proto文件转为C#实体脚本

unity中使用protobuf工具将proto文件转为C#实体脚本 介绍优点缺点Protobuf 为什么比 XML 快得多&#xff1f;Protobuf的EncodingProtobuf封解包的过程通常编写一个Google Protocol Buffer应用需要以下几步&#xff1a; Protostuff是什么Protobuf工具总结 介绍 protobuf也就是G…

设计模式--适配器模式

实验8&#xff1a;适配器模式 本次实验属于模仿型实验&#xff0c;通过本次实验学生将掌握以下内容&#xff1a; 1、理解适配器模式的动机&#xff0c;掌握该模式的结构&#xff1b; 2、能够利用适配器模式解决实际问题。 [实验任务]&#xff1a;双向适配器 实现一个双向…

Java学习时间和日期

1 常用类 1.1 Date 表示日期 具体类 设置时间 1.2 Calendar 表示日历 抽象类 设置日历的设定日期 void set(int year,int month,int date); void set(int year,int month, int date, int hour, int minute,int second); void setTime(Date d); int get(int field)&#…

直播的内容多样性

直播&#xff0c;作为一种新兴的媒体形式&#xff0c;已经深入到我们生活的方方面面。其内容多样性是吸引观众的关键因素之一。以下是直播内容多样性的几个主要方面: 1.主题多样性:直播涵盖的主题非常广泛&#xff0c;包括但不限于娱乐、游戏、体育、教育、招聘、新闻、金融、…

VS2020使用MFC开发一个贪吃蛇游戏

背景&#xff1a; 贪吃蛇游戏 按照如下步骤实现:。初始化地图 。通过键盘控制蛇运动方向&#xff0c;注意重新设置运动方向操作。 。制造食物。 。让蛇移动&#xff0c;如果吃掉食物就重新生成一个食物&#xff0c;如果会死亡就break。用蛇的坐标将地图中的空格替换为 #和”将…

Druid源码阅读-DruidStatInterceptor实现

上次我们在druid-spring-boot-starter里面看到有一个DruidSpringAopConfiguration的配置类&#xff0c;然后引入了DruidStatInterceptor这样一个切面逻辑。今天我们就来看一下这个类的实现。 DruidStatInterceptor 这个类的包路径下入com.alibaba.druid.support.spring.stat。…

DC电源模块有哪些注意事项和使用技巧?

BOSHIDA DC电源模块有哪些注意事项和使用技巧&#xff1f; DC电源模块的注意事项和使用技巧包括以下几点&#xff1a; 1. 选择适当的电源模块&#xff1a;根据需要选择合适的电源模块&#xff0c;考虑电压、电流和功率等参数。确保模块能够满足所需的电力要求。 2. 输入电压范…