使用QGraphicsView思想做一个简单图片查看器
如果要做一个图片查看器,支持放大、滚动操作,比较直接的方法是,使用QWidget来显示完整图片,将QWidget放入QScrollArea。缩放时调整QWidget的尺寸,QScrollArea会自动调整滚动范围,超出视口区域图片自然就不会显示。
如果要使用QGraphicsView的思想呢?
原理
QGraphicsScenes是固定不变的,QGraphicsView使用一个变换矩阵来实现QWidget区域与QGraphicsScene区域之间的转换。缩放和滚动相对直接,任意角度旋转涉及到的问题还是挺麻烦的,这里不考虑。
所以只要能实现图像坐标、区域,到QWidget的坐标、区域转换,和反向转换,剩下就非常简单了。只考虑缩放和滚动,只需要维护变换矩阵(QTransform)和滚动距离,交互上还需要考虑滚动范围,避免超出。
主要代码
以下代码全部使用了浮点值,防止精度损失和溢出
-
根据缩放,重设变换矩阵
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()); }
-
坐标与矩阵映射
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; }
-
绘制图片
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支持将图片重某区域绘制到指定区域。
-
滚动和缩放图片
滚动相对简单,监听鼠标事件,修改当前滚动。
缩放直接修改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()); }
最终效果
其他细节
上述代码基本包含了主要逻辑,一些细节可能需要根据实际需要再增加逻辑。
-
缩放限制
尽管浮点数的运算能最大程度保留精度,但最好考虑在修改scale时,限定范围。 -
缩放时同步缩放图片
很少有软件会做这样的支持,毕竟支持滚动了。
但Windows自带的照片有这样功能,具体原理可以再研究。 -
缩放或者调整窗口时,锚定某个坐标不动
当变换矩阵变化、窗口resize时,QGraphicsView支持锚定某个坐标在视口中不变。具体可以文档QGraphicsView::ViewportAnchor。
可以简单这样实现:// QPointF view_pos; //指定一个视口坐标 QPointF anch_pos = mapToImage(view_pos); // ... // 其他触发变换矩阵变化的逻辑 // ... // 调整前后差异,重新滚动对齐 QPointF new_pos = mapFromImage(anch_pos); scollView(new_pos - view_pos);
-
旋转、翻转
如果只支持90°倍数旋转,直接对原图修改应该比较简单(变换矩阵是否可以做到)