diff --git a/viewer/image_viewer/JFJochDiffractionImage.cpp b/viewer/image_viewer/JFJochDiffractionImage.cpp index 5f7b3576..7c12ab74 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.cpp +++ b/viewer/image_viewer/JFJochDiffractionImage.cpp @@ -1011,6 +1011,20 @@ QString JFJochDiffractionImage::HoverResolutionLabel() const { return QString("d = %1 \u00C5").arg(QString::number(hover_resolution, 'f', 2)); } +QString JFJochDiffractionImage::PixelLabel(int x, int y) const { + if (!image) + return {}; + + const int32_t v = image->Image()[static_cast(y) * W + x]; + if (v == GAP_PXL_VALUE) + return QStringLiteral("Gap"); + if (v == ERROR_PXL_VALUE) + return QStringLiteral("Err"); + if (v == SATURATED_PXL_VALUE) + return QStringLiteral("Sat"); + return QString::number(v); +} + void JFJochDiffractionImage::drawForeground(QPainter *painter, const QRectF &rect) { JFJochImage::drawForeground(painter, rect); diff --git a/viewer/image_viewer/JFJochDiffractionImage.h b/viewer/image_viewer/JFJochDiffractionImage.h index c9a3f847..3ba3884e 100644 --- a/viewer/image_viewer/JFJochDiffractionImage.h +++ b/viewer/image_viewer/JFJochDiffractionImage.h @@ -26,6 +26,9 @@ Q_OBJECT // currently sits, in viewport coordinates. QRect hover_text_rect_; [[nodiscard]] QString HoverResolutionLabel() const; + // Counts are exact integers: label them from the int32 image rather than from a float copy, + // which both avoids materialising that copy and cannot round large summed values. + [[nodiscard]] QString PixelLabel(int x, int y) const override; void drawForeground(QPainter *painter, const QRectF &rect) override; public: enum class RingMode {Auto, Estimation, Manual, None, IceRings}; diff --git a/viewer/image_viewer/JFJochImage.cpp b/viewer/image_viewer/JFJochImage.cpp index 26172d87..3ed3b9ed 100644 --- a/viewer/image_viewer/JFJochImage.cpp +++ b/viewer/image_viewer/JFJochImage.cpp @@ -817,86 +817,75 @@ void JFJochImage::applyViewport(QTransform transform, QPointF center) { m_applyingViewport = false; } -void JFJochImage::writePixelLabels() { - - static QFont font([] { - QFont f("DejaVu Sans Mono"); - f.setStyleHint(QFont::TypeWriter); - f.setPixelSize(1); - return f; - }()); - static const QString kGap = QStringLiteral("Gap"); - static const QString kErr = QStringLiteral("Err"); - static const QString kSat = QStringLiteral("Sat"); - - QRectF visibleRect = mapToScene(viewport()->geometry()).boundingRect(); - - const int startX = std::max(0, static_cast(std::floor(visibleRect.left()))); - const int endX = std::min(static_cast(W), static_cast(std::ceil(visibleRect.right()))); - const int startY = std::max(0, static_cast(std::floor(visibleRect.top()))); - const int endY = std::min(static_cast(H), static_cast(std::ceil(visibleRect.bottom()))); - - const int visW = std::max(0, endX - startX); - const int visH = std::max(0, endY - startY); - int maxLabels = 5000; - +QString JFJochImage::PixelLabel(int x, int y) const { // Choose thresholds that fit your UI width constexpr float kMinFixed = 1e-3; constexpr float kMaxFixed = 1e5; - if (visW * visH <= maxLabels) { - EnsurePixelValues(); + const float val = image_fp[static_cast(y) * W + x]; + const auto absVal = std::abs(val); + const auto nearest = std::nearbyint(val); - QString numBuf; // reused buffer + if (std::isnan(val)) + return QStringLiteral("Gap"); + if (std::isinf(val)) + return std::signbit(val) ? QStringLiteral("Err") : QStringLiteral("Sat"); + if (val == 0.0f) + return QStringLiteral("0"); + if (absVal >= kMinFixed && absVal < kMaxFixed) { + if (std::abs(val - nearest) < 1e-6) + return QString::number(static_cast(val)); + if (absVal < 1e4) + return QString::number(val, 'f', label_decimals_); + return QString::number(val, 'f', std::min(label_decimals_, 2)); + } + return QString::number(val, 'e', 1); +} - for (int y = startY; y < endY; y ++) { - for (int x = startX; x < endX; x++) { - const int idx = y * W + x; - const float val = image_fp[idx]; +void JFJochImage::drawPixelLabels(QPainter *painter) { + constexpr int kMaxLabels = 5000; - const auto absVal = std::abs(val); - const auto nearest = std::nearbyint(val); + const QRectF visibleRect = mapToScene(viewport()->rect()).boundingRect(); + const int startX = std::max(0, static_cast(std::floor(visibleRect.left()))); + const int endX = std::min(static_cast(W), static_cast(std::ceil(visibleRect.right()))); + const int startY = std::max(0, static_cast(std::floor(visibleRect.top()))); + const int endY = std::min(static_cast(H), static_cast(std::ceil(visibleRect.bottom()))); + if (endX <= startX || endY <= startY) + return; + if ((endX - startX) * (endY - startY) > kMaxLabels) + return; - const QString* pText = nullptr; - if (std::isnan(val)) { - pText = &kGap; - } else if (std::isinf(val)) { - pText = std::signbit(val) ? &kErr : &kSat; - } else if (val == 0.0f) { - numBuf = QStringLiteral("0"); - pText = &numBuf; - } else if (absVal >= kMinFixed && absVal < kMaxFixed) { - if (std::abs(val - nearest) < 1e-6) - numBuf = QString::number(static_cast(val)); - else if (absVal < 1e4) - numBuf = QString::number(val, 'f', label_decimals_); - else - numBuf = QString::number(val, 'f', std::min(label_decimals_, 2)); - pText = &numBuf; - } else { - numBuf = QString::number(val, 'e', 1); - pText = &numBuf; - } + // Laid out in viewport pixels: a constant, readable size independent of the zoom + painter->save(); + painter->resetTransform(); - auto *textItem = new QGraphicsSimpleTextItem(*pText); - textItem->setFont(font); - // Read the colour back from the rendered image rather than keeping a - // full-size mirror of it around for the few pixels that get a label. - const QRgb pxl = frame_->pixel(x, y); - if (luminance(rgb{.r = static_cast(qRed(pxl)), - .g = static_cast(qGreen(pxl)), - .b = static_cast(qBlue(pxl))}) > 128.0) - textItem->setBrush(Qt::black); - else - textItem->setBrush(Qt::white); + QFont font("DejaVu Sans Mono"); + font.setStyleHint(QFont::TypeWriter); + font.setPixelSize(std::clamp(static_cast(scale_factor * 0.3), 7, 16)); + painter->setFont(font); - textItem->setPos(x + 0.3, y + 0.2); - textItem->setTransform(QTransform::fromScale(0.2, 0.2)); - scene()->addItem(textItem); - addOverlayItem(textItem); - } + for (int y = startY; y < endY; y++) { + for (int x = startX; x < endX; x++) { + const QRect cell = mapFromScene(QRectF(x, y, 1, 1)).boundingRect(); + // Read the colour back from the rendered image rather than keeping a full-size + // mirror of it around for the few pixels that get a label. + const QRgb pxl = frame_->pixel(x, y); + painter->setPen(luminance(rgb{.r = static_cast(qRed(pxl)), + .g = static_cast(qGreen(pxl)), + .b = static_cast(qBlue(pxl))}) > 128.0 + ? Qt::black : Qt::white); + painter->drawText(cell, Qt::AlignCenter, PixelLabel(x, y)); } } + + painter->restore(); +} + +void JFJochImage::drawForeground(QPainter *painter, const QRectF &rect) { + QGraphicsView::drawForeground(painter, rect); + + if (scale_factor > 30.0 && W * H > 0 && frame_ && !frame_->isNull()) + drawPixelLabels(painter); } void JFJochImage::resetScenePointers() { @@ -928,9 +917,6 @@ void JFJochImage::updateOverlay() { image_dirty_ = false; } - if (scale_factor > 30.0) - writePixelLabels(); - DrawROI(); addCustomOverlay(); diff --git a/viewer/image_viewer/JFJochImage.h b/viewer/image_viewer/JFJochImage.h index b0b1da6d..acc6ea09 100644 --- a/viewer/image_viewer/JFJochImage.h +++ b/viewer/image_viewer/JFJochImage.h @@ -82,7 +82,7 @@ class JFJochImage : public QGraphicsView { void DrawROI(); virtual void addCustomOverlay(); void updateROI(); - void writePixelLabels(); + void drawPixelLabels(QPainter *painter); void wheelEvent(QWheelEvent* event) override; void resizeEvent(QResizeEvent *event) override; void contextMenuEvent(QContextMenuEvent *event) override; @@ -156,6 +156,12 @@ protected: QPointF roiEndPos; QRectF roiBox; + // Text for the per-pixel value label at (x, y). The base reads image_fp, which is what the + // float-valued views hold; a view whose source is exact integers overrides this so the label + // is exact and no float copy of the image has to exist at all. + [[nodiscard]] virtual QString PixelLabel(int x, int y) const; + void drawForeground(QPainter *painter, const QRectF &rect) override; + static QPointF RoundPoint(const QPointF& p); virtual void mouseHover(const QPointF &scenePos, Qt::KeyboardModifiers modifiers) = 0;