// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #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); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setTransform(QTransform::fromScale(zoom_, zoom_)); } void JFJochFollowerImage::SetFrame(std::shared_ptr frame) { const bool same_pointer = (frame_ == frame); const bool same_size = frame_ && frame && frame_->size() == frame->size(); frame_ = std::move(frame); if (!frame_ || frame_->isNull()) { scene()->clear(); item_ = nullptr; return; } // The producer reuses one buffer, so normally only the pixels changed and the item stays. if (!item_ || !same_pointer) { scene()->clear(); item_ = new JFJochImageItem(frame_); scene()->addItem(item_); } if (!same_size) { item_->refresh(); scene()->setSceneRect(0, 0, frame_->width(), frame_->height()); } viewport()->update(); } void JFJochFollowerImage::SetPixelValues(std::shared_ptr image) { values_ = std::move(image); } void JFJochFollowerImage::CenterAt(QPointF scenePos) { if (!frame_ || frame_->isNull()) return; centerOn(scenePos); } void JFJochFollowerImage::wheelEvent(QWheelEvent *event) { constexpr double step = 1.15; zoom_ *= (event->angleDelta().y() > 0) ? step : 1.0 / step; zoom_ = std::clamp(zoom_, 1.0, 200.0); const QPointF center = mapToScene(viewport()->rect().center()); 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(); }