Viewer: one overlay rebuild per pan/zoom, and only invalidate the image when it changed

Panning called updateOverlay() three times per mouse move: once for each
scrollbar's valueChanged -> onScroll(), then once explicitly. Zooming was the
same. Every one of those tore down and rebuilt every overlay item.

Suppress onScroll() for the duration of the gesture instead, and let the
gesture do its single rebuild at the end. Note this cannot be done by blocking
the scrollbars' signals: QAbstractScrollArea drives the actual scrolling off
valueChanged, so blocking it would stop the view moving at all.

updateOverlay() also refreshed the image item unconditionally, which marks the
whole item dirty and forces a full-viewport repaint even though pan and zoom
never change the pixels. Track whether RenderImage has run since the last
refresh and skip it otherwise.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-26 19:03:05 +02:00
co-authored by Claude Opus 5
parent fd9f93e1f1
commit c3eba650e9
2 changed files with 27 additions and 2 deletions
+23 -2
View File
@@ -64,6 +64,8 @@ JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) {
}
void JFJochImage::onScroll(int value) {
if (suppress_overlay_update_)
return;
updateOverlay();
}
@@ -134,6 +136,10 @@ void JFJochImage::wheelEvent(QWheelEvent *event) {
changeForeground(new_foreground);
emit foregroundChanged(foreground);
} else {
// Zooming and re-centering both move the scrollbars, and every move reaches
// onScroll(); suppress those and rebuild the overlay once, below.
suppress_overlay_update_ = true;
// Perform zooming
if (event->angleDelta().y() > 0) {
if (scale_factor * zoomFactor < 500.0) {
@@ -152,6 +158,8 @@ void JFJochImage::wheelEvent(QWheelEvent *event) {
QPointF delta = targetScenePos - updatedViewportCenter;
translate(delta.x(), delta.y()); // Shift the view
suppress_overlay_update_ = false;
updateOverlay();
emitViewportChanged();
}
@@ -248,8 +256,12 @@ void JFJochImage::mouseMoveEvent(QMouseEvent *event) {
const QPoint viewDelta = event->pos() - lastMousePos;
lastMousePos = event->pos();
// Each setValue() reaches onScroll(), so the overlay was rebuilt three times per
// mouse move. Suppress those and rebuild once, below.
suppress_overlay_update_ = true;
horizontalScrollBar()->setValue(horizontalScrollBar()->value() - viewDelta.x());
verticalScrollBar()->setValue(verticalScrollBar()->value() - viewDelta.y());
suppress_overlay_update_ = false;
updateOverlay();
emitViewportChanged();
@@ -760,6 +772,8 @@ void JFJochImage::RenderImage() {
scanLine[x] = qRgb(c.r, c.g, c.b);
}
});
image_dirty_ = true;
}
void JFJochImage::centerOnSpot(QPointF point) {
@@ -779,9 +793,12 @@ void JFJochImage::applyViewport(QTransform transform, QPointF center) {
if (m_applyingViewport || !scene())
return;
m_applyingViewport = true;
// As in wheelEvent: one rebuild, not one per scrollbar move
suppress_overlay_update_ = true;
setTransform(transform);
scale_factor = transform.m11();
centerOn(center);
suppress_overlay_update_ = false;
updateOverlay();
m_applyingViewport = false;
}
@@ -882,13 +899,17 @@ void JFJochImage::updateOverlay() {
qDeleteAll(overlay_items_);
overlay_items_.clear();
// Ensure the image item exists and is up-to-date
// Ensure the image item exists and is up-to-date. Refreshing it marks the whole item
// dirty, which forces a full repaint of the viewport, so only do it when the image
// really changed - not on every pan and zoom.
if (!image_item_) {
image_item_ = new JFJochImageItem(qimg_buffer_);
image_item_->setZValue(0);
scene()->addItem(image_item_);
} else {
image_dirty_ = false;
} else if (image_dirty_) {
image_item_->refresh();
image_dirty_ = false;
}
if (scale_factor > 30.0)
+4
View File
@@ -124,6 +124,10 @@ protected:
// instead of queueing a full recolour per event.
void ScheduleRenderImage();
bool render_pending_ = false;
bool image_dirty_ = false; // qimg_buffer_ changed since the item was last refreshed
// Set while a pan/zoom moves the scrollbars, so onScroll() does not rebuild the overlay
// once per scrollbar; the gesture rebuilds it once itself.
bool suppress_overlay_update_ = false;
void Redraw();
void CalcROI();