在 QScrollArea 中缩放和平移图像
Zooming and panning an image in a QScrollArea
我创建了一个显示渲染图像的预览。我将 Image Viewer Example 用于缩放功能 - 所以我有一个 class 继承 QScrollArea
,能够在 QLabel
中显示图像,并使用 in/out/fit 缩放具体限制。我正在显示滚动条 "as needed".
作为新要求,我必须能够平移,并且不显示滚动条。
我一直在寻找方法 - 发现 examples 的人使用鼠标按下、移动和释放事件将图像上的点与滚动条相关联。
问题:
1) 如果滚动条不可见,则移动方向是出乎意料的 - 在平移中,对象向鼠标方向移动(停留在鼠标下方),而滚动条向相反方向移动
2) 我认为移动仅限于滚动条大小,所以...如果我计算反向移动,我会撞到墙上,但仍有空间向一个方向移动
3)这不适用于缩放,而缩放正是需要平移的时候;需要更复杂的计算。
我可以交替使用 QGraphicsView
和
setDragMode(ScrollHandDrag);
它在缩放时也能很好地工作,我不必自己实现它。
我还没有这样做的原因是,我还需要添加一个 QGraphicsScene
和一个包含我想要的图像的 QGraphicsPixmapItem
- 然后找到如何禁用除平移之外的所有鼠标事件 - 和仍然使用 QScrollArea
来保持 QGraphicsView
;
似乎开销太大了(对于速度和内存都很少的嵌入式设备来说,这意味着非常轻巧)。
最好的选择是什么?有什么方法可以在查看器中平移缩放图像,尽可能轻吗?
考虑到自定义可缩放和平移像素图查看器的 paintEvent
实现有 5 行长,不妨从头开始实现它:
// https://github.com/KubaO/Whosebugn/tree/master/questions/image-panzoom-40683840
#include <QtWidgets>
#include <QtNetwork>
class ImageViewer : public QWidget {
QPixmap m_pixmap;
QRectF m_rect;
QPointF m_reference;
QPointF m_delta;
qreal m_scale = 1.0;
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.translate(rect().center());
p.scale(m_scale, m_scale);
p.translate(m_delta);
p.drawPixmap(m_rect.topLeft(), m_pixmap);
}
void mousePressEvent(QMouseEvent *event) override {
m_reference = event->pos();
qApp->setOverrideCursor(Qt::ClosedHandCursor);
setMouseTracking(true);
}
void mouseMoveEvent(QMouseEvent *event) override {
m_delta += (event->pos() - m_reference) * 1.0/m_scale;
m_reference = event->pos();
update();
}
void mouseReleaseEvent(QMouseEvent *) override {
qApp->restoreOverrideCursor();
setMouseTracking(false);
}
public:
void setPixmap(const QPixmap &pix) {
m_pixmap = pix;
m_rect = m_pixmap.rect();
m_rect.translate(-m_rect.center());
update();
}
void scale(qreal s) {
m_scale *= s;
update();
}
QSize sizeHint() const override { return {400, 400}; }
};
一个可比较的基于 QGraphicsView
的小部件只会稍微短一点,如果像素图非常小,开销会多一些。对于大型像素图,由于 QGraphicsScene
/QGraphicsView
机制,渲染像素图所花费的时间大大超过了任何开销。毕竟场景本身是静态的,这才是QGraphicsView
.
表现的理想操作点
class SceneImageViewer : public QGraphicsView {
QGraphicsScene m_scene;
QGraphicsPixmapItem m_item;
public:
SceneImageViewer() {
setScene(&m_scene);
m_scene.addItem(&m_item);
setDragMode(QGraphicsView::ScrollHandDrag);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setResizeAnchor(QGraphicsView::AnchorViewCenter);
}
void setPixmap(const QPixmap &pixmap) {
m_item.setPixmap(pixmap);
auto offset = -QRectF(pixmap.rect()).center();
m_item.setOffset(offset);
setSceneRect(offset.x()*4, offset.y()*4, -offset.x()*8, -offset.y()*8);
translate(1, 1);
}
void scale(qreal s) { QGraphicsView::scale(s, s); }
QSize sizeHint() const override { return {400, 400}; }
};
还有一个测试工具:
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QWidget ui;
QGridLayout layout{&ui};
ImageViewer viewer1;
SceneImageViewer viewer2;
QPushButton zoomOut{"Zoom Out"}, zoomIn{"Zoom In"};
layout.addWidget(&viewer1, 0, 0);
layout.addWidget(&viewer2, 0, 1);
layout.addWidget(&zoomOut, 1, 0, 1, 1, Qt::AlignLeft);
layout.addWidget(&zoomIn, 1, 1, 1, 1, Qt::AlignRight);
QNetworkAccessManager mgr;
QScopedPointer<QNetworkReply> rsp(
mgr.get(QNetworkRequest({"http://i.imgur.com/ikwUmUV.jpg"})));
QObject::connect(rsp.data(), &QNetworkReply::finished, [&]{
if (rsp->error() == QNetworkReply::NoError) {
QPixmap pixmap;
pixmap.loadFromData(rsp->readAll());
viewer1.setPixmap(pixmap);
viewer2.setPixmap(pixmap);
}
rsp.reset();
});
QObject::connect(&zoomIn, &QPushButton::clicked, [&]{
viewer1.scale(1.1); viewer2.scale(1.1);
});
QObject::connect(&zoomOut, &QPushButton::clicked, [&]{
viewer1.scale(1.0/1.1); viewer2.scale(1.0/1.1);
});
ui.show();
return a.exec();
}
我创建了一个显示渲染图像的预览。我将 Image Viewer Example 用于缩放功能 - 所以我有一个 class 继承 QScrollArea
,能够在 QLabel
中显示图像,并使用 in/out/fit 缩放具体限制。我正在显示滚动条 "as needed".
作为新要求,我必须能够平移,并且不显示滚动条。
我一直在寻找方法 - 发现 examples 的人使用鼠标按下、移动和释放事件将图像上的点与滚动条相关联。
问题:
1) 如果滚动条不可见,则移动方向是出乎意料的 - 在平移中,对象向鼠标方向移动(停留在鼠标下方),而滚动条向相反方向移动
2) 我认为移动仅限于滚动条大小,所以...如果我计算反向移动,我会撞到墙上,但仍有空间向一个方向移动
3)这不适用于缩放,而缩放正是需要平移的时候;需要更复杂的计算。
我可以交替使用 QGraphicsView
和
setDragMode(ScrollHandDrag);
它在缩放时也能很好地工作,我不必自己实现它。
我还没有这样做的原因是,我还需要添加一个 QGraphicsScene
和一个包含我想要的图像的 QGraphicsPixmapItem
- 然后找到如何禁用除平移之外的所有鼠标事件 - 和仍然使用 QScrollArea
来保持 QGraphicsView
;
似乎开销太大了(对于速度和内存都很少的嵌入式设备来说,这意味着非常轻巧)。
最好的选择是什么?有什么方法可以在查看器中平移缩放图像,尽可能轻吗?
考虑到自定义可缩放和平移像素图查看器的 paintEvent
实现有 5 行长,不妨从头开始实现它:
// https://github.com/KubaO/Whosebugn/tree/master/questions/image-panzoom-40683840
#include <QtWidgets>
#include <QtNetwork>
class ImageViewer : public QWidget {
QPixmap m_pixmap;
QRectF m_rect;
QPointF m_reference;
QPointF m_delta;
qreal m_scale = 1.0;
void paintEvent(QPaintEvent *) override {
QPainter p{this};
p.translate(rect().center());
p.scale(m_scale, m_scale);
p.translate(m_delta);
p.drawPixmap(m_rect.topLeft(), m_pixmap);
}
void mousePressEvent(QMouseEvent *event) override {
m_reference = event->pos();
qApp->setOverrideCursor(Qt::ClosedHandCursor);
setMouseTracking(true);
}
void mouseMoveEvent(QMouseEvent *event) override {
m_delta += (event->pos() - m_reference) * 1.0/m_scale;
m_reference = event->pos();
update();
}
void mouseReleaseEvent(QMouseEvent *) override {
qApp->restoreOverrideCursor();
setMouseTracking(false);
}
public:
void setPixmap(const QPixmap &pix) {
m_pixmap = pix;
m_rect = m_pixmap.rect();
m_rect.translate(-m_rect.center());
update();
}
void scale(qreal s) {
m_scale *= s;
update();
}
QSize sizeHint() const override { return {400, 400}; }
};
一个可比较的基于 QGraphicsView
的小部件只会稍微短一点,如果像素图非常小,开销会多一些。对于大型像素图,由于 QGraphicsScene
/QGraphicsView
机制,渲染像素图所花费的时间大大超过了任何开销。毕竟场景本身是静态的,这才是QGraphicsView
.
class SceneImageViewer : public QGraphicsView {
QGraphicsScene m_scene;
QGraphicsPixmapItem m_item;
public:
SceneImageViewer() {
setScene(&m_scene);
m_scene.addItem(&m_item);
setDragMode(QGraphicsView::ScrollHandDrag);
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setResizeAnchor(QGraphicsView::AnchorViewCenter);
}
void setPixmap(const QPixmap &pixmap) {
m_item.setPixmap(pixmap);
auto offset = -QRectF(pixmap.rect()).center();
m_item.setOffset(offset);
setSceneRect(offset.x()*4, offset.y()*4, -offset.x()*8, -offset.y()*8);
translate(1, 1);
}
void scale(qreal s) { QGraphicsView::scale(s, s); }
QSize sizeHint() const override { return {400, 400}; }
};
还有一个测试工具:
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QWidget ui;
QGridLayout layout{&ui};
ImageViewer viewer1;
SceneImageViewer viewer2;
QPushButton zoomOut{"Zoom Out"}, zoomIn{"Zoom In"};
layout.addWidget(&viewer1, 0, 0);
layout.addWidget(&viewer2, 0, 1);
layout.addWidget(&zoomOut, 1, 0, 1, 1, Qt::AlignLeft);
layout.addWidget(&zoomIn, 1, 1, 1, 1, Qt::AlignRight);
QNetworkAccessManager mgr;
QScopedPointer<QNetworkReply> rsp(
mgr.get(QNetworkRequest({"http://i.imgur.com/ikwUmUV.jpg"})));
QObject::connect(rsp.data(), &QNetworkReply::finished, [&]{
if (rsp->error() == QNetworkReply::NoError) {
QPixmap pixmap;
pixmap.loadFromData(rsp->readAll());
viewer1.setPixmap(pixmap);
viewer2.setPixmap(pixmap);
}
rsp.reset();
});
QObject::connect(&zoomIn, &QPushButton::clicked, [&]{
viewer1.scale(1.1); viewer2.scale(1.1);
});
QObject::connect(&zoomOut, &QPushButton::clicked, [&]{
viewer1.scale(1.0/1.1); viewer2.scale(1.0/1.1);
});
ui.show();
return a.exec();
}