From c3eba650e9d88ac357122b2b766fd6d63be1593f Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sun, 26 Jul 2026 19:03:05 +0200 Subject: [PATCH] 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) --- viewer/image_viewer/JFJochImage.cpp | 25 +++++++++++++++++++++++-- viewer/image_viewer/JFJochImage.h | 4 ++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/viewer/image_viewer/JFJochImage.cpp b/viewer/image_viewer/JFJochImage.cpp index f1452e44..1f7a9479 100644 --- a/viewer/image_viewer/JFJochImage.cpp +++ b/viewer/image_viewer/JFJochImage.cpp @@ -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) diff --git a/viewer/image_viewer/JFJochImage.h b/viewer/image_viewer/JFJochImage.h index 623f8125..7bf2f6e3 100644 --- a/viewer/image_viewer/JFJochImage.h +++ b/viewer/image_viewer/JFJochImage.h @@ -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();