当前位置:   article > 正文

QT实现地图或图片的细节图(抓取图片的细节,放大图片)_qt 显示超大尺寸的图

qt 显示超大尺寸的图

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

QQ录屏

一、使用QLabel

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

二、使用QGraphics

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

1、自定义QGraphicsView

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

2、新建一个QGraphicsScene类

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

  1. #ifndef MYSCENE_H
  2. #define MYSCENE_H
  3. #include <QObject>
  4. #include <QGraphicsScene>
  5. #include <QGraphicsItem>
  6. class MyScene : public QGraphicsScene
  7. {
  8. Q_OBJECT
  9. public:
  10. MyScene();
  11. };
  12. #endif // MYSCENE_H
  1. #include "myscene.h"
  2. MyScene::MyScene()
  3. {
  4. }

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事件。

  1. //view放大缩小(鼠标滑轮事件)
  2. void MyView::wheelEvent(QWheelEvent * event)
  3. {
  4. if (event->modifiers() == Qt::CTRL)
  5. {//按住ctrl键 可以放大缩小
  6. if((event->delta() > 0)&&(scale_m >= 50))//最大放大到原始图像的50
  7. {
  8. return;
  9. }
  10. else if((event->delta() < 0)&&(scale_m <= 0.01))//图像缩小到自适应大小之后就不继续缩小
  11. {
  12. return;//重置图片大小和位置,使之自适应控件窗口大小
  13. }
  14. else
  15. {
  16. // 当前放缩倍数;
  17. qreal scaleFactor = this->matrix().m11();
  18. scale_m = scaleFactor;
  19. int wheelDeltaValue = event->delta();
  20. // 向上滚动,放大;
  21. if (wheelDeltaValue > 0)
  22. {
  23. this->scale(1.2, 1.2);
  24. scale_/=1.2;
  25. }
  26. else
  27. {// 向下滚动,缩小;
  28. this->scale(1.0 / 1.2, 1.0 / 1.2);
  29. scale_*=1.2;
  30. }
  31. update();
  32. }
  33. }
  34. }
  35. //鼠标移动事件
  36. void MyView::mouseMoveEvent(QMouseEvent *event){
  37. QPoint point(event->x(),event->y());
  38. emit sendMousePose(point);//实时显示鼠标在view中位置的信号
  39. if(!isPressed)return;
  40. QPointF lTInMap=mapToScene(leftTop);
  41. emit sendLTScale(scale_,lTInMap,view_width,view_height);//显示view中图片在整体图片的位置
  42. QGraphicsView::mouseMoveEvent(event);//必须加
  43. }
  44. //鼠标按下事件
  45. void MyView::mousePressEvent(QMouseEvent *event){
  46. isPressed=true;
  47. QGraphicsView::mousePressEvent(event);
  48. }
  49. //鼠标释放事件
  50. void MyView::mouseReleaseEvent(QMouseEvent *event){
  51. isPressed=false;
  52. }

        由于在鼠标移动或按下时,会拦截该事件,所有我们在重载最后都需要在加上该事件,使其可以传到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限制在图片的范围内。

  1. //鼠标按下
  2. void MyLabel::mousePressEvent(QMouseEvent *event){
  3. if(event->button()==Qt::LeftButton){
  4. //判断鼠标按下时光标是否在矩形框内
  5. if((event->x()>=pointLT.x())&&(event->y()>=pointLT.y())&&(event->x()<=pointRB.x())&&(event->y()<=pointRB.y())){
  6. isPressed=true;
  7. startPoint=event->pos();//发送鼠标按下的坐标
  8. QPointF lTop(pointLT.x()/scale_size,pointLT.y()/scale_size);
  9. emit sendPressPoint(lTop);
  10. }
  11. }
  12. }
  13. //鼠标移动
  14. void MyLabel::mouseMoveEvent(QMouseEvent *event){
  15. if(isPressed == false||rect.width()==img.width())
  16. return;
  17. endPoint=event->pos();//获得鼠标移动时每个点,x和y坐标减去前一个点的坐标就是移动的距离
  18. qreal temp_x=endPoint.x()-startPoint.x();
  19. qreal temp_y=endPoint.y()-startPoint.y();
  20. //限制矩形框在图片内
  21. if(pointLT.x()+temp_x<=0){
  22. pointLT.setX(0);
  23. }
  24. else {
  25. if(pointLT.x()+rect_width +temp_x >=img.width()){
  26. pointLT.setX(img.width() - rect_width);
  27. }
  28. else {
  29. pointLT.setX(pointLT.x() + temp_x);
  30. }
  31. }
  32. if(pointLT.y() + temp_y<=0){
  33. pointLT.setY(0);
  34. }else {
  35. if(pointLT.y() +rect_height+ temp_y >=img.height()){
  36. pointLT.setY(img.height() - rect_height);
  37. }
  38. else {
  39. pointLT.setY(pointLT.y() + temp_y);
  40. }
  41. }
  42. QRectF rect2(pointLT.x(),pointLT.y(),rect_width,rect_height);
  43. rect=rect2;
  44. startPoint=endPoint;
  45. QPointF lTop(pointLT.x()/scale_size,pointLT.y()/scale_size);
  46. emit sendMovePoint(lTop);
  47. }
  48. //鼠标释放
  49. void MyLabel::mouseReleaseEvent(QMouseEvent *event){
  50. if(event->button()==Qt::LeftButton){
  51. endPoint=event->pos();
  52. isPressed=false;
  53. }
  54. }

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移动这两个偏移量,然后再在判断中将偏移量置零。

  1. //view放大缩小
  2. void MyView::wheelEvent(QWheelEvent * event)
  3. {
  4. initView();//里面没啥东西,就是将view的width和height赋值给全局变量
  5. if (event->modifiers() == Qt::CTRL)
  6. {//按住ctrl键 可以放大缩小
  7. if(offset_h!=0||offset_w!=0){
  8. emit sendOffset(offset_w,offset_h);//发送偏移量
  9. offset_h=0;
  10. offset_w=0;//偏移量置零
  11. QPointF lTInMap=mapToScene(leftTop);
  12. emit sendLTScale(scale_,lTInMap,view_width,view_height);
  13. }
  14. if((event->delta() > 0)&&(scale_m >= 50))//最大放大到原始图像的50
  15. {
  16. return;
  17. }
  18. else if((event->delta() < 0)&&(scale_m <= 0.01))//图像缩小到自适应大小之后就不继续缩小
  19. {
  20. return;//重置图片大小和位置,使之自适应控件窗口大小
  21. }
  22. else
  23. {
  24. // 当前放缩倍数;
  25. qreal scaleFactor = this->matrix().m11();
  26. scale_m = scaleFactor;
  27. int wheelDeltaValue = event->delta();
  28. // 向上滚动,放大;
  29. if (wheelDeltaValue > 0)
  30. {
  31. this->scale(1.2, 1.2);
  32. scale_num*=1.2;
  33. scale_/=1.2;
  34. }
  35. else
  36. {// 向下滚动,缩小;
  37. this->scale(1.0 / 1.2, 1.0 / 1.2);
  38. scale_num/=1.2;
  39. scale_*=1.2;
  40. }
  41. update();
  42. rightButtom.setX(this->width());
  43. rightButtom.setY(this->height());
  44. QPointF lTInMap=mapToScene(leftTop);
  45. emit sendScale_m(scale_num);
  46. emit sendLTScale(scale_,lTInMap,view_width,view_height);
  47. }
  48. }
  49. }
  50. //鼠标移动事件
  51. void MyView::mouseMoveEvent(QMouseEvent *event){
  52. QPoint point(event->x(),event->y());
  53. emit sendMousePose(point);
  54. if(!isPressed)return;
  55. QPointF lTInMap=mapToScene(leftTop);
  56. emit sendLTScale(scale_,lTInMap,view_width,view_height);
  57. QGraphicsView::mouseMoveEvent(event);
  58. }
  59. //鼠标按下事件
  60. void MyView::mousePressEvent(QMouseEvent *event){
  61. isPressed=true;
  62. if(offset_h!=0||offset_w!=0){
  63. emit sendOffset(offset_w,offset_h);
  64. offset_h=0;
  65. offset_w=0;
  66. QPointF lTInMap=mapToScene(leftTop);
  67. emit sendLTScale(scale_,lTInMap,view_width,view_height);
  68. }
  69. QGraphicsView::mousePressEvent(event);
  70. }
  71. //鼠标释放事件
  72. void MyView::mouseReleaseEvent(QMouseEvent *event){
  73. isPressed=false;
  74. }

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/91679
推荐阅读
相关标签
  

闽ICP备14008679号