diff --git a/viewer/widgets/JFJochDiffractionImage.cpp b/viewer/widgets/JFJochDiffractionImage.cpp index a9f54678..29288114 100644 --- a/viewer/widgets/JFJochDiffractionImage.cpp +++ b/viewer/widgets/JFJochDiffractionImage.cpp @@ -15,9 +15,6 @@ // Constructor JFJochDiffractionImage::JFJochDiffractionImage(QWidget *parent) : JFJochImage(parent) {} -QPointF RoundPoint(const QPointF &input) { - return QPointF(qRound(input.x()), qRound(input.y())); -} void JFJochDiffractionImage::mousePressEvent(QMouseEvent *event) { if (!scene() || !image) return; diff --git a/viewer/widgets/JFJochDiffractionImage.h b/viewer/widgets/JFJochDiffractionImage.h index 9511106b..63603ea6 100644 --- a/viewer/widgets/JFJochDiffractionImage.h +++ b/viewer/widgets/JFJochDiffractionImage.h @@ -29,8 +29,7 @@ private: void updateOverlay() override; void Redraw() override; - - void updateROI(); + void updateROI() override; void LoadImageInternal(); void DrawResolutionRings(); @@ -53,14 +52,6 @@ private: bool highlight_ice_rings = true; float ice_ring_width_Q_recipA = 0.01; - QPoint lastMousePos; // To track panning movement - - QPointF roiStartPos; - QPointF roiEndPos; - - enum class RoiType {RoiBox, RoiCircle}; - RoiType roi_type = RoiType::RoiBox; - public slots: void loadImage(std::shared_ptr image); void setAutoForeground(bool input); diff --git a/viewer/widgets/JFJochImage.cpp b/viewer/widgets/JFJochImage.cpp index c8908699..5e10314a 100644 --- a/viewer/widgets/JFJochImage.cpp +++ b/viewer/widgets/JFJochImage.cpp @@ -93,3 +93,7 @@ void JFJochImage::resizeEvent(QResizeEvent *event) { QGraphicsView::resizeEvent(event); updateOverlay(); } + +QPointF JFJochImage::RoundPoint(const QPointF &input) { + return QPointF(qRound(input.x()), qRound(input.y())); +} \ No newline at end of file diff --git a/viewer/widgets/JFJochImage.h b/viewer/widgets/JFJochImage.h index a9031a2b..7c940cb0 100644 --- a/viewer/widgets/JFJochImage.h +++ b/viewer/widgets/JFJochImage.h @@ -1,8 +1,7 @@ // SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only -#ifndef JFJOCH_JFJOCHIMAGE_H -#define JFJOCH_JFJOCHIMAGE_H +#pragma once #include #include "../../common/ColorScale.h" @@ -12,6 +11,7 @@ class JFJochImage : public QGraphicsView { virtual void updateOverlay() = 0; virtual void Redraw() = 0; + virtual void updateROI() = 0; void wheelEvent(QWheelEvent* event) override; void resizeEvent(QResizeEvent *event) override; @@ -26,6 +26,15 @@ protected: QPixmap pixmap; enum class MouseEventType {None, Panning, DrawingROI, MovingROI, ResizingROI}; MouseEventType mouse_event_type = MouseEventType::None; + enum class RoiType {RoiBox, RoiCircle}; + RoiType roi_type = RoiType::RoiBox; + QPoint lastMousePos; // To track panning movement + + QPointF roiStartPos; + QPointF roiEndPos; + QRectF roiBox; + + static QPointF RoundPoint(const QPointF& p); signals: void foregroundChanged(float v); @@ -42,5 +51,3 @@ public slots: public: explicit JFJochImage(QWidget *parent = nullptr); }; - -#endif //JFJOCH_JFJOCHIMAGE_H \ No newline at end of file diff --git a/viewer/widgets/JFJochSimpleImage.cpp b/viewer/widgets/JFJochSimpleImage.cpp index ccdd192b..cdc826f3 100644 --- a/viewer/widgets/JFJochSimpleImage.cpp +++ b/viewer/widgets/JFJochSimpleImage.cpp @@ -29,16 +29,21 @@ void JFJochSimpleImage::clear() { } void JFJochSimpleImage::setImage(std::shared_ptr img) { - image_ = std::move(img); - has_image_ = true; - renderImage(); - updateOverlay(); + if (img) { + image_ = std::move(img); + has_image_ = true; + Redraw(); + } else { + has_image_ = false; + image_.reset(); + if (scene()) + scene()->clear(); + } } void JFJochSimpleImage::Redraw() { if (has_image_) { renderImage(); - // Preserve current transform while updating updateOverlay(); } } @@ -55,24 +60,23 @@ void JFJochSimpleImage::mousePressEvent(QMouseEvent* event) { active_handle_ = hitTestROIHandle(scenePos, 4.0 / std::sqrt(std::max(1e-4, scale_factor))); if (active_handle_ != ResizeHandle::None && active_handle_ != ResizeHandle::Inside) { mouse_event_type = MouseEventType::ResizingROI; - roi_start_pos_ = roi_box_.topLeft(); - roi_end_pos_ = roi_box_.bottomRight(); + roiStartPos = roiBox.topLeft(); + roiEndPos = roiBox.bottomRight(); setCursor(Qt::SizeAllCursor); - } else if (roi_box_.contains(scenePos)) { + } else if (roiBox.contains(scenePos)) { mouse_event_type = MouseEventType::MovingROI; - last_mouse_pos_ = event->pos(); + lastMousePos = event->pos(); setCursor(Qt::ClosedHandCursor); } else { mouse_event_type = MouseEventType::DrawingROI; - roi_start_pos_ = RoundPoint(scenePos); - roi_end_pos_ = roi_start_pos_; + roiStartPos = RoundPoint(scenePos); + roiEndPos = roiStartPos; setCursor(Qt::CrossCursor); } } else { mouse_event_type = MouseEventType::Panning; - panning_ = true; setCursor(Qt::ClosedHandCursor); - last_mouse_pos_ = event->pos(); + lastMousePos = event->pos(); } } @@ -86,27 +90,27 @@ void JFJochSimpleImage::mouseMoveEvent(QMouseEvent* event) { switch (mouse_event_type) { case MouseEventType::Panning: { - const QPointF delta = mapToScene(event->pos()) - mapToScene(last_mouse_pos_); - last_mouse_pos_ = event->pos(); + const QPointF delta = mapToScene(event->pos()) - mapToScene(lastMousePos); + lastMousePos = event->pos(); translate(delta.x(), delta.y()); updateOverlay(); break; } case MouseEventType::DrawingROI: { - roi_end_pos_ = RoundPoint(scenePos); + roiEndPos = RoundPoint(scenePos); updateROI(); break; } case MouseEventType::MovingROI: { - const QPointF delta = mapToScene(event->pos()) - mapToScene(last_mouse_pos_); - last_mouse_pos_ = event->pos(); - roi_box_.translate(delta); + const QPointF delta = mapToScene(event->pos()) - mapToScene(lastMousePos); + lastMousePos = event->pos(); + roiBox.translate(delta); updateROI(); break; } case MouseEventType::ResizingROI: { // Modify the corresponding edges based on active_handle_ - QRectF r = roi_box_; + QRectF r = roiBox; switch (active_handle_) { case ResizeHandle::Left: r.setLeft(scenePos.x()); break; case ResizeHandle::Right: r.setRight(scenePos.x()); break; @@ -118,7 +122,7 @@ void JFJochSimpleImage::mouseMoveEvent(QMouseEvent* event) { case ResizeHandle::BottomRight:r.setBottom(scenePos.y()); r.setRight(scenePos.x()); break; default: break; } - roi_box_ = r.normalized(); + roiBox = r.normalized(); updateROI(); break; } @@ -151,10 +155,9 @@ void JFJochSimpleImage::mouseReleaseEvent(QMouseEvent* event) { if (event->button() == Qt::LeftButton) { if (mouse_event_type == MouseEventType::DrawingROI) { - roi_end_pos_ = RoundPoint(mapToScene(event->pos())); + roiEndPos = RoundPoint(mapToScene(event->pos())); updateROI(); } - panning_ = false; mouse_event_type = MouseEventType::None; active_handle_ = ResizeHandle::None; setCursor(Qt::ArrowCursor); @@ -331,17 +334,12 @@ void JFJochSimpleImage::renderImage(QImage &qimg, const uint8_t *input) { } } - -QPointF JFJochSimpleImage::RoundPoint(const QPointF& p) { - return QPointF(qRound(p.x()), qRound(p.y())); -} - JFJochSimpleImage::ResizeHandle JFJochSimpleImage::hitTestROIHandle(const QPointF& scenePos, qreal tol) const { - if (roi_box_.isNull() || roi_box_.width() <= 0 || roi_box_.height() <= 0) + if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0) return ResizeHandle::None; - const QRectF r = roi_box_; + const QRectF r = roiBox; const QPointF tl = r.topLeft(); const QPointF tr = r.topRight(); const QPointF bl = r.bottomLeft(); @@ -372,8 +370,8 @@ JFJochSimpleImage::hitTestROIHandle(const QPointF& scenePos, qreal tol) const { void JFJochSimpleImage::updateROI() { // Normalize box if drawing - if (mouse_event_type_ == MouseEventType::DrawingROI) { - roi_box_ = QRectF(roi_start_pos_, roi_end_pos_).normalized(); + if (mouse_event_type == MouseEventType::DrawingROI) { + roiBox = QRectF(roiStartPos, roiEndPos).normalized(); } // Clamp ROI to image bounds @@ -381,19 +379,19 @@ void JFJochSimpleImage::updateROI() { const QRectF bounds(0.0, 0.0, image_->image.GetWidth(), image_->image.GetHeight()); - roi_box_ = roi_box_.intersected(bounds); + roiBox = roiBox.intersected(bounds); } scheduleSceneUpdate(); } void JFJochSimpleImage::drawROI(QGraphicsScene* scn) { - if (roi_box_.isNull() || roi_box_.width() <= 0 || roi_box_.height() <= 0) return; + if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0) return; QPen pen(feature_color, 2); pen.setStyle(Qt::DashLine); pen.setCosmetic(true); - scn->addRect(roi_box_, pen); + scn->addRect(roiBox, pen); // Small corner handles const qreal handleSize = 3.0 / std::sqrt(std::max(1e-4, scale_factor)); @@ -401,13 +399,13 @@ void JFJochSimpleImage::drawROI(QGraphicsScene* scn) { scn->addRect(QRectF(p.x() - handleSize, p.y() - handleSize, 2 * handleSize, 2 * handleSize), QPen(feature_color, 1), QBrush(feature_color)); }; - addHandle(roi_box_.topLeft()); - addHandle(roi_box_.topRight()); - addHandle(roi_box_.bottomLeft()); - addHandle(roi_box_.bottomRight()); + addHandle(roiBox.topLeft()); + addHandle(roiBox.topRight()); + addHandle(roiBox.bottomLeft()); + addHandle(roiBox.bottomRight()); // Compute stats inside integer pixel ROI - QRect roi_px = roi_box_.toRect().normalized(); + QRect roi_px = roiBox.toRect().normalized(); ROIStats stats = computeROIStats(roi_px); if (stats.valid()) { @@ -436,7 +434,7 @@ void JFJochSimpleImage::drawROI(QGraphicsScene* scn) { t->setZValue(20.0); // Place near top-left of ROI, slightly above - QPointF pos = roi_box_.topLeft() + QPointF(2.0, -16.0 / std::sqrt(f)); + QPointF pos = roiBox.topLeft() + QPointF(2.0, -16.0 / std::sqrt(f)); t->setPos(pos); } } diff --git a/viewer/widgets/JFJochSimpleImage.h b/viewer/widgets/JFJochSimpleImage.h index 5d870a1c..17fa436b 100644 --- a/viewer/widgets/JFJochSimpleImage.h +++ b/viewer/widgets/JFJochSimpleImage.h @@ -55,10 +55,6 @@ private: std::vector image_values_; // original scalar values per pixel (for labels and min/max) - // View/interaction - QPoint last_mouse_pos_; - bool panning_ = false; - // Smooth movement/repaint QTimer repaint_timer_; QPointF pan_accum_{0.0, 0.0}; @@ -70,18 +66,14 @@ private: template void renderImage(QImage &qimg, const uint8_t *input); - QRectF roi_box_; // in scene/image pixel coordinates - QPointF roi_start_pos_; // for drawing/resizing - QPointF roi_end_pos_; // for drawing/resizing - // Resizing which edge/corner enum class ResizeHandle { None, Left, Right, Top, Bottom, TopLeft, TopRight, BottomLeft, BottomRight, Inside }; ResizeHandle active_handle_ = ResizeHandle::None; // Helpers - static QPointF RoundPoint(const QPointF& p); + ResizeHandle hitTestROIHandle(const QPointF& scenePos, qreal tol = 3.0) const; - void updateROI(); // normalize and request repaint + void updateROI() override; // normalize and request repaint bool auto_fgbg = true; }; diff --git a/viewer/windows/JFJochCalibrationWindow.cpp b/viewer/windows/JFJochCalibrationWindow.cpp index c7b6c36c..e5a76124 100644 --- a/viewer/windows/JFJochCalibrationWindow.cpp +++ b/viewer/windows/JFJochCalibrationWindow.cpp @@ -42,10 +42,16 @@ JFJochCalibrationWindow::JFJochCalibrationWindow(QWidget *parent) : JFJochHelper grid_layout->addWidget(viewer, 3, 0, 1, 2); connect(viewer, &JFJochSimpleImage::backgroundChanged, - [this] (float val) {background_slider->setValue(val);}); + [this] (float val) { + QSignalBlocker blocker(background_slider); + background_slider->setValue(val); + }); connect(viewer, &JFJochSimpleImage::foregroundChanged, - [this] (float val) {foreground_slider->setValue(val);}); + [this] (float val) { + QSignalBlocker blocker(foreground_slider); + foreground_slider->setValue(val); + }); connect(background_slider, &SliderPlusBox::valueChanged, viewer, &JFJochSimpleImage::changeBackground); connect(foreground_slider, &SliderPlusBox::valueChanged, viewer, &JFJochSimpleImage::changeForeground);