您的位置:首页 > 娱乐 > 明星 > 使用QGraphicsView思想做一个简单图片查看器

使用QGraphicsView思想做一个简单图片查看器

2024/10/6 12:19:38 来源:https://blog.csdn.net/eiilpux17/article/details/141325162  浏览:    关键词:使用QGraphicsView思想做一个简单图片查看器

使用QGraphicsView思想做一个简单图片查看器

如果要做一个图片查看器,支持放大、滚动操作,比较直接的方法是,使用QWidget来显示完整图片,将QWidget放入QScrollArea。缩放时调整QWidget的尺寸,QScrollArea会自动调整滚动范围,超出视口区域图片自然就不会显示。

如果要使用QGraphicsView的思想呢?

原理

QGraphicsScenes是固定不变的,QGraphicsView使用一个变换矩阵来实现QWidget区域与QGraphicsScene区域之间的转换。缩放和滚动相对直接,任意角度旋转涉及到的问题还是挺麻烦的,这里不考虑。
场景和视口关系
所以只要能实现图像坐标、区域,到QWidget的坐标、区域转换,和反向转换,剩下就非常简单了。只考虑缩放和滚动,只需要维护变换矩阵(QTransform)和滚动距离,交互上还需要考虑滚动范围,避免超出。

主要代码

以下代码全部使用了浮点值,防止精度损失和溢出

  1. 根据缩放,重设变换矩阵

    void resetView()
    {// QSizeF scrollRange; //缓存滚动范围// qreal scale; //当前缩放QRectF rect(this->rect());scrollRange = imageRect.size() / scale - rect.size();QPointF offset(0, 0);	// 当图片显示小于视口,用于居中if(scrollRange.width() < 0){// 水平居中offset.rx() = - scrollRange.rwidth() / 2;scrollRange.rwidth() = 0;}if(scrollRange.height() < 0){// 垂直居中offset.ry() = - scrollRange.height() / 2;scrollRange.rheight() = 0;}// 变换矩阵transform = QTransform().scale(scale, scale).translate(-offset.x(), -offset.y());
    }
    
  2. 坐标与矩阵映射
    QGraphicsView内部,滚动范围值是场景区域经过变换后的区域范围,并非从0起始。
    由于滚动代表实际的偏移位置,直接写入transform不方便

    // 	QPointF scrollValue; // 当前滚动,为了方便使用坐标点
    QPointF mapToImage(QPointF pos){return transform.map(pos + scrollValue);
    }
    QPointF mapFromImage(QPointF pos){return transform.inverted().map(pos) - scrollValue;
    }
    QRectF mapToImage(QRectF rect){rect.moveTopLeft(rect.topLeft() + scrollValue);return transform.mapRect(rect);
    }
    QRectF mapFromImage(QRectF rect){rect = transform.inverted().mapRect(rect);rect.moveTopLeft(rect.topLeft() - scrollValue);return rect;
    }
    
  3. 绘制图片

    QRectF rect(this->rect());
    QRectF img_rect = mapToImage(rect).intersected(imageRect);
    QRectF paint_rect = mapFromImage(img_rect);QPainter painter(this);
    painter.setRenderHint(QPainter::SmoothPixmapTransform);
    painter.drawImage(paint_rect, image, img_rect);
    

    绘制时,先将窗口区域变换到图片区域,求取交集,再反算到视口区域。QPainter支持将图片重某区域绘制到指定区域。

  4. 滚动和缩放图片
    滚动相对简单,监听鼠标事件,修改当前滚动。
    缩放直接修改scale,调用resetView重新计算滚动范围、变换矩阵。

    void scollView(QPointF dp){scrollValue.rx() = qBound(0.0, scrollValue.x() + dp.x(), scrollRange.width());scrollValue.ry() = qBound(0.0, scrollValue.y() + dp.y(), scrollRange.height());
    }
    

最终效果

在这里插入图片描述

其他细节

上述代码基本包含了主要逻辑,一些细节可能需要根据实际需要再增加逻辑。

  1. 缩放限制
    尽管浮点数的运算能最大程度保留精度,但最好考虑在修改scale时,限定范围。

  2. 缩放时同步缩放图片
    很少有软件会做这样的支持,毕竟支持滚动了。
    但Windows自带的照片有这样功能,具体原理可以再研究。

  3. 缩放或者调整窗口时,锚定某个坐标不动
    当变换矩阵变化、窗口resize时,QGraphicsView支持锚定某个坐标在视口中不变。具体可以文档QGraphicsView::ViewportAnchor。
    可以简单这样实现:

    // QPointF view_pos; //指定一个视口坐标
    QPointF anch_pos = mapToImage(view_pos);
    // ...
    // 其他触发变换矩阵变化的逻辑
    // ...
    // 调整前后差异,重新滚动对齐
    QPointF new_pos = mapFromImage(anch_pos);
    scollView(new_pos - view_pos);
    
  4. 旋转、翻转
    如果只支持90°倍数旋转,直接对原图修改应该比较简单(变换矩阵是否可以做到)

  5. 高分屏适配
    将原始图像的信息除以缩放,与QWidget一致,在最后即将绘制时再换算到实际像素区域

  6. 抗锯齿
    即便QPainter开启抗锯齿和平滑缩放图片,依然得不到好的效果……属于Qt问题,可以复制一份图片待绘制区域的像素,并手动缩放再绘制。这就需要优化效率了

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com