jfjoch_viewer: Add context menu to images to allow copy/clear ROI/fit to view

This commit is contained in:
2025-11-26 10:10:27 +01:00
parent 11647b1ae5
commit 2b28aa3a46
2 changed files with 87 additions and 0 deletions
+82
View File
@@ -8,6 +8,13 @@
#include <QWheelEvent>
#include <QMouseEvent>
#include <QTimer>
#include <QMenu>
#include <QContextMenuEvent>
#include <QClipboard>
#include <QGuiApplication>
#include <QMimeData>
#include <QBuffer>
#include <QPainter>
JFJochImage::JFJochImage(QWidget *parent) : QGraphicsView(parent) {
setDragMode(QGraphicsView::NoDrag); // Disable default drag mode
@@ -265,6 +272,81 @@ void JFJochImage::mouseReleaseEvent(QMouseEvent *event) {
}
void JFJochImage::contextMenuEvent(QContextMenuEvent *event) {
QMenu menu(this);
QAction *copyImageAct = menu.addAction(tr("Copy image"));
QAction *copyWithOverlayAct = menu.addAction(tr("Copy image with overlay"));
menu.addSeparator();
QAction *fitAct = menu.addAction(tr("Fit image to view"));
QAction *clearRoiAct = menu.addAction(tr("Clear ROI"));
const bool hasImage = (W > 0 && H > 0 && !pixmap.isNull());
copyImageAct->setEnabled(hasImage);
copyWithOverlayAct->setEnabled(hasImage && scene());
QAction *chosen = menu.exec(event->globalPos());
if (!chosen) return;
if (chosen == copyImageAct) {
copyImageToClipboard();
} else if (chosen == copyWithOverlayAct) {
copyImageWithOverlayToClipboard();
} else if (chosen == fitAct) {
fitToView();
} else if (chosen == clearRoiAct) {
clearROIInternal();
}
}
static void setClipboardAsJpegAndImage(const QImage &img, int quality = 95) {
// Provide both "image/jpeg" and generic image flavors for better compatibility
QByteArray ba;
ba.reserve(img.width() * img.height() * 3 / 2);
QBuffer buf(&ba);
buf.open(QIODevice::WriteOnly);
img.convertToFormat(QImage::Format_ARGB32); // ensure a known format for encoding
img.save(&buf, "JPEG", quality);
auto *mime = new QMimeData();
mime->setData("image/jpeg", ba);
mime->setImageData(img); // also set as generic bitmap
QGuiApplication::clipboard()->setMimeData(mime);
}
void JFJochImage::copyImageToClipboard() {
if (W == 0 || H == 0 || pixmap.isNull()) return;
// Use the underlying rendered image (no overlay)
QImage img = pixmap.toImage();
setClipboardAsJpegAndImage(img, 95);
emit writeStatusBar(tr("Image copied to clipboard"), 2000);
}
void JFJochImage::copyImageWithOverlayToClipboard() {
if (W == 0 || H == 0 || !scene()) return;
// Render the entire scene (image + overlay) at native image resolution
QImage img(int(W), int(H), QImage::Format_ARGB32_Premultiplied);
img.fill(Qt::transparent);
QPainter p(&img);
// Ensure we render the same logical rect as the scene/image coordinates
const QRectF sourceRect(0, 0, qreal(W), qreal(H));
scene()->render(&p, QRectF(0, 0, qreal(W), qreal(H)), sourceRect);
p.end();
setClipboardAsJpegAndImage(img, 95);
emit writeStatusBar(tr("Image with overlay copied to clipboard"), 2000);
}
void JFJochImage::clearROIInternal() {
roiBox = QRectF(); // clear any ROI
// Keep current roi_type; ROI simply becomes empty
CalcROI(); // will emit a zeroed ROI message
updateOverlay();
emit writeStatusBar(tr("ROI cleared"), 1500);
}
JFJochImage::ResizeHandle
JFJochImage::hitTestROIHandle(const QPointF& scenePos, qreal tol) const {
if (roiBox.isNull() || roiBox.width() <= 0 || roiBox.height() <= 0)
+5
View File
@@ -19,6 +19,7 @@ class JFJochImage : public QGraphicsView {
void writePixelLabels();
void wheelEvent(QWheelEvent* event) override;
void resizeEvent(QResizeEvent *event) override;
void contextMenuEvent(QContextMenuEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
@@ -76,6 +77,10 @@ protected:
// Perform initial fit-to-view (shorter direction), once per image size
void fitToViewShorterSideOnce();
QSize last_fit_viewport_ = {};
void copyImageToClipboard();
void copyImageWithOverlayToClipboard();
void clearROIInternal();
signals:
void foregroundChanged(float v);
void backgroundChanged(float v);