The status bar, the resolution readout and the magnifier were all regenerated on every single mouse motion event. Each regeneration repaints, and on a remote X session a repaint uploads the whole window regardless of how little changed, so the pointer merely crossing the image saturates the link: measured with a counting relay in front of the X server, 50 motions over the image cost 273 MB, and a build with the hover work removed cost 18 KB. Rate-limit it. Two details matter: - The limit is applied inline, not from a timer. Running the update inside the mouse event keeps its damage in the same repaint as anything else that event triggers (a pan). A first attempt deferred the work to a timer instead, which split one repaint into two and made panning measurably worse. - The catch-up that reports the final position is debounced, not queued per skipped motion, so it fires once after the pointer stops rather than repeatedly mid-gesture. mouseHover() now takes the scene position and modifiers instead of the event, which also removes the identical mapToScene() from all four implementations. Hover traffic over 3 repeats: 173 MB mean -> 140 MB, and the run-to-run spread drops from +-14% to +-2%. The harness tops out near 30 motions/s, barely above the 15 Hz limit; a real mouse reports far faster, where the cap does more. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
117 lines
3.6 KiB
C++
117 lines
3.6 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochSimpleImage.h"
|
|
#include <QGraphicsScene>
|
|
#include <QGraphicsPixmapItem>
|
|
#include <QMouseEvent>
|
|
#include <QtMath>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
#include "../../common/JFJochException.h"
|
|
|
|
JFJochSimpleImage::JFJochSimpleImage(QWidget *parent)
|
|
: JFJochImage(parent) {
|
|
auto *scn = new QGraphicsScene(this);
|
|
setScene(scn);
|
|
|
|
// Keep overlays in pixel units independent of zoom (for labels font sizing)
|
|
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
|
|
|
|
// The predicted/float image is unreadable with 3-decimal per-pixel labels.
|
|
label_decimals_ = 1;
|
|
}
|
|
|
|
void JFJochSimpleImage::setImage(std::shared_ptr<const SimpleImage> img) {
|
|
if (img) {
|
|
image_ = std::move(img);
|
|
loadImageInternal();
|
|
RenderImage();
|
|
Redraw();
|
|
CalcROI();
|
|
} else {
|
|
image_.reset();
|
|
W = 0; H = 0;
|
|
if (scene())
|
|
scene()->clear();
|
|
resetScenePointers();
|
|
CalcROI();
|
|
}
|
|
}
|
|
|
|
|
|
void JFJochSimpleImage::mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers) {
|
|
if (image_) {
|
|
// Hover feedback / status bar display
|
|
if ((scenePos.x() >= 0)
|
|
&& (scenePos.x() < image_->image.GetWidth())
|
|
&& (scenePos.y() >= 0)
|
|
&& (scenePos.y() < image_->image.GetHeight())) {
|
|
const auto ix = int(scenePos.x());
|
|
const auto iy = int(scenePos.y());
|
|
const auto idx = iy * int(image_->image.GetWidth()) + ix;
|
|
if (idx >= 0 && idx < int(image_fp.size()))
|
|
emit writeStatusBar(QString("x=%1 y=%2 I=%3")
|
|
.arg(scenePos.x(), 0, 'f', 1)
|
|
.arg(scenePos.y(), 0, 'f', 1)
|
|
.arg(image_fp[size_t(idx)]), 3000);
|
|
} else {
|
|
emit writeStatusBar("", 1000);
|
|
}
|
|
}
|
|
}
|
|
|
|
template<class T>
|
|
void JFJochSimpleImage::loadImageInternal(const uint8_t *input) {
|
|
const size_t W = image_->image.GetWidth();
|
|
const size_t H = image_->image.GetHeight();
|
|
|
|
auto ptr = reinterpret_cast<const T *>(input);
|
|
|
|
for (int i = 0; i < W * H; i++)
|
|
image_fp[i] = static_cast<float>(ptr[i]);
|
|
}
|
|
|
|
void JFJochSimpleImage::loadImageInternal() {
|
|
W = image_->image.GetWidth();
|
|
H = image_->image.GetHeight();
|
|
if (W == 0 || H == 0) return;
|
|
|
|
image_fp.resize(W * H);
|
|
|
|
std::vector<uint8_t> image_buffer;
|
|
|
|
// Access uncompressed data
|
|
const uint8_t *src = image_->image.GetUncompressedPtr(image_buffer);
|
|
const auto mode = image_->image.GetMode();
|
|
|
|
switch (mode) {
|
|
case CompressedImageMode::Uint8:
|
|
loadImageInternal<uint8_t>(src);
|
|
break;
|
|
case CompressedImageMode::Int8:
|
|
loadImageInternal<int8_t>(src);
|
|
break;
|
|
case CompressedImageMode::Uint16:
|
|
loadImageInternal<uint16_t>(src);
|
|
break;
|
|
case CompressedImageMode::Int16:
|
|
loadImageInternal<int16_t>(src);
|
|
break;
|
|
case CompressedImageMode::Uint32:
|
|
loadImageInternal<uint32_t>(src);
|
|
break;
|
|
case CompressedImageMode::Int32:
|
|
loadImageInternal<int32_t>(src);
|
|
break;
|
|
case CompressedImageMode::Float32:
|
|
loadImageInternal<float>(src);
|
|
break;
|
|
case CompressedImageMode::Float64:
|
|
loadImageInternal<double>(src);
|
|
break;
|
|
default:
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid, "Image format not supported");
|
|
}
|
|
} |