diff --git a/viewer/JFJochViewerWindow.cpp b/viewer/JFJochViewerWindow.cpp index 82fade34..56ea0a8f 100644 --- a/viewer/JFJochViewerWindow.cpp +++ b/viewer/JFJochViewerWindow.cpp @@ -400,6 +400,10 @@ JFJochViewerWindow::JFJochViewerWindow(QWidget *parent, bool dbus, const QString connect(viewer, &JFJochImage::frameRendered, magnifierWindow, [viewer, magnifierWindow] { magnifierWindow->setFrame(viewer->Frame()); }); + + // ... and the raw counts behind it, for the per-pixel labels. Stores a pointer, nothing more. + connect(this, &JFJochViewerWindow::imageReady, + magnifierWindow, &JFJochHelperWindow::imageLoaded); connect(viewer, &JFJochImage::hoverScenePos, magnifierWindow, &JFJochMagnifierWindow::centerAt); diff --git a/viewer/image_viewer/JFJochFollowerImage.cpp b/viewer/image_viewer/JFJochFollowerImage.cpp index 83d92126..95761db5 100644 --- a/viewer/image_viewer/JFJochFollowerImage.cpp +++ b/viewer/image_viewer/JFJochFollowerImage.cpp @@ -3,12 +3,26 @@ #include "JFJochFollowerImage.h" #include "JFJochImage.h" +#include "../../common/ColorScale.h" #include +#include #include +#include #include +// Same wording as the main view's per-pixel labels +static QString PixelValueText(int32_t value) { + if (value == GAP_PXL_VALUE) + return QStringLiteral("Gap"); + if (value == ERROR_PXL_VALUE) + return QStringLiteral("Err"); + if (value == SATURATED_PXL_VALUE) + return QStringLiteral("Sat"); + return QString::number(value); +} + JFJochFollowerImage::JFJochFollowerImage(QWidget *parent) : QGraphicsView(parent) { setScene(new QGraphicsScene(this)); setTransformationAnchor(QGraphicsView::AnchorViewCenter); @@ -43,6 +57,10 @@ void JFJochFollowerImage::SetFrame(std::shared_ptr frame) { viewport()->update(); } +void JFJochFollowerImage::SetPixelValues(std::shared_ptr image) { + values_ = std::move(image); +} + void JFJochFollowerImage::CenterAt(QPointF scenePos) { if (!frame_ || frame_->isNull()) return; @@ -58,3 +76,47 @@ void JFJochFollowerImage::wheelEvent(QWheelEvent *event) { setTransform(QTransform::fromScale(zoom_, zoom_)); centerOn(center); } + +void JFJochFollowerImage::drawForeground(QPainter *painter, const QRectF &rect) { + QGraphicsView::drawForeground(painter, rect); + + if (zoom_ < kLabelZoom || !values_ || !frame_ || frame_->isNull()) + return; + + const int W = frame_->width(); + const int H = frame_->height(); + const auto &pixels = values_->Image(); + if (static_cast(pixels.size()) < static_cast(W) * H) + return; // values belong to a different frame + + const QRectF visible = mapToScene(viewport()->rect()).boundingRect(); + const int x0 = std::max(0, static_cast(std::floor(visible.left()))); + const int x1 = std::min(W, static_cast(std::ceil(visible.right()))); + const int y0 = std::max(0, static_cast(std::floor(visible.top()))); + const int y1 = std::min(H, static_cast(std::ceil(visible.bottom()))); + if (x1 <= x0 || y1 <= y0 || (x1 - x0) * (y1 - y0) > kMaxLabels) + return; + + // Lay the text out in viewport pixels so it stays a constant, readable size + painter->save(); + painter->resetTransform(); + + QFont font("DejaVu Sans Mono"); + font.setStyleHint(QFont::TypeWriter); + font.setPixelSize(std::clamp(static_cast(zoom_ * 0.3), 7, 16)); + painter->setFont(font); + + for (int y = y0; y < y1; y++) { + for (int x = x0; x < x1; x++) { + const QRect cell = mapFromScene(QRectF(x, y, 1, 1)).boundingRect(); + const QRgb c = frame_->pixel(x, y); + const rgb col{.r = static_cast(qRed(c)), + .g = static_cast(qGreen(c)), + .b = static_cast(qBlue(c))}; + painter->setPen(luminance(col) > 128.0 ? Qt::black : Qt::white); + painter->drawText(cell, Qt::AlignCenter, PixelValueText(pixels[y * W + x])); + } + } + + painter->restore(); +} diff --git a/viewer/image_viewer/JFJochFollowerImage.h b/viewer/image_viewer/JFJochFollowerImage.h index 0c484f79..90bd5f5d 100644 --- a/viewer/image_viewer/JFJochFollowerImage.h +++ b/viewer/image_viewer/JFJochFollowerImage.h @@ -8,25 +8,37 @@ #include #include +#include "../../reader/JFJochReaderImage.h" + class JFJochImageItem; // A second view of a frame that a JFJochImage has already rendered: the same pixels, with its own // zoom and centre. Nothing is converted or coloured here and there is no second full-size buffer, // so following the main view costs a pointer assignment per frame rather than a whole render. // -// It shows only the image. Overlays, ROI tools and per-pixel value labels belong to the view that -// owns the data; a magnifier does not need them. +// Zoomed in far enough it writes the per-pixel counts over the image. Those are read straight from +// the detector's int32 buffer - the same source the main view colours from - so no float copy of +// the image is needed either. +// +// It draws no overlays and has no ROI tools: those belong to the view that owns the data. class JFJochFollowerImage : public QGraphicsView { Q_OBJECT + // Per-pixel counts are only legible once a detector pixel is a few tens of screen pixels + static constexpr double kLabelZoom = 30.0; + static constexpr int kMaxLabels = 2000; + JFJochImageItem *item_ = nullptr; std::shared_ptr frame_; + std::shared_ptr values_; double zoom_ = 12.0; void wheelEvent(QWheelEvent *event) override; + void drawForeground(QPainter *painter, const QRectF &rect) override; public: explicit JFJochFollowerImage(QWidget *parent = nullptr); void SetFrame(std::shared_ptr frame); + void SetPixelValues(std::shared_ptr image); void CenterAt(QPointF scenePos); }; diff --git a/viewer/windows/JFJochMagnifierWindow.cpp b/viewer/windows/JFJochMagnifierWindow.cpp index 52e6da7b..11c779eb 100644 --- a/viewer/windows/JFJochMagnifierWindow.cpp +++ b/viewer/windows/JFJochMagnifierWindow.cpp @@ -18,6 +18,10 @@ void JFJochMagnifierWindow::setFrame(std::shared_ptr frame) { m_image->SetFrame(std::move(frame)); } +void JFJochMagnifierWindow::imageLoaded(std::shared_ptr image) { + m_image->SetPixelValues(std::move(image)); +} + void JFJochMagnifierWindow::centerAt(QPointF scenePos) { if (!isVisible()) return; diff --git a/viewer/windows/JFJochMagnifierWindow.h b/viewer/windows/JFJochMagnifierWindow.h index 346be210..de01c7d6 100644 --- a/viewer/windows/JFJochMagnifierWindow.h +++ b/viewer/windows/JFJochMagnifierWindow.h @@ -23,6 +23,10 @@ class JFJochMagnifierWindow : public JFJochHelperWindow { public: explicit JFJochMagnifierWindow(QWidget *parent = nullptr); + // Raw counts for the per-pixel labels. This only stores the pointer - the pixels are + // displayed from the frame the main view rendered, nothing is converted here. + void imageLoaded(std::shared_ptr image) override; + public slots: void setFrame(std::shared_ptr frame); void centerAt(QPointF scenePos);