计算在鼠标光标位置放大的视图偏移

Posted

技术标签:

【中文标题】计算在鼠标光标位置放大的视图偏移【英文标题】:Calculating view offset for zooming in at the position of the mouse cursor 【发布时间】:2016-07-09 12:20:45 【问题描述】:

我有一个“画布”,用户可以在上面绘制像素等。它运作良好,但我的缩放功能目前使用相同的原点,而不管鼠标的位置。我想实现类似于谷歌地图缩放行为的功能:

也就是说,缩放的原点应该始终是鼠标光标的位置。

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 https://***.com/a/14085161/904422
//            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() 函数中做错了什么?

【问题讨论】:

你期待什么,你看到了什么?为什么不是 Qt 图形框架? @ilotXXI 如果您花时间阅读,所有信息都在问题中。 哦,找到了一个 GIF。试试这个:mOffset = event-&gt;pos() - float(mZoomLevel) / float(oldZoomLevel) * (event-&gt;pos() - mOffset);. @ilotXXI 非常感谢!效果很好! :) 请回答,以便我接受。如果可能的话,如果你能解释一下代码在做什么,那就太好了。 完成。如果难以理解或有错误,请告诉我。 【参考方案1】:

您有一个带有绝对坐标的矩形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);

【讨论】:

我的数学很差劲,但这对于不懂数学的人来说似乎很好解释。 :D【参考方案2】:

如果你想像谷歌地图一样缩放,那么你的原点必须在图像的左上角(比如说 (x,y) = (0,0) 和 (width, height) = (100,100))初始缩放级别 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。 通过这种实现,您将能够缩放光标所在的特定点。但是当您将光标移动到其他一些位置时,此实现将不起作用。 我也在寻找那个实现。

【讨论】:

以上是关于计算在鼠标光标位置放大的视图偏移的主要内容,如果未能解决你的问题,请参考以下文章

【缩放】实现svg以鼠标为焦点缩放

缩放画布到鼠标光标

c# winform中如何实现跟随光标的放大镜

使用 glm 根据光标位置旋转模型视图矩阵

verdi使用

我可以在 ContentEditable 中找到光标的像素位置吗?