如何获取在 QGraphicsView 中加载的图像像素位置 - 奇怪的 MapToScene() 行为

How to get Image pixel position loaded in QGraphicsView - Strange MapToScene() behaviour

我最初在 QGraphicsView 中加载图像并使用 this method 进行基本的缩小和放大功能。 但是,我无法使用 Graphics_view_zoom class 的 eventFilter 函数中的 mapToScene 功能检索实际图像像素位置。下面的代码产生的行为与 windows 照片查看器仅缩放选定区域完全相同。

MapToScene() returns 与鼠标事件位置相同Point

这是处理缩放的class。

#include "Graphics_view_zoom.h"
#include <QMouseEvent>
#include <QApplication>
#include <QScrollBar>
#include <qmath.h>

Graphics_view_zoom::Graphics_view_zoom(QGraphicsView* view)
  : QObject(view), _view(view)
{
  _view->viewport()->installEventFilter(this);
  _view->setMouseTracking(true);
  _modifiers = Qt::ControlModifier;
  _zoom_factor_base = 1.0015;
}

void Graphics_view_zoom::gentle_zoom(double factor) {
  _view->scale(factor, factor);
  _view->centerOn(target_scene_pos);
  QPointF delta_viewport_pos = target_viewport_pos - QPointF(_view->viewport()->width() / 2.0,
                                                             _view->viewport()->height() / 2.0);
  QPointF viewport_center = _view->mapFromScene(target_scene_pos) - delta_viewport_pos;
  _view->centerOn(_view->mapToScene(viewport_center.toPoint()));
  emit zoomed();
}

void Graphics_view_zoom::set_modifiers(Qt::KeyboardModifiers modifiers) {
  _modifiers = modifiers;

}

void Graphics_view_zoom::set_zoom_factor_base(double value) {
  _zoom_factor_base = value;
}

bool Graphics_view_zoom::eventFilter(QObject *object, QEvent *event) {
  if (event->type() == QEvent::MouseMove) {
    QMouseEvent* mouse_event = static_cast<QMouseEvent*>(event);
    QPointF delta = target_viewport_pos - mouse_event->pos();
    // Here I want to get absolute image coordinates
    if (qAbs(delta.x()) > 5 || qAbs(delta.y()) > 5) {
      target_viewport_pos = mouse_event->pos();
      target_scene_pos = _view->mapToScene(mouse_event->pos());
    }
  } else if (event->type() == QEvent::Wheel) {
    QWheelEvent* wheel_event = static_cast<QWheelEvent*>(event);
    if (QApplication::keyboardModifiers() == _modifiers) {
      if (wheel_event->orientation() == Qt::Vertical) {
        double angle = wheel_event->angleDelta().y();
        double factor = qPow(_zoom_factor_base, angle);
        gentle_zoom(factor);
        return true;
      }
    }
  }
  Q_UNUSED(object)
  return false;

mainwindow.cpp, 我正在创建此 class 的对象并加载如下图像:

   m_GraphicsScene = new QGraphicsScene();
   pixmapItem = new QGraphicsPixmapItem();
   m_GraphicsScene->addItem(multiview[i].pixmapItem);
   view_wrapper = new Graphics_view_zoom(ui->GraphicsView);
   ui->GraphicsView->setScene(multiview[i].m_GraphicsScene);

   pixmapItem->setPixmap(QPixmap::fromImage("img.jpg"));
   multiview[view].m_GraphicsView->fitInView(QRectF(0,0,640,320),Qt::KeepAspectRatio);

谁能帮我实现这个目标?

最好使用从 QGraphicsScene 继承的自定义图形场景 class,因为这样可以更简单地提取必要的坐标。唯一的障碍是您必须在自定义 QGraphicsScene class 中提供 QGraphicsPixmapItem::pos - 我已经包含了一个完整的工作示例,它使用 Graphics_view_zoom.hGraphics_view_zoom.cpp 来自linked questionQGraphicsPixmapItem 的位置传递给 QGraphicsScene subclass、Frame 的成员,以便进行必要的更正。

#include <QPixmap>
#include <QGraphicsPixmapItem>
#include <QGraphicsTextItem>
#include <QGraphicsScene>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <qfont.h>
#include "Graphics_view_zoom.h"

class Frame : public QGraphicsScene {
    Q_OBJECT
public:
    QGraphicsTextItem * coords;
    QPointF pic_tl;
    Frame::Frame(QWidget* parent)
        : QGraphicsScene(parent) {
        coords = new QGraphicsTextItem();
        coords->setZValue(1);
        coords->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
        addItem(coords);
    }

    void Frame::tl(QPointF p) {
        pic_tl = p;
    }

protected:
    void Frame::mouseMoveEvent(QGraphicsSceneMouseEvent* event) {
        QPointF pos = event->scenePos();
        coords->setPlainText("(" + QString("%1").arg(int(pos.x() - pic_tl.x())) + ", " 
                       + QString("%1").arg(int(pos.y() - pic_tl.y())) + ")");
        coords->setPos(pos);
        coords->adjustSize();
    }
};

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);
    QMainWindow* main = new QMainWindow();

    QGraphicsView* GraphicsView = new QGraphicsView(main);
    Graphics_view_zoom* view_wrapper = new Graphics_view_zoom(GraphicsView);
    Frame* frame = new Frame(main);
    QGraphicsPixmapItem* pixmapItem = new QGraphicsPixmapItem();
    frame->addItem(pixmapItem);
    GraphicsView->setScene(frame);

    // Loads a 497x326 pixel test image
    pixmapItem->setPixmap(QPixmap(":/Whosebug/test"));
    // small offset to ensure it works for pictures which are not at 
    // (0,0). Larger offsets produce the same result but require manual
    // adjustments of the view, I have neglected those for brevity as
    // they are not in the scope of the question.
    pixmapItem->setPos(-20, 20);
    frame->tl(pixmapItem->pos());
    GraphicsView->fitInView(QRectF(0, 0, 640, 320), Qt::KeepAspectRatio);
    GraphicsView->centerOn(pixmapItem->pos());

    main->resize(1920, 1080);
    main->show();
    GraphicsView->resize(main->width(), main->height());

    return a.exec();
}

这将在左上角显示鼠标下的像素相对于(0,0)的图像坐标。

鼠标在这些屏幕截图中不可见,但第一个正好在左上角,第二个正好在右下角。如果 Graphics_view_zoom 对象内部需要这些坐标,那么您只需适当地限定 Frame 实例的范围,或者根据需要传递值。

注意 - 显示的确切坐标可能无法准确表示此示例中鼠标的位置,因为它们被转换为 ints 用于演示,但浮动自 QGraphicsSceneMoveEvent::scenePos() returns a QPointF 以来,可以轻松访问点值。此外,请注意,在 运行 此演示中,鼠标相对于其 'actual' 位置的位置可能会有一些(希望非常小)变化 - 我建议使用 Qt::CrossCursor 来缓解这种情况.例如在我的系统上,默认光标在我较小的显示器上的某些区域关闭了大约一个像素,这也受到缩放级别的影响 - 较高的缩放会产生更准确的结果,较小的缩放会不太准确。

请记住,您使用的缩放仅缩放场景,而不缩放项目。鉴于此,可以得到像素点的位置,所以算法为:

  • 获取鼠标相对于 QGraphicsView 的位置
  • 使用 mapToScene 变换相对于场景的位置
  • 使用 QGraphicsItem 的 mapFromScene 转换项目的场景坐标。

考虑到上述情况,我实现了以下示例:

#include <QtWidgets>
#include <random>

static QPixmap create_image(const QSize & size){
    QImage image(size, QImage::Format_ARGB32);
    image.fill(Qt::blue);
    std::random_device rd;
    std::mt19937_64 rng(rd());
    std::uniform_int_distribution<int> uni(0, 255);
    for(int i=0; i< image.width(); ++i)
        for(int j=0; j < image.height(); ++j)
            image.setPixelColor(QPoint(i, j), QColor(uni(rng), uni(rng), uni(rng)));
    return QPixmap::fromImage(image);
}

class GraphicsView : public QGraphicsView
{
    Q_OBJECT
    Q_PROPERTY(Qt::KeyboardModifiers modifiers READ modifiers WRITE setModifiers)
public:
    GraphicsView(QWidget *parent=nullptr): QGraphicsView(parent){
        setScene(new QGraphicsScene);

        setModifiers(Qt::ControlModifier);
        auto item = scene()->addPixmap(create_image(QSize(100, 100)));
        item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
        item->setPos(40, 40);
        fitInView(QRectF(0, 0, 640, 320),Qt::KeepAspectRatio);
        resize(640, 480);
    }
    void setModifiers(const Qt::KeyboardModifiers &modifiers){
        m_modifiers = modifiers;
    }
    Qt::KeyboardModifiers modifiers() const{
        return  m_modifiers;
    }
signals:
    void pixelChanged(const QPoint &);
protected:
    void mousePressEvent(QMouseEvent *event) override{
        if(QGraphicsPixmapItem *item = qgraphicsitem_cast<QGraphicsPixmapItem *>(itemAt(event->pos()))){
            QPointF p = item->mapFromScene(mapToScene(event->pos()));
            QPoint pixel_pos = p.toPoint();
            emit pixelChanged(pixel_pos);
        }
        QGraphicsView::mousePressEvent(event);
    }
    void wheelEvent(QWheelEvent *event) override{
        if(event->modifiers() == m_modifiers){
            double angle = event->orientation() == Qt::Vertical ? event->angleDelta().y(): event->angleDelta().x();
            double factor = qPow(base, angle);
            applyZoom(factor, event->pos());
        }
    }
private:
    void applyZoom(double factor, const QPoint & fixedViewPos)
    {
        QPointF fixedScenePos = mapToScene(fixedViewPos);
        centerOn(fixedScenePos);
        scale(factor, factor);
        QPointF delta = mapToScene(fixedViewPos) - mapToScene(viewport()->rect().center());
        centerOn(fixedScenePos - delta);
    }
    Qt::KeyboardModifiers m_modifiers;
    const double base = 1.0015;
};


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    GraphicsView *view = new GraphicsView;
    QLabel *label = new QLabel;
    QObject::connect(view, &GraphicsView::pixelChanged, label, [label](const QPoint & p){
        label->setText(QString("(%1, %2)").arg(p.x()).arg(p.y()));
    });
    label->setAlignment(Qt::AlignCenter);
    QWidget w;
    QVBoxLayout *lay = new QVBoxLayout(&w);
    lay->addWidget(view);
    lay->addWidget(label);
    w.show();
    return a.exec();
}

#include "main.moc"