From 9ab4267939b4cb0a149f5c3e4c84af1815995920 Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Fri, 7 Nov 2025 16:49:02 +0100 Subject: [PATCH] jfjoch_viewer: code for image viewer is nicer, but it doesn't work :) --- viewer/widgets/JFJochDiffractionImage.cpp | 30 +----- viewer/widgets/JFJochDiffractionImage.h | 5 +- viewer/widgets/JFJochImage.cpp | 107 ++++++++++++++++++++++ viewer/widgets/JFJochImage.h | 6 +- viewer/widgets/JFJochSimpleImage.cpp | 74 +-------------- viewer/widgets/JFJochSimpleImage.h | 4 - 6 files changed, 114 insertions(+), 112 deletions(-) diff --git a/viewer/widgets/JFJochDiffractionImage.cpp b/viewer/widgets/JFJochDiffractionImage.cpp index e90fbfdf..48c3406e 100644 --- a/viewer/widgets/JFJochDiffractionImage.cpp +++ b/viewer/widgets/JFJochDiffractionImage.cpp @@ -304,20 +304,6 @@ void JFJochDiffractionImage::DrawTopPixels() { iter->second / image->Dataset().experiment.GetXPixelsNum() + 0.5, 15, 3); } -void JFJochDiffractionImage::updateROI() { - if (roi_type == RoiType::RoiBox) { - QRect roiBox_int = QRectF(RoundPoint(roiStartPos), RoundPoint(roiEndPos)).normalized().toRect(); - roiBox= roiBox_int; - emit roiBoxUpdated(roiBox_int); - } else { - QPointF delta = roiStartPos - roiEndPos; - double radius = std::sqrt(delta.x() * delta.x() + delta.y() * delta.y()); - roiBox= QRectF(roiStartPos.x() - radius, roiStartPos.y() - radius, - 2 * radius, 2 * radius).normalized(); - emit roiCircleUpdated(roiStartPos.x(), roiStartPos.y(), radius); - } - updateOverlay(); -} void JFJochDiffractionImage::updateOverlay() { if (!scene() || !image) return; @@ -378,21 +364,7 @@ void JFJochDiffractionImage::updateOverlay() { if (show_saturation) DrawSaturation(); - if (roiBox.width() * roiBox.height() > 0) { - QPen pen(feature_color, 3); - pen.setStyle(Qt::DashLine); - pen.setCosmetic(true); - if (roi_type == RoiType::RoiBox) { - scene()->addRect(roiBox, pen); - } else { - scene()->addEllipse(roiBox, pen); - - double pointRadius = 0.5; - QRectF pointBoundingBox = QRectF(roiStartPos.x() - pointRadius, roiStartPos.y() - pointRadius, - 2 * pointRadius, 2 * pointRadius); - scene()->addEllipse(pointBoundingBox, pen); - } - } + DrawROI(); } void JFJochDiffractionImage::Redraw() { diff --git a/viewer/widgets/JFJochDiffractionImage.h b/viewer/widgets/JFJochDiffractionImage.h index 675e5075..3e44029d 100644 --- a/viewer/widgets/JFJochDiffractionImage.h +++ b/viewer/widgets/JFJochDiffractionImage.h @@ -17,14 +17,11 @@ Q_OBJECT public: JFJochDiffractionImage(QWidget *parent = nullptr); -signals: - void roiBoxUpdated(QRect box); - void roiCircleUpdated(double x, double y, double radius); + private: void updateOverlay() override; void Redraw() override; - void updateROI() override; void LoadImageInternal(); void DrawResolutionRings(); diff --git a/viewer/widgets/JFJochImage.cpp b/viewer/widgets/JFJochImage.cpp index 28525b70..a2a82fc7 100644 --- a/viewer/widgets/JFJochImage.cpp +++ b/viewer/widgets/JFJochImage.cpp @@ -305,3 +305,110 @@ JFJochImage::hitTestROIHandle(const QPointF& scenePos, qreal tol) const { if (r.contains(scenePos)) return ResizeHandle::Inside; return ResizeHandle::None; } + +void JFJochImage::updateROI() { + if (roi_type == RoiType::RoiBox) { + QRect roiBox_int = QRectF(RoundPoint(roiStartPos), RoundPoint(roiEndPos)).normalized().toRect(); + roiBox= roiBox_int; + emit roiBoxUpdated(roiBox_int); + } else { + QPointF delta = roiStartPos - roiEndPos; + double radius = std::sqrt(delta.x() * delta.x() + delta.y() * delta.y()); + roiBox= QRectF(roiStartPos.x() - radius, roiStartPos.y() - radius, + 2 * radius, 2 * radius).normalized(); + emit roiCircleUpdated(roiStartPos.x(), roiStartPos.y(), radius); + } + updateOverlay(); +} + +void JFJochImage::DrawROI() { + if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0) return; + + auto scn = scene(); + if (!scn) + return; + + QPen pen(feature_color, 2); + pen.setStyle(Qt::DashLine); + pen.setCosmetic(true); + + const qreal f = std::clamp(scale_factor, 0.5, 50.0); + const qreal handleSize = 3.0 / std::sqrt(std::max(1e-4, f)); + + if (roi_type == RoiType::RoiCircle) { + // Draw circle + scn->addEllipse(roiBox, pen); + + // A single handle on the circle at the rightmost point + const QPointF c = roiBox.center(); + const qreal rad = 0.5 * (roiBox.width() + roiBox.height()) * 0.5; // average, should be equal + QPointF hpos = QPointF(roiBox.right(), c.y()); + scn->addRect(QRectF(hpos.x() - handleSize, hpos.y() - handleSize, 2 * handleSize, 2 * handleSize), + QPen(feature_color, 1), QBrush(feature_color)); + + // On hover near perimeter: draw in/out arrows along radius at handle + if (hover_handle_ != ResizeHandle::None && hover_handle_ != ResizeHandle::Inside) { + QPen apen(feature_color, 1); + apen.setCosmetic(true); + const qreal arrowLen = 8.0 / std::sqrt(std::max(1e-4, f)); + // Outward arrow + scn->addLine(QLineF(c, c + QPointF(rad + arrowLen, 0)), apen); + // Inward arrow + scn->addLine(QLineF(c, c + QPointF(rad - arrowLen, 0)), apen); + } + } else { + // Box + scn->addRect(roiBox, pen); + + // Corner handles + auto addHandle = [&](const QPointF& p) { + scn->addRect(QRectF(p.x() - handleSize, p.y() - handleSize, 2 * handleSize, 2 * handleSize), + QPen(feature_color, 1), QBrush(feature_color)); + }; + addHandle(roiBox.topLeft()); + addHandle(roiBox.topRight()); + addHandle(roiBox.bottomLeft()); + addHandle(roiBox.bottomRight()); + + // On hover over a resizable edge/corner: draw small arrows indicating resize direction + if (hover_handle_ != ResizeHandle::None && hover_handle_ != ResizeHandle::Inside) { + QPen apen(feature_color, 1); + apen.setCosmetic(true); + const qreal arrowLen = 6.0 / std::sqrt(std::max(1e-4, f)); + const qreal off = 10.0 / std::sqrt(std::max(1e-4, f)); + auto drawArrow = [&](const QPointF& a, const QPointF& b) { + scn->addLine(QLineF(a, b), apen); + }; + const QRectF r = roiBox; + switch (hover_handle_) { + case ResizeHandle::Left: + drawArrow(QPointF(r.left(), r.center().y() - off), QPointF(r.left() - arrowLen, r.center().y() - off)); + drawArrow(QPointF(r.left(), r.center().y() + off), QPointF(r.left() - arrowLen, r.center().y() + off)); + break; + case ResizeHandle::Right: + drawArrow(QPointF(r.right(), r.center().y() - off), QPointF(r.right() + arrowLen, r.center().y() - off)); + drawArrow(QPointF(r.right(), r.center().y() + off), QPointF(r.right() + arrowLen, r.center().y() + off)); + break; + case ResizeHandle::Top: + drawArrow(QPointF(r.center().x() - off, r.top()), QPointF(r.center().x() - off, r.top() - arrowLen)); + drawArrow(QPointF(r.center().x() + off, r.top()), QPointF(r.center().x() + off, r.top() - arrowLen)); + break; + case ResizeHandle::Bottom: + drawArrow(QPointF(r.center().x() - off, r.bottom()), QPointF(r.center().x() - off, r.bottom() + arrowLen)); + drawArrow(QPointF(r.center().x() + off, r.bottom()), QPointF(r.center().x() + off, r.bottom() + arrowLen)); + break; + case ResizeHandle::TopLeft: + case ResizeHandle::TopRight: + case ResizeHandle::BottomLeft: + case ResizeHandle::BottomRight: + // For corners, show arrows on both axes (simple version) + drawArrow(QPointF(r.right(), r.center().y()), QPointF(r.right() + arrowLen, r.center().y())); + drawArrow(QPointF(r.left(), r.center().y()), QPointF(r.left() - arrowLen, r.center().y())); + drawArrow(QPointF(r.center().x(), r.top()), QPointF(r.center().x(), r.top() - arrowLen)); + drawArrow(QPointF(r.center().x(), r.bottom()), QPointF(r.center().x(), r.bottom() + arrowLen)); + break; + default: break; + } + } + } +} diff --git a/viewer/widgets/JFJochImage.h b/viewer/widgets/JFJochImage.h index cc5f1635..a37ca868 100644 --- a/viewer/widgets/JFJochImage.h +++ b/viewer/widgets/JFJochImage.h @@ -12,7 +12,7 @@ class JFJochImage : public QGraphicsView { virtual void updateOverlay() = 0; virtual void Redraw() = 0; - virtual void updateROI() = 0; + void updateROI(); void wheelEvent(QWheelEvent* event) override; void resizeEvent(QResizeEvent *event) override; @@ -22,7 +22,7 @@ class JFJochImage : public QGraphicsView { void mouseReleaseEvent(QMouseEvent *event) override; protected: virtual void mouseHover(QMouseEvent* event) = 0; - + void DrawROI(); double scale_factor = 1.0; QColor feature_color = Qt::magenta; @@ -54,6 +54,8 @@ signals: void foregroundChanged(float v); void backgroundChanged(float v); void writeStatusBar(QString string, int timeout_ms = 0); + void roiBoxUpdated(QRect box); + void roiCircleUpdated(double x, double y, double radius); private slots: void onScroll(int value); diff --git a/viewer/widgets/JFJochSimpleImage.cpp b/viewer/widgets/JFJochSimpleImage.cpp index 8af8a989..a7ef725a 100644 --- a/viewer/widgets/JFJochSimpleImage.cpp +++ b/viewer/widgets/JFJochSimpleImage.cpp @@ -198,7 +198,7 @@ void JFJochSimpleImage::updateOverlay() { } } - drawROI(scene()); + DrawROI(); } template @@ -236,78 +236,6 @@ void JFJochSimpleImage::renderImage(QImage &qimg, const uint8_t *input) { } } - -void JFJochSimpleImage::updateROI() { - // Normalize box if drawing - if (mouse_event_type == MouseEventType::DrawingROI) { - roiBox = QRectF(roiStartPos, roiEndPos).normalized(); - } - - // Clamp ROI to image bounds - if (has_image_) { - const QRectF bounds(0.0, 0.0, - image_->image.GetWidth(), - image_->image.GetHeight()); - roiBox = roiBox.intersected(bounds); - } - - scheduleSceneUpdate(); -} - -void JFJochSimpleImage::drawROI(QGraphicsScene* scn) { - if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0) return; - - QPen pen(feature_color, 2); - pen.setStyle(Qt::DashLine); - pen.setCosmetic(true); - scn->addRect(roiBox, pen); - - // Small corner handles - const qreal handleSize = 3.0 / std::sqrt(std::max(1e-4, scale_factor)); - auto addHandle = [&](const QPointF& p) { - scn->addRect(QRectF(p.x() - handleSize, p.y() - handleSize, 2 * handleSize, 2 * handleSize), - QPen(feature_color, 1), QBrush(feature_color)); - }; - addHandle(roiBox.topLeft()); - addHandle(roiBox.topRight()); - addHandle(roiBox.bottomLeft()); - addHandle(roiBox.bottomRight()); - - // Compute stats inside integer pixel ROI - QRect roi_px = roiBox.toRect().normalized(); - ROIStats stats = computeROIStats(roi_px); - - if (stats.valid()) { - // Compose text - QString text = QString("min %1 max %2 avg %3 std %4 n=%5") - .arg(stats.min, 0, 'f', 2) - .arg(stats.max, 0, 'f', 2) - .arg(stats.avg, 0, 'f', 2) - .arg(stats.stddev, 0, 'f', 2) - .arg(qulonglong(stats.count)); - - // Choose background based on local luminance under top-left pixel of ROI - const int W = int(image_->image.GetWidth()); - const int H = int(image_->image.GetHeight()); - int px = std::clamp(roi_px.left(), 0, std::max(0, W - 1)); - int py = std::clamp(roi_px.top(), 0, std::max(0, H - 1)); - const rgb bgc = image_rgb[size_t(py) * size_t(W) + size_t(px)]; - const bool darkText = luminance(bgc) > 128.0; - - QFont font("Arial"); - const qreal f = std::clamp(scale_factor, 0.5, 50.0); - font.setPointSizeF(14.0 / std::sqrt(f)); - - auto* t = scn->addText(text, font); - t->setDefaultTextColor(darkText ? Qt::black : Qt::white); - t->setZValue(20.0); - - // Place near top-left of ROI, slightly above - QPointF pos = roiBox.topLeft() + QPointF(2.0, -16.0 / std::sqrt(f)); - t->setPos(pos); - } -} - JFJochSimpleImage::ROIStats JFJochSimpleImage::computeROIStats(const QRect& roi_px) const { ROIStats s; diff --git a/viewer/widgets/JFJochSimpleImage.h b/viewer/widgets/JFJochSimpleImage.h index b59b327b..c6b37a7c 100644 --- a/viewer/widgets/JFJochSimpleImage.h +++ b/viewer/widgets/JFJochSimpleImage.h @@ -35,7 +35,6 @@ private: void renderImage(); // builds pixmap_ from current image_ + settings void updateOverlay() override; // clears scene, adds pixmap + labels + ROI (if any) - void drawROI(QGraphicsScene* scn); // draw box and stats ROIStats computeROIStats(const QRect& roi_px) const; // Image and rendering state @@ -55,9 +54,6 @@ private: template void renderImage(QImage &qimg, const uint8_t *input); - // Helpers - void updateROI() override; // normalize and request repaint - bool auto_fgbg = true; void mouseHover(QMouseEvent* event) override;