QT实现地图或图片的细节图(抓取图片的细节,放大图片)

      最近由于项目上的要求,需要实现一个细节图,就是一个矩形框中加载一个大图,右下或者左下方有一个小矩形框,可以加载全图,并显示大矩形框中图片在全图的位置,有点拗口,直接上结果:

QQ录屏

一、使用QLabel

        最开始由于笔者对QGraphics不熟悉,当时有两个思路,但第一时间选择用QLabel。过程中也遇到了坐标映射的问题,在领导的指点下解决了,不过由于最后需求中需要拖动细节图的红框而大图中视角随之移动,这个需求完成不了,最后选择了不采用QLabel,也是笔者水平有限,要是你们用QLabel实现了的话,就更棒了。由于最后没有实现需求,代码这里就不放出来了!

二、使用QGraphics

        由于QLabel的失败,笔者就开始研究QGraphics,发现这个类用来加载图片简直太方便了。

1、自定义QGraphicsView

        首先,需要加载大图,我们这里需要在ui界面中拖入一个QGraphicsView控件,并点击右键提升为自定义控件类。这里要是不会提升自定义控件可以查阅相关博客或文章,并不难,其目的主要是用于处理鼠标滑轮和鼠标移动事件。同时新建一个类继承自QGraphicsView。

2、新建一个QGraphicsScene类

        view只是一个用来显示图片的窗口(视图),最后加载图片是通过设置item加载到scene(场景中)。新建一个QGraphicsScene类,方便在主线程中使用。

#ifndef MYSCENE_H
#define MYSCENE_H

#include <QObject>
#include <QGraphicsScene>
#include <QGraphicsItem>
class MyScene : public QGraphicsScene
{
  Q_OBJECT
public:
  MyScene();

};

#endif // MYSCENE_H
#include "myscene.h"

MyScene::MyScene()
{


}

3、将图片加载到scene中 

        先在mainwindow中设置场景和视图,再通过addItem将图片加载到场景中。由于graphicsview是ui的控件,所有这里不需要再实例化。

MyScene myscene;
QGraphicsItem *map;//头文件声明

 QImage image("work.png");//图片地址,自行修改
  map=new QGraphicsPixmapItem(QPixmap::fromImage(image));
  myscene.addItem(map);
  ui.graphicsView->setScene(&myscene);//给视图设置场景

此时就可以将图片加载到场景中!

4、鼠标滑轮放大事件 

        在自定义的QGraphicsView中重载鼠标滑轮事件。通过滑轮上下滑动即可放大缩小图片,其本质是缩小放大了view窗口相对于scene的大小,并没有实际地缩放scene中的图片。(代码段见6)

5、(细节图)新建一个QLabel提升为自定义控件

        细节图的思路就是用一个QLabel来加载图片,显示图片整体,并在其中用红色矩形框表示view在图片的范围。在ui界面拖入一个QLabel,然后新建一个mylabel的类继承自QLabel,然后提升自定义控件。

        QLabel控件中加载图片有两种思路,可以通过mainwindow中设置图片,也可以像笔者一样,通过信号槽,将图片发送到mylabel中,只需要在mainwindow中定义一个信号(void sendPixMap(QImage)),再在mylabel中定义一个槽函数(void getPixMap(QImage)),至于连接可以在mainwindow中连接,也可在ui界面正下方的Signals Slots Editor中连接,不过如果采用后者,需要在每个对象中右键“改变信号\槽”中添加对应的信号或槽。

        笔者给定了label的尺寸,所有加载时缩放了图片的尺寸,使之能适应label的尺寸。

        //缩放图片
qreal MyLabel::scaleImg(QImage &img){
  if((img.width()>this->width())||(img.height()>this->height())){
    double prop_w=(double)this->width()/(double)img.width();
    double prop_h=(double)this->height()/(double)img.height();
    if(prop_w>=prop_h){
      return prop_h;
    }else if(prop_w<prop_h){
      return prop_w;
    }
  }else{
    return 1;
  }
}

//得到图片
void MyLabel::getMap(QImage img1){
  isPixMap=true;
  img=img1;
  scale_size=scaleImg(img);
  img=img.scaled((double)img.width()*scale_size,(double)img.height()*scale_size);
}

重载绘图事件,使能在label中绘制图片。

//绘图事件
void MyLabel::paintEvent(QPaintEvent *event){
  QPainter painter(this);
  painter.drawPixmap(0,0,QPixmap::fromImage(img));
  update();//刷新
}

6、view中缩放图片,以及鼠标移动(拖拽图片)事件 

        重载mousemoveevent事件。

//view放大缩小(鼠标滑轮事件)
void MyView::wheelEvent(QWheelEvent * event)
{

  if (event->modifiers() == Qt::CTRL)
    {//按住ctrl键 可以放大缩小
      if((event->delta() > 0)&&(scale_m >= 50))//最大放大到原始图像的50倍
      {
        return;
      }
      else if((event->delta() < 0)&&(scale_m <= 0.01))//图像缩小到自适应大小之后就不继续缩小
      {
        return;//重置图片大小和位置,使之自适应控件窗口大小
      }
      else
      {
        // 当前放缩倍数;
        qreal scaleFactor = this->matrix().m11();
        scale_m = scaleFactor;
        int wheelDeltaValue = event->delta();
        // 向上滚动,放大;
        if (wheelDeltaValue > 0)
        {
          this->scale(1.2, 1.2);
          scale_/=1.2;
        }
        else
        {// 向下滚动,缩小;
          this->scale(1.0 / 1.2, 1.0 / 1.2);
          scale_*=1.2;
        }
        update();
      }
    }
}
//鼠标移动事件
void MyView::mouseMoveEvent(QMouseEvent *event){
  QPoint point(event->x(),event->y());
  emit sendMousePose(point);//实时显示鼠标在view中位置的信号
  if(!isPressed)return;
  QPointF lTInMap=mapToScene(leftTop); 
  emit sendLTScale(scale_,lTInMap,view_width,view_height);//显示view中图片在整体图片的位置
  QGraphicsView::mouseMoveEvent(event);//必须加
}
//鼠标按下事件
void MyView::mousePressEvent(QMouseEvent *event){
  isPressed=true;
  QGraphicsView::mousePressEvent(event);
}
//鼠标释放事件
void MyView::mouseReleaseEvent(QMouseEvent *event){
  isPressed=false;
 }

        由于在鼠标移动或按下时,会拦截该事件,所有我们在重载最后都需要在加上该事件,使其可以传到scene中 。同时滑动时放大,是通过view在scene中的左上角坐标,以及自身的长宽和缩放比来确定在scene中的范围,所有在鼠标滑轮事件和鼠标移动事件中,可以定义一个信号(void sendLTScale(qreal,QPointF,int,int)),实时传送此时view的各个参数,label中接受到后做如下处理:

void MyLabel::getLTScale(qreal scale_,QPointF lTop,int view_width,int view_height){
  QRectF rect2(lTop.x(),lTop.y(),view_width*scale_*scale_size*0.01,view_height*scale_*scale_size*0.01);//view映射出的矩形框
  pointLT=lTop;//label中红色矩形框左上角坐标
  rect_width=view_width*scale_*scale_size*0.01;
  rect_height=view_height*scale_*scale_size*0.01;
  rect=rect2;
}

上面这个是拖动或滑动滑轮,都会发送当前view在图片位置的信号,label中连接槽函数得到后,存到一个全局变量rect的矩形框中,然后label中的位绘图事件中再加入绘制矩形。

painter.drawRect(rect);

7、label中拖动矩形框rect

        上面实现了拖动view中图片,移动label中rect,此外我们还要拖动label中rect,随之view中视角改变。当然我们还需要把rect限制在图片的范围内。

//鼠标按下
void MyLabel::mousePressEvent(QMouseEvent *event){
  if(event->button()==Qt::LeftButton){
//判断鼠标按下时光标是否在矩形框内
    if((event->x()>=pointLT.x())&&(event->y()>=pointLT.y())&&(event->x()<=pointRB.x())&&(event->y()<=pointRB.y())){
      isPressed=true;
      startPoint=event->pos();//发送鼠标按下的坐标
      QPointF lTop(pointLT.x()/scale_size,pointLT.y()/scale_size);
      emit sendPressPoint(lTop);
    }
  }
}
//鼠标移动
void MyLabel::mouseMoveEvent(QMouseEvent *event){

  if(isPressed == false||rect.width()==img.width())
    return;
  endPoint=event->pos();//获得鼠标移动时每个点,x和y坐标减去前一个点的坐标就是移动的距离
  qreal temp_x=endPoint.x()-startPoint.x();
  qreal temp_y=endPoint.y()-startPoint.y();
//限制矩形框在图片内
  if(pointLT.x()+temp_x<=0){
    pointLT.setX(0);
  }
  else {
    if(pointLT.x()+rect_width +temp_x >=img.width()){
      pointLT.setX(img.width() - rect_width);
    }
    else {
      pointLT.setX(pointLT.x() + temp_x);
    }
  }

  if(pointLT.y() + temp_y<=0){
      pointLT.setY(0);
    }else {
      if(pointLT.y() +rect_height+ temp_y >=img.height()){
        pointLT.setY(img.height() - rect_height);
      }
      else {
        pointLT.setY(pointLT.y() + temp_y);
      }
    }

  QRectF rect2(pointLT.x(),pointLT.y(),rect_width,rect_height);
  rect=rect2;
  startPoint=endPoint;
  QPointF lTop(pointLT.x()/scale_size,pointLT.y()/scale_size);
  emit sendMovePoint(lTop);

}
//鼠标释放
void MyLabel::mouseReleaseEvent(QMouseEvent *event){
  if(event->button()==Qt::LeftButton){
    endPoint=event->pos();
    isPressed=false;
  }
}

8、item随之移动

上面拖动label中rect,鼠标实时发送光标位置,后一个减前一个坐标,并把当前坐标又赋值给前一个坐标,即可获得移动的距离。

但是,我们不能直接移动view(即不能使用view.move(x,y),这是在窗口中拖动view窗口),因为通过maptoscene这个函数发现,我们鼠标拖拽里面的图片,实际view映射到item上的坐标根本没有改变。笔者这里选择的是移动scene中的item。

void MainWindow::getMovePoint(QPointF temp){

  map->moveBy(-((double)temp.x()-(double)start_.x()),-((double)temp.y()-(double)start_.y()));
  emit sendMoveSize(((double)temp.x()-(double)start_.x()),((double)temp.y()-(double)start_.y()));
  start_=temp;
}

通过信号槽将鼠标的坐标发送到主线程中,因为我们是在mainwindow这个里面加入item,也可在这里移动item。

9、坐标映射问题

由于笔者上面移动item,虽然达到了拖动label中rect,view中视角随之移动,但是也引发了深层次问题,即我们拖动了label中rect,再讲鼠标放在view中拖拽图片或者放大缩小图片,就会发现,此时图片发现了便宜,坐标已经映射不到原来的坐标了。

最后,由于笔者水平有限,没有更好的办法,就选择在拖动label中rect,一旦在view中拖拽图片或放大缩小图片,我们就将偏移量还原回来,此时也达到了一定目的。希望笔者后续水平提升可以想出更好的方法。

思路就是在view中鼠标滑轮和按下事件中加一个判断,将item移动的偏移量,存在view的两个全局变量里,移动触发这两个事件,判断是否变量为0,不为0,就发送信号,主线程中接受到触发槽函数将item移动这两个偏移量,然后再在判断中将偏移量置零。

//view放大缩小
void MyView::wheelEvent(QWheelEvent * event)
{
  initView();//里面没啥东西,就是将view的width和height赋值给全局变量

  if (event->modifiers() == Qt::CTRL)
    {//按住ctrl键 可以放大缩小
    if(offset_h!=0||offset_w!=0){
      emit sendOffset(offset_w,offset_h);//发送偏移量
      offset_h=0;
      offset_w=0;//偏移量置零
      QPointF lTInMap=mapToScene(leftTop);
      emit sendLTScale(scale_,lTInMap,view_width,view_height);
    }
      if((event->delta() > 0)&&(scale_m >= 50))//最大放大到原始图像的50倍
      {
        return;
      }
      else if((event->delta() < 0)&&(scale_m <= 0.01))//图像缩小到自适应大小之后就不继续缩小
      {
        return;//重置图片大小和位置,使之自适应控件窗口大小
      }
      else
      {
        // 当前放缩倍数;
        qreal scaleFactor = this->matrix().m11();
        scale_m = scaleFactor;
        int wheelDeltaValue = event->delta();
        // 向上滚动,放大;
        if (wheelDeltaValue > 0)
        {
          this->scale(1.2, 1.2);
          scale_num*=1.2;
          scale_/=1.2;
        }
        else
        {// 向下滚动,缩小;
          this->scale(1.0 / 1.2, 1.0 / 1.2);
          scale_num/=1.2;
          scale_*=1.2;
        }
        update();
        rightButtom.setX(this->width());
        rightButtom.setY(this->height());
        QPointF lTInMap=mapToScene(leftTop);
        emit sendScale_m(scale_num);
        emit sendLTScale(scale_,lTInMap,view_width,view_height);
      }
    }
}
//鼠标移动事件
void MyView::mouseMoveEvent(QMouseEvent *event){
  QPoint point(event->x(),event->y());
  emit sendMousePose(point);
  if(!isPressed)return;
  QPointF lTInMap=mapToScene(leftTop); 
  emit sendLTScale(scale_,lTInMap,view_width,view_height);
  QGraphicsView::mouseMoveEvent(event);
}
//鼠标按下事件
void MyView::mousePressEvent(QMouseEvent *event){
  isPressed=true;
  if(offset_h!=0||offset_w!=0){
    emit sendOffset(offset_w,offset_h);
    offset_h=0;
    offset_w=0;
    QPointF lTInMap=mapToScene(leftTop);
    emit sendLTScale(scale_,lTInMap,view_width,view_height);
  }
  QGraphicsView::mousePressEvent(event);
}
//鼠标释放事件
void MyView::mouseReleaseEvent(QMouseEvent *event){
  isPressed=false;
 }

mainwindow接收到信号后,将item移动对应的偏移量。

void MainWindow::getOffset(double offset_w,double offset_h){
  map->moveBy(offset_w,offset_h);

接下来,笔者的工作还需要在item上画图,标点或画线,后续还会涉及到坐标转换的问题,额,几何学的差,哎~~

  • 24
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值