Вычисление смещения вида для увеличения в позиции курсора мыши

У меня есть "холст", на который пользователь может рисовать пиксели и т. Д. Это работает хорошо, но моя функция масштабирования в настоящее время использует один и тот же источник независимо от положения мыши. Я хотел бы реализовать функциональность, подобную функции масштабирования в Картах Google:

Google Maps увеличить

То есть, источником увеличения всегда должна быть позиция курсора мыши.

То, что у меня сейчас есть, не совсем верно...

Мои попытки в основном были ударами в темноте, но я также пытался использовать код из этого ответа безуспешно.

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://stackru.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() функционировать?

1 ответ

Решение

У вас есть прямоугольник R = [x_0, x_0 + w] x [y_0, y_0 + h] с абсолютными координатами. Когда вы отображаете его в виджет (другой прямоугольник), вы применяете некоторые преобразования T в область W из R, Это преобразование является линейным со смещением:

T (x, y) = (a_x x + b_x, a_y y + b_y).

Значения a_x, b_x, a_y, b_y рассчитаны на удовлетворение некоторых простых условий, вы уже сделали это.

У вас также есть курсор (x_c, y_c) в R, Это координаты в W являются T(x_c, y_c), Теперь вы хотите применить другое преобразование T '\ двоеточие R \ rightarrow W',

T '(x, y) = (a_x' x + b_x ', a_y' y + b_y ')

изменение масштабных коэффициентов a_x, a_y к известному a_x', a_y' со следующим условием: вы хотите, чтобы курсор указывал на те же координаты (x_c, y_c) в R, Т.е. T'(x_c, y_c) = T(x_c, y_c) - одна и та же точка в относительных координатах указывает на одну и ту же позицию в абсолютных координатах. Выведем систему для неизвестных смещений b_x', b_y' с известными значениями покоя. Это дает

b_z '= z_c (a_z - a_z') + b_z, z = x, y.

Последняя работа, чтобы найти (x_c, y_c) из позиции курсора виджета (x_p, y_p) = T(x_c, y_c):

z_c = (z_p - b_z) / a_z, z = x, y

и заменить его:

b_z '= z_p - a_z' / a_z (z_p - b_z), \ quad z = x, y.

В твоих терминах это

mOffset = event->pos() - float(mZoomLevel) / float(oldZoomLevel) *
     (event->pos() - mOffset);

Если вы хотите масштабировать, как карты Google, ваше начало координат должно быть в верхнем левом углу изображения (скажем, (x,y) = (0,0) и (width, 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. Благодаря этой реализации вы сможете увеличивать масштаб в определенной точке, где находится курсор. Но эта реализация не будет работать, когда вы переместите курсор в другие позиции. Я тоже ищу эту реализацию.

Другие вопросы по тегам