268 lines
9.6 KiB
C++
268 lines
9.6 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "JFJochImage.h"
|
|
|
|
#include <QScrollBar>
|
|
#include <QWheelEvent>
|
|
#include <QMouseEvent>
|
|
|
|
JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) {
|
|
setDragMode(QGraphicsView::NoDrag); // Disable default drag mode
|
|
setTransformationAnchor(QGraphicsView::AnchorUnderMouse); // Zoom anchors
|
|
setRenderHint(QPainter::Antialiasing); // Enable smooth rendering
|
|
setRenderHint(QPainter::SmoothPixmapTransform);
|
|
|
|
setFocusPolicy(Qt::ClickFocus);
|
|
// Connect the horizontal scrollbar's valueChanged signal
|
|
connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &JFJochImage::onScroll);
|
|
|
|
// Connect the vertical scrollbar's valueChanged signal
|
|
connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &JFJochImage::onScroll);
|
|
|
|
// Optional: a sensible default colormap
|
|
color_scale.Select(ColorScaleEnum::Indigo);
|
|
}
|
|
|
|
void JFJochImage::onScroll(int value) {
|
|
updateOverlay();
|
|
}
|
|
|
|
void JFJochImage::changeBackground(float val) {
|
|
background = val;
|
|
Redraw();
|
|
}
|
|
|
|
void JFJochImage::changeForeground(float val) {
|
|
foreground = val;
|
|
Redraw();
|
|
}
|
|
|
|
void JFJochImage::setColorMap(int color_map) {
|
|
try {
|
|
color_scale.Select(static_cast<ColorScaleEnum>(color_map));
|
|
Redraw();
|
|
} catch (...) {
|
|
}
|
|
}
|
|
|
|
void JFJochImage::setFeatureColor(QColor input) {
|
|
feature_color = input;
|
|
Redraw();
|
|
}
|
|
|
|
void JFJochImage::wheelEvent(QWheelEvent *event) {
|
|
if (!scene()) return;
|
|
|
|
const double zoomFactor = 1.15; // Zoom factor
|
|
|
|
// Get the position of the mouse in scene coordinates
|
|
QPointF targetScenePos = mapToScene(event->position().toPoint());
|
|
|
|
if (event->modifiers() == Qt::ShiftModifier) {
|
|
float new_foreground = foreground + event->angleDelta().y() / 120.0f;
|
|
if (new_foreground < 1)
|
|
new_foreground = 1.0;
|
|
foreground = new_foreground;
|
|
emit foregroundChanged(foreground);
|
|
Redraw();
|
|
} else {
|
|
// Perform zooming
|
|
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);
|
|
}
|
|
}
|
|
|
|
// Adjust the view's center to keep the zoom focused on the mouse position
|
|
QPointF updatedViewportCenter = mapToScene(viewport()->rect().center());
|
|
QPointF delta = targetScenePos - updatedViewportCenter;
|
|
translate(delta.x(), delta.y()); // Shift the view
|
|
|
|
updateOverlay();
|
|
}
|
|
}
|
|
|
|
void JFJochImage::resizeEvent(QResizeEvent *event) {
|
|
QGraphicsView::resizeEvent(event);
|
|
updateOverlay();
|
|
}
|
|
|
|
QPointF JFJochImage::RoundPoint(const QPointF &input) {
|
|
return QPointF(qRound(input.x()), qRound(input.y()));
|
|
}
|
|
|
|
void JFJochImage::SetROIBox(QRect box) {
|
|
roi_type = RoiType::RoiBox;
|
|
roiBox= box;
|
|
roiStartPos = roiBox.topLeft();
|
|
roiEndPos = roiBox.bottomRight();
|
|
Redraw();
|
|
}
|
|
|
|
void JFJochImage::SetROICircle(double x, double y, double radius) {
|
|
roi_type = RoiType::RoiCircle;
|
|
roiBox= QRectF(x - radius, y - radius, 2 * radius, 2 * radius).normalized();
|
|
roiStartPos = roiBox.topLeft();
|
|
roiEndPos = roiBox.bottomRight();
|
|
Redraw();
|
|
}
|
|
|
|
void JFJochImage::mousePressEvent(QMouseEvent *event) {
|
|
if (!scene()) return;
|
|
|
|
if (event->button() == Qt::LeftButton) {
|
|
const QPointF scenePos = mapToScene(event->pos());
|
|
bool mouse_inside_roi = false;
|
|
if (roiStartPos != roiEndPos) {
|
|
if (roi_type == RoiType::RoiBox) {
|
|
QRectF roiRect = QRectF(roiStartPos, roiEndPos).normalized();
|
|
mouse_inside_roi = roiRect.contains(mapToScene(event->pos()));
|
|
} else {
|
|
auto curr_position = mapToScene(event->pos());
|
|
auto delta = roiStartPos - curr_position;
|
|
auto radius_vec = roiStartPos - roiEndPos;
|
|
auto radius_2 = radius_vec.x() * radius_vec.x() + radius_vec.y() * radius_vec.y();
|
|
mouse_inside_roi = (delta.x() * delta.x() + delta.y() * delta.y() < radius_2);
|
|
}
|
|
}
|
|
|
|
// 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)));
|
|
if (active_handle_ != ResizeHandle::None && active_handle_ != ResizeHandle::Inside) {
|
|
mouse_event_type = MouseEventType::ResizingROI;
|
|
roiStartPos = roiBox.topLeft();
|
|
roiEndPos = roiBox.bottomRight();
|
|
setCursor(Qt::SizeAllCursor);
|
|
} else if (roiBox.contains(scenePos)) {
|
|
mouse_event_type = MouseEventType::MovingROI;
|
|
lastMousePos = event->pos();
|
|
setCursor(Qt::ClosedHandCursor);
|
|
} else {
|
|
mouse_event_type = MouseEventType::DrawingROI;
|
|
roiStartPos = RoundPoint(scenePos);
|
|
roiEndPos = roiStartPos;
|
|
roi_type = (event->modifiers() & Qt::Modifier::CTRL) ? RoiType::RoiCircle : RoiType::RoiBox;
|
|
setCursor(Qt::CrossCursor);
|
|
}
|
|
} else {
|
|
mouse_event_type = MouseEventType::Panning;
|
|
setCursor(Qt::ClosedHandCursor);
|
|
lastMousePos = event->pos();
|
|
}
|
|
}
|
|
|
|
QGraphicsView::mousePressEvent(event);
|
|
}
|
|
|
|
void JFJochImage::mouseMoveEvent(QMouseEvent *event) {
|
|
if (!scene()) return;
|
|
|
|
const QPointF scenePos = mapToScene(event->pos());
|
|
|
|
switch (mouse_event_type) {
|
|
case MouseEventType::Panning: {
|
|
const QPointF delta = mapToScene(event->pos()) - mapToScene(lastMousePos);
|
|
lastMousePos = event->pos();
|
|
translate(delta.x(), delta.y());
|
|
updateOverlay();
|
|
break;
|
|
}
|
|
case MouseEventType::DrawingROI: {
|
|
roiEndPos = RoundPoint(scenePos);
|
|
updateROI();
|
|
break;
|
|
}
|
|
case MouseEventType::MovingROI: {
|
|
const QPointF delta = mapToScene(event->pos()) - mapToScene(lastMousePos);
|
|
lastMousePos = event->pos();
|
|
roiBox.translate(delta);
|
|
updateROI();
|
|
break;
|
|
}
|
|
case MouseEventType::ResizingROI: {
|
|
// Modify the corresponding edges based on active_handle_
|
|
QRectF r = roiBox;
|
|
switch (active_handle_) {
|
|
case ResizeHandle::Left: r.setLeft(scenePos.x()); break;
|
|
case ResizeHandle::Right: r.setRight(scenePos.x()); break;
|
|
case ResizeHandle::Top: r.setTop(scenePos.y()); break;
|
|
case ResizeHandle::Bottom: r.setBottom(scenePos.y()); break;
|
|
case ResizeHandle::TopLeft: r.setTop(scenePos.y()); r.setLeft(scenePos.x()); break;
|
|
case ResizeHandle::TopRight: r.setTop(scenePos.y()); r.setRight(scenePos.x()); break;
|
|
case ResizeHandle::BottomLeft: r.setBottom(scenePos.y()); r.setLeft(scenePos.x()); break;
|
|
case ResizeHandle::BottomRight:r.setBottom(scenePos.y()); r.setRight(scenePos.x()); break;
|
|
default: break;
|
|
}
|
|
roiBox = r.normalized();
|
|
updateROI();
|
|
break;
|
|
}
|
|
case MouseEventType::None: {
|
|
mouseHover(event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
QGraphicsView::mouseMoveEvent(event);
|
|
}
|
|
|
|
void JFJochImage::mouseReleaseEvent(QMouseEvent *event) {
|
|
if (!scene()) return;
|
|
|
|
if (event->button() == Qt::LeftButton) {
|
|
if (mouse_event_type == MouseEventType::DrawingROI) {
|
|
roiEndPos = RoundPoint(mapToScene(event->pos()));
|
|
updateROI();
|
|
}
|
|
mouse_event_type = MouseEventType::None;
|
|
active_handle_ = ResizeHandle::None;
|
|
setCursor(Qt::ArrowCursor);
|
|
}
|
|
|
|
QGraphicsView::mouseReleaseEvent(event);
|
|
|
|
}
|
|
|
|
JFJochImage::ResizeHandle
|
|
JFJochImage::hitTestROIHandle(const QPointF& scenePos, qreal tol) const {
|
|
if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0)
|
|
return ResizeHandle::None;
|
|
|
|
const QRectF r = roiBox;
|
|
const QPointF tl = r.topLeft();
|
|
const QPointF tr = r.topRight();
|
|
const QPointF bl = r.bottomLeft();
|
|
const QPointF br = r.bottomRight();
|
|
|
|
auto nearPt = [&](const QPointF& a, const QPointF& b, qreal t) {
|
|
return std::abs(a.x() - b.x()) <= t && std::abs(a.y() - b.y()) <= t;
|
|
};
|
|
|
|
if (nearPt(scenePos, tl, tol)) return ResizeHandle::TopLeft;
|
|
if (nearPt(scenePos, tr, tol)) return ResizeHandle::TopRight;
|
|
if (nearPt(scenePos, bl, tol)) return ResizeHandle::BottomLeft;
|
|
if (nearPt(scenePos, br, tol)) return ResizeHandle::BottomRight;
|
|
|
|
// Edges
|
|
if (std::abs(scenePos.x() - r.left()) <= tol && scenePos.y() >= r.top() - tol && scenePos.y() <= r.bottom() + tol)
|
|
return ResizeHandle::Left;
|
|
if (std::abs(scenePos.x() - r.right()) <= tol && scenePos.y() >= r.top() - tol && scenePos.y() <= r.bottom() + tol)
|
|
return ResizeHandle::Right;
|
|
if (std::abs(scenePos.y() - r.top()) <= tol && scenePos.x() >= r.left() - tol && scenePos.x() <= r.right() + tol)
|
|
return ResizeHandle::Top;
|
|
if (std::abs(scenePos.y() - r.bottom()) <= tol && scenePos.x() >= r.left() - tol && scenePos.x() <= r.right() + tol)
|
|
return ResizeHandle::Bottom;
|
|
|
|
if (r.contains(scenePos)) return ResizeHandle::Inside;
|
|
return ResizeHandle::None;
|
|
}
|