Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I'm attempting to implement mouse cursor centered zoom in/out on an image. For simplicity, assume I only need to zoom in/out in the horizontal axis. I am using Qt with QPainter to draw my scene and QWidget:: mouseWheelEvent override to calculate a zoom factor and a zoom position.

To put it into words before showing the code, the zoom factor, and zoom position define the transformation needed to transform from the original image into the zoomed image (to put it another way, I don't combine matrices or something similar, but calculate the absolute transformation in the paint event), as can be finally seen here:

void MyWidget::paintEvent(QPaintEvent* e)
{
    QPainter painter{this};

    painter.translate(m_zoomCenterX, 0.);
    painter.scale(m_zoomFactor, 1.);
    painter.translate(-m_zoomCenterX, 0.);

    // content margins are all 0 btw
    painter.drawImage(contentsRect(), m_image);
}

In the mouse wheel event, I try to calculate the scaled difference between the zoom center and the new position which I add to the zoom center. Zoom factor is computed simply by multiplying with a constant according to the angle delta. The final if-else branching does some boundaries checking (which is something I'd like to do so the image stays stretched in the view and doesn't move somewhere I don't want to):

void MyWidget::wheelEvent(QWheelEvent* e)
{
    const QRect rect = contentsRect();
    // m_zoomCenterX is first initialized to 0 in the MyWidget constructor, m_zoomFactor to 1.
    const qreal diff = (e->pos().x() - m_zoomCenterX) / m_zoomFactor;
    qDebug() << diff;

    m_zoomCenterX += diff;
    static constexpr const qreal ZOOM_IN_FACTOR = 1.1,
                                 ZOOM_OUT_FACTOR = .9;
    m_zoomFactor *= e->angleDelta().y() > 0. ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;

    if (m_zoomFactor < 1.)
    {
        m_zoomFactor = 1.;
        m_zoomCenterX = 0.;
    }
    else if (m_zoomFactor > 5.)
    {
        m_zoomFactor = 5.;
    }
    if (m_zoomCenterX < 0)
    {
        m_zoomCenter = 0;
    }
    else if (m_zoomCenterX >= rect.width())
    {
        m_zoomCenterX = rect.width() - 1;
    }

    update();
}

This doesn't seem to work once I try to zoom in/out after let's say zooming in (see picture:
see picture, sorry if it's not much), the zoom center position is likely not correctly computed but I'm not sure why. The qDebug line gives me non-zero differences after consecutive scrollings when the cursor is on a new position, but after the first scrolling it should be zero...

I guess it must be something trivial but I'm quite stuck at the moment.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
2.3k views
Welcome To Ask or Share your Answers For Others

1 Answer

Well I got it working. I use inverse matrix to compute the new zoom center, but once zoomed in, it will not match the mouse position, so the entire image must be translated additionally by the inverted_position - mouse_position. Here's the final code for any future wanderers. m_extraTranslation is obviously initialised 0 in the constructor.

QMatrix MyWidget::computeScaleMatrix() const noexcept
{
    QMatrix scaleMatrix{1., 0., 0., 1., 0., 0.};
    scaleMatrix.translate(-m_extraTranslation, 0.);
    scaleMatrix.translate(m_zoomCenter.x(), 0.);
    scaleMatrix.scale(m_zoomFactor, 1.);
    scaleMatrix.translate(-m_zoomCenter.x(), 0.);
    return scaleMatrix;
}

void MyWidget::wheelEvent(QWheelEvent* e)
{
    const QMatrix invScaleMatrix = computeScaleMatrix().inverted();
    const QPointF invPos = invScaleMatrix.map(e->pos());

    m_zoomCenter.setX(invPos.x());
    static constexpr const qreal ZOOM_IN_FACTOR = 1.1,
                                 ZOOM_OUT_FACTOR = .9;
    m_zoomFactor *= e->angleDelta().y() > 0. ? ZOOM_IN_FACTOR : ZOOM_OUT_FACTOR;

    m_extraTranslation = invPos.x() - e->pos().x();

    // here I'm making sure the image corners do not wander off inside the widget, this is necessary to do for zooming out
    const QMatrix scaleMatrix = computeScaleMatrix();
    const qreal startX = scaleMatrix.map(QPointF{0., 0.}).x(),
                endX = scaleMatrix.map(QPointF(contentsRect().width() - 1, 0.)).x();

    if (startX > 0.)
    {
        m_extraTranslation += startX;
    }
    else if (endX < contentsRect().width() - 1)
    {
        m_extraTranslation -= contentsRect().width() - endX - 1;
    }

    if (m_zoomFactor < 1.)
    {
        m_zoomFactor = 1.;
        m_zoomCenter = {0, 0};
        m_extraTranslation = 0;
    }
    else if (m_zoomFactor > 5.)
    {
        m_zoomFactor = 5.;
    }
    if (m_zoomCenter.x() < 0)
    {
        m_zoomCenter.setX(0);
    }
    else if (m_zoomCenter.x() >= contentsRect().width())
    {
        m_zoomCenter.setX(contentsRect().width() - 1);
    }

    update();
    e->accept();
}

void MyWidget::paintEvent(QPaintEvent* e)
{
    QPainter painter{this};
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::SmoothPixmapTransform);

    painter.setMatrix(computeScaleMatrix()));

    painter.drawImage(contentsRect(), m_image);
}

Extending this for the Y axis should be trivial since it would follow the same logic.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...