计算视图偏移以在鼠标光标的位置放大
Calculating view offset for zooming in at the position of the mouse cursor
我有一个 "canvas",用户可以在上面绘制像素等。它运行良好,但无论鼠标的位置如何,我的缩放功能目前都使用相同的原点。我想实现类似 Google 地图缩放行为的功能:
也就是说,缩放的原点应该始终是鼠标光标的位置。
What I currently have 不完全正确...
我的尝试大多是在黑暗中进行,但我也尝试过使用 this answer 中的代码但没有成功。
main.cpp
:
#include <QGuiApplication>
#include <QtQuick>
class Canvas : public QQuickPaintedItem
{
Q_OBJECT
public:
Canvas() :
mTileWidth(25),
mTileHeight(25),
mTilesAcross(10),
mTilesDown(10),
mOffset(QPoint(400, 400)),
mZoomLevel(1)
{
}
void paint(QPainter *painter) override {
painter->translate(mOffset);
const int zoomedTileWidth = mTilesAcross * mZoomLevel;
const int zoomedTileHeight = mTilesDown * mZoomLevel;
const int zoomedMapWidth = qMin(mTilesAcross * zoomedTileWidth, qFloor(width()));
const int zoomedMapHeight = qMin(mTilesDown * zoomedTileHeight, qFloor(height()));
painter->fillRect(0, 0, zoomedMapWidth, zoomedMapHeight, QColor(Qt::gray));
for (int y = 0; y < mTilesDown; ++y) {
for (int x = 0; x < mTilesAcross; ++x) {
const QRect rect(x * zoomedTileWidth, y * zoomedTileHeight, zoomedTileWidth, zoomedTileHeight);
painter->drawText(rect, QString::fromLatin1("%1, %2").arg(x).arg(y));
}
}
}
protected:
void wheelEvent(QWheelEvent *event) override {
const int oldZoomLevel = mZoomLevel;
mZoomLevel = qMax(1, qMin(mZoomLevel + (event->angleDelta().y() > 0 ? 1 : -1), 30));
const QPoint cursorPosRelativeToOffset = event->pos() - mOffset;
if (mZoomLevel != oldZoomLevel) {
mOffset.rx() -= cursorPosRelativeToOffset.x();
mOffset.ry() -= cursorPosRelativeToOffset.y();
// Attempts based on
// mOffset.setX((event->pos().x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((event->pos().y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
// mOffset.setX((cursorPosRelativeToOffset.x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((cursorPosRelativeToOffset.y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
update();
}
}
void keyReleaseEvent(QKeyEvent *event) override {
static const int panDistance = 50;
switch (event->key()) {
case Qt::Key_Left:
mOffset.rx() -= panDistance;
update();
break;
case Qt::Key_Right:
mOffset.rx() += panDistance;
update();
break;
case Qt::Key_Up:
mOffset.ry() -= panDistance;
update();
break;
case Qt::Key_Down:
mOffset.ry() += panDistance;
update();
break;
}
}
private:
const int mTileWidth;
const int mTileHeight;
const int mTilesAcross;
const int mTilesDown;
QPoint mOffset;
int mZoomLevel;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Canvas>("App", 1, 0, "Canvas");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml
:
import QtQuick 2.5
import QtQuick.Window 2.2
import App 1.0 as App
Window {
visible: true
width: 1200
height: 900
title: qsTr("Hello World")
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
App.Canvas {
focus: true
anchors.fill: parent
}
}
我在 wheelEvent()
函数中做错了什么?
您有一个具有绝对坐标的矩形 R = [x_0, x_0 + w] x [y_0, y_0 + h]
。当您将它映射到一个小部件(另一个矩形)时,您将一些变换 T
应用到 R
的区域 W
。此转换与偏移量呈线性关系:
计算a_x, b_x, a_y, b_y
的值是为了满足一些简单的条件,你已经做到了。
您在 R
中也有一个游标 (x_c, y_c)
。它在 W
中的坐标是 T(x_c, y_c)
。现在您想应用另一个转换 ,
将比例系数 a_x, a_y
更改为已知 a_x', a_y'
,条件如下:您希望光标指向 R
中的相同坐标 (x_c, y_c)
。 IE。 T'(x_c, y_c) = T(x_c, y_c)
— 相对坐标中的相同点指向绝对坐标中的相同位置。我们推导出一个具有已知剩余值的未知偏移量 b_x', b_y'
的系统。它给出
最后的工作是从小部件光标位置 (x_p, y_p) = T(x_c, y_c)
:
找到 (x_c, y_c)
并替换它:
用你的话说就是
mOffset = event->pos() - float(mZoomLevel) / float(oldZoomLevel) *
(event->pos() - mOffset);
如果您想像 Google 地图那样缩放,那么您的原点必须位于图像的 top-left 角(比方说 (x,y) = (0,0) 和(宽度, height) = (100,100)) 初始 zoomLevel 100。
如果你想在点 (40,20) 以 5% 的缩放因子缩放,那么,
位移可以计算为-
newX = 40 - 40*(100.0/105)
newY = 20 - 20*(100.0/105)
newWidth = width - (100.0/105)
newHeight = height - (100.0/105)
然后将newX、newY设置为原点,并将宽度、高度更改为newWidth和newHeight。
通过此实现,您将能够缩放光标所在的特定点。但是当您将光标移动到其他一些位置时,此实现将不起作用。
我也在寻找那个实现。
我有一个 "canvas",用户可以在上面绘制像素等。它运行良好,但无论鼠标的位置如何,我的缩放功能目前都使用相同的原点。我想实现类似 Google 地图缩放行为的功能:
也就是说,缩放的原点应该始终是鼠标光标的位置。
What I currently have 不完全正确...
我的尝试大多是在黑暗中进行,但我也尝试过使用 this answer 中的代码但没有成功。
main.cpp
:
#include <QGuiApplication>
#include <QtQuick>
class Canvas : public QQuickPaintedItem
{
Q_OBJECT
public:
Canvas() :
mTileWidth(25),
mTileHeight(25),
mTilesAcross(10),
mTilesDown(10),
mOffset(QPoint(400, 400)),
mZoomLevel(1)
{
}
void paint(QPainter *painter) override {
painter->translate(mOffset);
const int zoomedTileWidth = mTilesAcross * mZoomLevel;
const int zoomedTileHeight = mTilesDown * mZoomLevel;
const int zoomedMapWidth = qMin(mTilesAcross * zoomedTileWidth, qFloor(width()));
const int zoomedMapHeight = qMin(mTilesDown * zoomedTileHeight, qFloor(height()));
painter->fillRect(0, 0, zoomedMapWidth, zoomedMapHeight, QColor(Qt::gray));
for (int y = 0; y < mTilesDown; ++y) {
for (int x = 0; x < mTilesAcross; ++x) {
const QRect rect(x * zoomedTileWidth, y * zoomedTileHeight, zoomedTileWidth, zoomedTileHeight);
painter->drawText(rect, QString::fromLatin1("%1, %2").arg(x).arg(y));
}
}
}
protected:
void wheelEvent(QWheelEvent *event) override {
const int oldZoomLevel = mZoomLevel;
mZoomLevel = qMax(1, qMin(mZoomLevel + (event->angleDelta().y() > 0 ? 1 : -1), 30));
const QPoint cursorPosRelativeToOffset = event->pos() - mOffset;
if (mZoomLevel != oldZoomLevel) {
mOffset.rx() -= cursorPosRelativeToOffset.x();
mOffset.ry() -= cursorPosRelativeToOffset.y();
// Attempts based on
// mOffset.setX((event->pos().x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((event->pos().y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
// mOffset.setX((cursorPosRelativeToOffset.x() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.x()));
// mOffset.setY((cursorPosRelativeToOffset.y() * (mZoomLevel - oldZoomLevel)) + (mZoomLevel * -mOffset.y()));
update();
}
}
void keyReleaseEvent(QKeyEvent *event) override {
static const int panDistance = 50;
switch (event->key()) {
case Qt::Key_Left:
mOffset.rx() -= panDistance;
update();
break;
case Qt::Key_Right:
mOffset.rx() += panDistance;
update();
break;
case Qt::Key_Up:
mOffset.ry() -= panDistance;
update();
break;
case Qt::Key_Down:
mOffset.ry() += panDistance;
update();
break;
}
}
private:
const int mTileWidth;
const int mTileHeight;
const int mTilesAcross;
const int mTilesDown;
QPoint mOffset;
int mZoomLevel;
};
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<Canvas>("App", 1, 0, "Canvas");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml
:
import QtQuick 2.5
import QtQuick.Window 2.2
import App 1.0 as App
Window {
visible: true
width: 1200
height: 900
title: qsTr("Hello World")
Shortcut {
sequence: "Ctrl+Q"
onActivated: Qt.quit()
}
App.Canvas {
focus: true
anchors.fill: parent
}
}
我在 wheelEvent()
函数中做错了什么?
您有一个具有绝对坐标的矩形 R = [x_0, x_0 + w] x [y_0, y_0 + h]
。当您将它映射到一个小部件(另一个矩形)时,您将一些变换 T
应用到 R
的区域 W
。此转换与偏移量呈线性关系:
计算a_x, b_x, a_y, b_y
的值是为了满足一些简单的条件,你已经做到了。
您在 R
中也有一个游标 (x_c, y_c)
。它在 W
中的坐标是 T(x_c, y_c)
。现在您想应用另一个转换 ,
将比例系数 a_x, a_y
更改为已知 a_x', a_y'
,条件如下:您希望光标指向 R
中的相同坐标 (x_c, y_c)
。 IE。 T'(x_c, y_c) = T(x_c, y_c)
— 相对坐标中的相同点指向绝对坐标中的相同位置。我们推导出一个具有已知剩余值的未知偏移量 b_x', b_y'
的系统。它给出
最后的工作是从小部件光标位置 (x_p, y_p) = T(x_c, y_c)
:
(x_c, y_c)
并替换它:
用你的话说就是
mOffset = event->pos() - float(mZoomLevel) / float(oldZoomLevel) *
(event->pos() - mOffset);
如果您想像 Google 地图那样缩放,那么您的原点必须位于图像的 top-left 角(比方说 (x,y) = (0,0) 和(宽度, height) = (100,100)) 初始 zoomLevel 100。 如果你想在点 (40,20) 以 5% 的缩放因子缩放,那么, 位移可以计算为-
newX = 40 - 40*(100.0/105)
newY = 20 - 20*(100.0/105)
newWidth = width - (100.0/105)
newHeight = height - (100.0/105)
然后将newX、newY设置为原点,并将宽度、高度更改为newWidth和newHeight。 通过此实现,您将能够缩放光标所在的特定点。但是当您将光标移动到其他一些位置时,此实现将不起作用。 我也在寻找那个实现。