Reputation: 361
I have a (so far) fairly simple QWidget (size of say 300 * 300) that I am using to display a large QPixmap (e.g 5184 * 3456). The key parts of the code read as follows:
void DSSImageWidget::resizeEvent(QResizeEvent* e)
{
QSize sz = e->size();
qreal hScale = (qreal)sz.width() / (m_pixmap.width() + 4);
qreal vScale = (qreal)sz.height() / (m_pixmap.height() + 4);
m_scale = std::min(hScale, vScale);
update();
Inherited::resizeEvent(e);
}
void DSSImageWidget::paintEvent(QPaintEvent* event)
{
QPainter painter;
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
//QPointF whereScaled = m_where / (m_zoom * m_scale);
//qDebug() << "m_where:" << m_where.x() << m_where.y();
//qDebug() << whereScaled.x() << " " << whereScaled.y();
//painter.translate(-m_where);
painter.scale(m_zoom*m_scale, m_zoom*m_scale);
//painter.translate(m_where);
painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap);
painter.setPen(QPen(QColor(255, 0, 0, alpha), 0.25, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
painter.setBrush(Qt::NoBrush);
painter.drawRect(QRectF(0, 0, m_pixmap.width(), m_pixmap.height()).adjusted(-2, -2, 2, 2));
painter.end();
}
void DSSImageWidget::wheelEvent(QWheelEvent* e)
{
qreal degrees = -e->angleDelta().y() / 8.0;
//
// If zooming in and zoom factor is currently 1.0
// then remember mouse location
//
if ((degrees > 0) && (m_zoom == 1.0))
{
m_where = e->position();
}
qreal steps = degrees / 60.0;
qreal factor = m_zoom * std::pow(1.125, steps);
m_zoom = std::clamp(factor, 1.0, 5.0);
update();
Inherited::wheelEvent(e);
}
This scales the pixmap about the window origin which is a good start, but it's not what I want. I want it to scale the pixmap so that the part of the image under the mouse pointer remains where it is and the image expands around that point.
I've played all sorts of tunes with the painter.translate() calls and the location I use to actually draw the pixmap, but I've so far failed with flying colours :).
Please could someone who knows how this stuff actually works put me out of my misery and tell me how I achieve my objective here?
Thank you
Upvotes: 1
Views: 174
Reputation: 361
After quite some grappling with the code I've finally solved this. Given that it wasn't dead simple, I'm posting the relevant portions of the code here in the hope it will assist someone else.
From the header:
typedef QWidget
Inherited;
private:
bool initialised;
qreal m_scale, m_zoom;
QPointF m_origin;
QPixmap & m_pixmap;
QPointF m_pointInPixmap;
inline bool mouseOverImage(QPointF loc)
{
qreal x = loc.x(), y = loc.y(), ox = m_origin.x(), oy = m_origin.y();
return (
(x >= ox) &&
(x <= ox + (m_pixmap.width() * m_scale)) &&
(y >= oy) &&
(y <= oy + (m_pixmap.height() * m_scale)));
};
And the source code:
DSSImageWidget::DSSImageWidget(QPixmap& p, QWidget* parent)
: QWidget(parent),
initialised(false),
m_scale(1.0),
m_zoom(1.0),
m_origin(0.0, 0.0),
m_pixmap(p),
m_pointInPixmap((m_pixmap.width() / 2), (m_pixmap.height() / 2))
{
setAttribute(Qt::WA_MouseTracking);
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}
void DSSImageWidget::resizeEvent(QResizeEvent* e)
{
QSize sz = e->size();
qreal pixWidth = m_pixmap.width();
qreal pixHeight = m_pixmap.height();
qreal hScale = (qreal)sz.width() / pixWidth;
qreal vScale = (qreal)sz.height() / pixHeight;
m_scale = std::min(hScale, vScale);
qreal xoffset = 0.0, yoffset = 0.0;
if ((pixWidth * m_scale) < sz.width())
{
xoffset = (sz.width() - (pixWidth * m_scale)) / 2.0;
}
if ((pixHeight * m_scale) < sz.height())
{
yoffset = (sz.height() - (pixHeight * m_scale)) / 2.0;
}
m_origin = QPointF(xoffset, yoffset);
update();
Inherited::resizeEvent(e);
}
void DSSImageWidget::paintEvent(QPaintEvent* event)
{
QPainter painter;
qDebug() << "pointInPixmap: " << m_pointInPixmap.x() << m_pointInPixmap.y();
//
// Now calcualate the rectangle we're interested in
//
qreal width = m_pixmap.width();
qreal height = m_pixmap.height();
qreal x = m_pointInPixmap.x();
qreal y = m_pointInPixmap.y();
QRectF sourceRect(
x - (x / m_zoom),
y - (y / m_zoom),
width / m_zoom,
height / m_zoom
);
qDebug() << "sourceRect: " << sourceRect.x() << sourceRect.y() << sourceRect.width() << sourceRect.height();
//
// Now calculate the rectangle that is the intersection of this rectangle and the pixmap's rectangle.
//
sourceRect &= m_pixmap.rect();
painter.begin(this);
painter.setRenderHint(QPainter::Antialiasing);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.translate(m_origin);
painter.scale(m_zoom*m_scale, m_zoom*m_scale);
painter.translate(-m_origin);
//painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap, sourceRect);
painter.drawPixmap(m_origin, m_pixmap, sourceRect);
painter.end();
}
#if QT_CONFIG(wheelevent)
void DSSImageWidget::wheelEvent(QWheelEvent* e)
{
qreal degrees = -e->angleDelta().y() / 8.0;
//
// If zooming in and zoom factor is currently 1.0
// then remember mouse location
//
if ((degrees > 0) && (m_zoom == 1.0))
{
QPointF mouseLocation = e->position();
if (mouseOverImage(mouseLocation))
{
m_pointInPixmap = QPointF((mouseLocation-m_origin) / m_scale);
}
else
{
m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
}
}
qreal steps = degrees / 60.0;
qreal factor = m_zoom * std::pow(1.125, steps);
m_zoom = std::clamp(factor, 1.0, 5.0);
if (degrees < 0 && m_zoom == 1.0)
{
m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
}
update();
Inherited::wheelEvent(e);
}
#endif
All the best, David
Upvotes: 1