jfjoch_viewer: Combine Simple image viewer and DiffractionImageViewer into one parent class

This commit is contained in:
2025-11-07 12:06:18 +01:00
parent 77fd916c96
commit ae6574d3ec
8 changed files with 175 additions and 220 deletions

View File

@@ -21,26 +21,15 @@ static inline double normalize_to_unit(double raw, double bg, double fg) {
}
JFJochSimpleImageViewer::JFJochSimpleImageViewer(QWidget *parent)
: QGraphicsView(parent) {
setDragMode(QGraphicsView::NoDrag);
setTransformationAnchor(QGraphicsView::AnchorUnderMouse);
setRenderHint(QPainter::Antialiasing);
setRenderHint(QPainter::SmoothPixmapTransform);
setFocusPolicy(Qt::ClickFocus);
: JFJochImage(parent) {
auto *scn = new QGraphicsScene(this);
setScene(scn);
// Optional: a sensible default colormap
color_scale_.Select(ColorScaleEnum::Indigo);
color_scale.Select(ColorScaleEnum::Indigo);
// Keep overlays in pixel units independent of zoom (for labels font sizing)
setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
// QTimer for smoother movement/updates
repaint_timer_.setInterval(16); // ~60 FPS
repaint_timer_.setSingleShot(false);
connect(&repaint_timer_, &QTimer::timeout, this, &JFJochSimpleImageViewer::onRepaintTimer);
}
void JFJochSimpleImageViewer::clear() {
@@ -55,69 +44,14 @@ void JFJochSimpleImageViewer::setImage(std::shared_ptr<const SimpleImage> img) {
image_ = std::move(img);
has_image_ = true;
renderImage();
updateScene();
updateOverlay();
}
void JFJochSimpleImageViewer::setBackground(float v) {
background_ = v;
emit backgroundChanged(background_);
void JFJochSimpleImageViewer::Redraw() {
if (has_image_) {
renderImage();
// Preserve current transform while updating
updateScene();
}
}
void JFJochSimpleImageViewer::setForeground(float v) {
foreground_ = std::max(1e-6f, v);
emit foregroundChanged(foreground_);
if (has_image_) {
renderImage();
updateScene();
}
}
void JFJochSimpleImageViewer::setColorMap(int colorMapEnumValue) {
try {
color_scale_.Select(static_cast<ColorScaleEnum>(colorMapEnumValue));
if (has_image_) {
renderImage();
updateScene();
}
} catch (...) {
// ignore invalid value
}
}
void JFJochSimpleImageViewer::wheelEvent(QWheelEvent *event) {
if (!scene() || !has_image_) return;
const double zoomFactor = 1.15;
const QPointF targetScenePos = mapToScene(event->position().toPoint());
if (event->modifiers() == Qt::ShiftModifier) {
// Shift + wheel adjusts foreground (like your main viewer)
float new_fg = foreground_ + event->angleDelta().y() / 120.0f;
if (new_fg < 1.0f) new_fg = 1.0f;
setForeground(new_fg);
} else {
if (event->angleDelta().y() > 0) {
if (scale_factor_ * zoomFactor < 500.0) {
scale_factor_ *= zoomFactor;
scale(zoomFactor, zoomFactor);
}
} else {
if (scale_factor_ > 0.2) {
scale_factor_ *= 1.0 / zoomFactor;
scale(1.0 / zoomFactor, 1.0 / zoomFactor);
}
}
// Keep focus under mouse
const QPointF updatedCenter = mapToScene(viewport()->rect().center());
const QPointF delta = targetScenePos - updatedCenter;
translate(delta.x(), delta.y());
// Only labels depend on redraw; pixmap stays. Rebuild scene to re-place labels
updateScene();
updateOverlay();
}
}
@@ -130,7 +64,7 @@ void JFJochSimpleImageViewer::mousePressEvent(QMouseEvent* event) {
// With Shift -> ROI interactions (draw/resize/move); without -> pan
if (event->modifiers() & Qt::Modifier::SHIFT) {
// Determine if we are over an existing ROI handle
active_handle_ = hitTestROIHandle(scenePos, 4.0 / std::sqrt(std::max(1e-4, scale_factor_)));
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();
@@ -167,7 +101,7 @@ void JFJochSimpleImageViewer::mouseMoveEvent(QMouseEvent* event) {
const QPointF delta = mapToScene(event->pos()) - mapToScene(last_mouse_pos_);
last_mouse_pos_ = event->pos();
translate(delta.x(), delta.y());
updateScene();
updateOverlay();
break;
}
case MouseEventType::DrawingROI: {
@@ -254,7 +188,7 @@ void JFJochSimpleImageViewer::renderImage() {
// Prepare destination QImage (RGB888)
QImage qimg(int(W), int(H), QImage::Format_RGB888);
image_rgb_.resize(W * H);
image_rgb.resize(W * H);
image_values_.resize(W * H);
std::vector<uint8_t> image_buffer;
@@ -271,7 +205,7 @@ void JFJochSimpleImageViewer::renderImage() {
dstRow[idx * 3 + 0] = c.r;
dstRow[idx * 3 + 1] = c.g;
dstRow[idx * 3 + 2] = c.b;
image_rgb_[idx] = c;
image_rgb[idx] = c;
};
// Fast path: packed RGB (assumed 3 bytes per pixel)
@@ -317,16 +251,16 @@ void JFJochSimpleImageViewer::renderImage() {
}
// Build pixmap
pixmap_ = QPixmap::fromImage(qimg);
pixmap = QPixmap::fromImage(qimg);
}
void JFJochSimpleImageViewer::updateScene() {
void JFJochSimpleImageViewer::updateOverlay() {
if (!scene()) return;
scene()->clear();
if (!has_image_) return;
// Add base image
scene()->addItem(new QGraphicsPixmapItem(pixmap_));
scene()->addItem(new QGraphicsPixmapItem(pixmap));
QFont font("Arial", 1); // Font for pixel value text
font.setPixelSize(1); // This will render very small text (1-pixel high).
@@ -344,8 +278,8 @@ void JFJochSimpleImageViewer::updateScene() {
const int endY = std::min(H, static_cast<int>(std::ceil(visibleRect.bottom())));
// Only when zoomed in and region small (performance)
if (scale_factor_ > 20.0 && (endX - startX + 1) * (endY - startY + 1) < 500) {
const qreal f = std::clamp(scale_factor_, 0.5, 50.0);
if (scale_factor > 20.0 && (endX - startX + 1) * (endY - startY + 1) < 500) {
const qreal f = std::clamp(scale_factor, 0.5, 50.0);
QFont font("Arial", 1);
font.setPixelSize(1); // 1 px font; we use scaling below as needed
@@ -355,7 +289,7 @@ void JFJochSimpleImageViewer::updateScene() {
const size_t idx = base + size_t(x);
const double raw = image_values_[idx];
const rgb c = image_rgb_[idx];
const rgb c = image_rgb[idx];
// If no scalar was stored (e.g., RGB source), you can skip or compute some alt text
if (std::isnan(raw)) continue;
@@ -398,19 +332,19 @@ void JFJochSimpleImageViewer::renderImage(QImage &qimg, const uint8_t *input) {
if (auto_fgbg) {
auto vec1(image_values_);
std::sort(vec1.begin(), vec1.end());
background_ = vec1[0];
foreground_ = vec1[vec1.size() - 1];
emit backgroundChanged(background_);
emit foregroundChanged(foreground_);
background = vec1[0];
foreground = vec1[vec1.size() - 1];
emit backgroundChanged(background);
emit foregroundChanged(foreground);
}
for (int y = 0; y < H; ++y) {
uchar *scanLine = qimg.scanLine(y); // Get writable pointer to the row
for (int x = 0; x < W; ++x) {
image_rgb_[y * W + x] = color_scale_.Apply(image_values_[y * W + x], background_, foreground_);
scanLine[x * 3 + 0] = image_rgb_[y * W + x].r; // Red
scanLine[x * 3 + 1] = image_rgb_[y * W + x].g; // Green
scanLine[x * 3 + 2] = image_rgb_[y * W + x].b; // Blue
image_rgb[y * W + x] = color_scale.Apply(image_values_[y * W + x], background, foreground);
scanLine[x * 3 + 0] = image_rgb[y * W + x].r; // Red
scanLine[x * 3 + 1] = image_rgb[y * W + x].g; // Green
scanLine[x * 3 + 2] = image_rgb[y * W + x].b; // Blue
}
}
}
@@ -480,7 +414,7 @@ void JFJochSimpleImageViewer::drawROI(QGraphicsScene* scn) {
scn->addRect(roi_box_, pen);
// Small corner handles
const qreal handleSize = 3.0 / std::sqrt(std::max(1e-4, scale_factor_));
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));
@@ -508,11 +442,11 @@ void JFJochSimpleImageViewer::drawROI(QGraphicsScene* scn) {
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 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);
const qreal f = std::clamp(scale_factor, 0.5, 50.0);
font.setPointSizeF(14.0 / std::sqrt(f));
auto* t = scn->addText(text, font);
@@ -579,7 +513,7 @@ void JFJochSimpleImageViewer::onRepaintTimer() {
// Rebuild scene if requested
if (needs_scene_update_) {
updateScene();
updateOverlay();
needs_scene_update_ = false;
}
@@ -600,8 +534,3 @@ void JFJochSimpleImageViewer::scheduleSceneUpdate() {
if (!repaint_timer_.isActive())
repaint_timer_.start();
}
void JFJochSimpleImageViewer::setFeatureColor(QColor input) {
feature_color = input;
scheduleSceneUpdate();
}