目录
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 字段
- color:默认标注形状颜色
- current:默认-1,current表示当前操作的标注形状的索引
- pixmap:中心图片
- size:默认标注形状大小
- zoomLevel:默认放大倍数
- MagniFier:是否开启放大镜
- manager:存放2d中心组件
- XOffsetSum:每次移动标注形状的总偏移量
- YOffsetSum:每次移动标注形状的总偏移量
- shapes:标注形状列表
- status:标注状态,默认为noshape
1.3 方法
- paintEvent:重写绘制事件的处理
- mousePressEvent:重写鼠标按压事件的处理
- mouseMoveEvent:重写鼠标移动事件的处理
- 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;
}
}