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;
    }
}

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-26 17:48:04       98 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-26 17:48:04       106 阅读
  3. 在Django里面运行非项目文件

    2023-12-26 17:48:04       87 阅读
  4. Python语言-面向对象

    2023-12-26 17:48:04       96 阅读

热门阅读

  1. 自定义删除无依赖文件的webpack插件

    2023-12-26 17:48:04       57 阅读
  2. js 学习

    2023-12-26 17:48:04       47 阅读
  3. ebpf基础篇(一) -------- hello ebpf

    2023-12-26 17:48:04       57 阅读
  4. 服务器的出口IP地址查询

    2023-12-26 17:48:04       49 阅读
  5. LeetCode 75| 前缀和

    2023-12-26 17:48:04       57 阅读
  6. MFC或QT中,自绘控件的目的和实现步骤

    2023-12-26 17:48:04       48 阅读
  7. 关于 log4net 日志功能使用方法

    2023-12-26 17:48:04       47 阅读
  8. ecology-SQL优化技巧

    2023-12-26 17:48:04       57 阅读