4

I am originally loading image in QGraphicsView and using this method for basic zoom out and zoom in functionality. However, I am unable to retrieve actual image pixel position using mapToScene functionality in eventFilter function of Graphics_view_zoom class. The below code produces behaviour exactly as windows photo viewer zooming only selected region.

MapToScene() returns same Point as mouse event position.

Here is the class which deals with zooming.

#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;

In mainwindow.cpp, I am creating object of this class and loading an image as below:

   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);

Can anyone help with how do I achieve this ?

William Miller
  • 9,839
  • 3
  • 25
  • 46
Anonymous
  • 336
  • 3
  • 23

2 Answers2

3

Keep in mind that the scaling you use only scales the scene, not the items. Given this, the position of the pixel can be obtained, so the algorithm is:

  • Obtain the mouse position with respect to the QGraphicsView
  • Transform that position with respect to the scene using mapToScene
  • Convert the coordinate with respect to the scene in relation to the item using mapFromScene of the QGraphicsItem.

Considering the above, I have implemented the following example:

#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"
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
0

It may be better to use a custom graphics scene subclassed from QGraphicsScene as this makes extracting the necessary coordinates much simpler. The only snag is you have to have the QGraphicsPixmapItem::pos available in the custom QGraphicsScene class - I have included a full working example which uses Graphics_view_zoom.h and Graphics_view_zoom.cpp from the linked question. The position of the QGraphicsPixmapItem is passed to a member of the QGraphicsScene subclass, Frame, in order to make the necessary correction.

#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(":/StackOverflow/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();
}

This will display the image coordinates of the pixel under the mouse relative to (0,0) at the top left corner.

The mouse is not visible in these screenshots but in the first it is in exactly the upper left corner and the second it is in exactly the lower right corner. If these coordinates are needed inside the Graphics_view_zoom object then you simply have to scope the Frame instance appropriately, or pass the value as needed.

Note - the exact coordinates displayed may not precisely represent the position of the mouse in this example since they are cast to ints for demonstration, but the floating point values can be easily accessed since QGraphicsSceneMoveEvent::scenePos() returns a QPointF. Additionally, note that in running this demonstration there may be some (hopefully very small) variation on where the mouse appears to be relative to it's 'actual' position - I recommend using Qt::CrossCursor to allay this. For example on my system the default cursor is off by about a pixel for certain areas on my smaller display, this is also affected by the zoom level - higher zoom will produce more accurate results, less zoom will be less accurate.

William Miller
  • 9,839
  • 3
  • 25
  • 46