Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Zooming into Pixmap about mouse pointer

Zooming into Pixmap about mouse pointer

Scheduled Pinned Locked Moved Solved General and Desktop
13 Posts 2 Posters 3.3k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • P Offline
    P Offline
    Perdrix
    wrote on 14 Aug 2020, 16:43 last edited by
    #1

    Further to an earlier question. When I roll the mouse wheel to zoom into the pixmap, I wish to zoom into it so that the zoom is centred on the mouse position (or the position within the scaled Pixmap that's closest to the mouse pointer).

    Currently the zoom is all relative to the origin (top left).

    My code sets the basic scaling in resizeEvent() and the zoom factor in wheelEvent():

    void DSSImageWidget::resizeEvent(QResizeEvent* e)
    {
        QSize sz = e->size();
        qreal hScale = (qreal)sz.width() / (m_pixmap.width() + 4);
        qreal vScale = (qreal)sz.height() / (m_pixmap.height() + 4);
        m_scale = std::min(hScale, vScale);
    
        update();
        Inherited::resizeEvent(e);
    }
    
    void DSSImageWidget::wheelEvent(QWheelEvent* e)
    {
        qreal degrees = -e->angleDelta().y() / 8.0;
        qreal steps = degrees / 60.0;
        qreal factor = m_factor * std::pow(1.125, steps);
        
        m_factor = std::clamp(factor, 1.0, 5.0);
        update();
    }
    

    Which is fine but how do I adjust my paintEvent code to do that? I'm sort of guessing that I need to save the mouse position in wheelEvent and do some tricksy stuff in paintEvent(), but I'm very unclear what's needed.

    My paint code (stolen from the affine sample) currently looks like:

    void DSSImageWidget::paintEvent(QPaintEvent* event)
    {
        QPainter painter;
        painter.begin(this);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setRenderHint(QPainter::SmoothPixmapTransform);
        QPointF center(m_pixmap.width() / qreal(2), m_pixmap.height() / qreal(2));
    
    
        //painter.translate(center);
        painter.scale(m_factor*m_scale, m_factor*m_scale);
        //painter.translate(-center);
    
        painter.drawPixmap(QPointF(0, 0), m_pixmap);
        painter.setPen(QPen(QColor(255, 0, 0, alpha), 0.25, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
        painter.setBrush(Qt::NoBrush);
        painter.drawRect(QRectF(0, 0, m_pixmap.width(), m_pixmap.height()).adjusted(-2, -2, 2, 2));
        painter.end();
    }
    

    Thanks
    David

    1 Reply Last reply
    0
    • P Offline
      P Offline
      Perdrix
      wrote on 22 Aug 2020, 09:38 last edited by Perdrix
      #13

      I finally beat this into submission, here are the critical parts of the code:

      in the header file:

      typedef QWidget
              Inherited;
      
      private:
          bool initialised;
          qreal m_scale, m_zoom;
          QPointF m_origin;
          QPixmap & m_pixmap;
          QPointF m_pointInPixmap;
      
          inline bool mouseOverImage(QPointF loc)
          {
              qreal x = loc.x(), y = loc.y(), ox = m_origin.x(), oy = m_origin.y();
              return (
                          (x >= ox) &&
                          (x <= ox + (m_pixmap.width() * m_scale)) &&
                          (y >= oy) &&
                          (y <= oy + (m_pixmap.height() * m_scale)));
              
          };
      

      And the main C++ code:

      DSSImageWidget::DSSImageWidget(QPixmap& p, QWidget* parent)
          : QWidget(parent),
          initialised(false),
          m_scale(1.0),
          m_zoom(1.0),
          m_origin(0.0, 0.0),
          m_pixmap(p),
          m_pointInPixmap((m_pixmap.width() / 2), (m_pixmap.height() / 2))
      
      {
          setAttribute(Qt::WA_MouseTracking);
         
          setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
      }
      
      void DSSImageWidget::resizeEvent(QResizeEvent* e)
      {
          QSize sz = e->size();
          qreal pixWidth = m_pixmap.width();
          qreal pixHeight = m_pixmap.height();
          qreal hScale = (qreal)sz.width() / pixWidth;
          qreal vScale = (qreal)sz.height() / pixHeight;
          m_scale = std::min(hScale, vScale);
      
          qreal xoffset = 0.0, yoffset = 0.0;
          if ((pixWidth * m_scale) < sz.width())
          {
              xoffset = (sz.width() - (pixWidth * m_scale)) / 2.0;
      
          }
          if ((pixHeight * m_scale) < sz.height())
          {
              yoffset = (sz.height() - (pixHeight * m_scale)) / 2.0;
          }
          m_origin = QPointF(xoffset, yoffset);
      
          update();
          Inherited::resizeEvent(e);
      }
      
      void DSSImageWidget::paintEvent(QPaintEvent* event)
      {
          QPainter painter;
       
          qDebug() << "pointInPixmap: " << m_pointInPixmap.x() << m_pointInPixmap.y();
          //
          // Now calcualate the rectangle we're interested in
          //
          qreal width = m_pixmap.width();
          qreal height = m_pixmap.height();
          qreal x = m_pointInPixmap.x();
          qreal y = m_pointInPixmap.y();
          QRectF  sourceRect(
              x - (x / m_zoom),
              y - (y / m_zoom),
              width / m_zoom,
              height / m_zoom
          );
      
          qDebug() << "sourceRect: " << sourceRect.x() << sourceRect.y() << sourceRect.width() << sourceRect.height();
      
          //
          // Now calculate the rectangle that is the intersection of this rectangle and the pixmap's rectangle.
          //
          sourceRect &= m_pixmap.rect();
      
          painter.begin(this);
          painter.setRenderHint(QPainter::Antialiasing);
          painter.setRenderHint(QPainter::SmoothPixmapTransform);
          
          painter.translate(m_origin);
          painter.scale(m_zoom*m_scale, m_zoom*m_scale);
          painter.translate(-m_origin);
      
          //painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap, sourceRect);
          painter.drawPixmap(m_origin, m_pixmap, sourceRect);
      
          painter.end();
      }
      
      #if QT_CONFIG(wheelevent)
      void DSSImageWidget::wheelEvent(QWheelEvent* e)
      {
          qreal degrees = -e->angleDelta().y() / 8.0;
          //
          // If zooming in and zoom factor is currently 1.0
          // then remember mouse location
          //
          if ((degrees > 0) && (m_zoom == 1.0))
          {
              QPointF mouseLocation = e->position();
      
              if (mouseOverImage(mouseLocation))
              {
                  m_pointInPixmap = QPointF((mouseLocation-m_origin) / m_scale);
              }
              else
              {
                  m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
              }
          }
          qreal steps = degrees / 60.0;
          qreal factor = m_zoom * std::pow(1.125, steps);
          
          m_zoom = std::clamp(factor, 1.0, 5.0);
      
          if (degrees < 0 && m_zoom == 1.0)
          {
              m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
          }
          update();
          Inherited::wheelEvent(e);
      }
      #endif
      

      Hope this helps someone else. David

      1 Reply Last reply
      0
      • P Offline
        P Offline
        Perdrix
        wrote on 16 Aug 2020, 16:17 last edited by
        #2

        Any help much appreciated.

        David

        1 Reply Last reply
        0
        • P Offline
          P Offline
          Perdrix
          wrote on 18 Aug 2020, 14:33 last edited by
          #3

          Is there anyone who can help with this - I'm sure that to some of you this may seem like a stupid beginner's question, but I'm rather struggling to understand how to scale a pixmap to a window (that part works) and then zoom in with the part of the pixmap under the mouse pointer remaining in place.

          Thanks
          David

          1 Reply Last reply
          0
          • O Offline
            O Offline
            ofmrew
            wrote on 19 Aug 2020, 18:05 last edited by
            #4

            There are multiple ways of attacking this problem, but the best approach will be determined by the size of the pixmap. If it is large, like those associated with maps, then the best approach is to use something like wavelet compression or tile the map. For small pixmaps the easiest method is to scale the pixmap and then use a rectangle to copy only that portion of the pixmap to the widget. The other way is to select the area to be drawn and scale it.

            1 Reply Last reply
            0
            • P Offline
              P Offline
              Perdrix
              wrote on 19 Aug 2020, 18:19 last edited by
              #5

              I don't think you've understood my intent. If I position the mouse over a star in the image (as these are astrophotographic images), and rotate the mouse wheel I want the star to remain under the mouse pointer and image to be zoomed around it.

              The code I have works to the extent that it zooms the image but only about the top left corner which is the bit that doesn't move when the pixmap is zoomed.

              1 Reply Last reply
              0
              • O Offline
                O Offline
                ofmrew
                wrote on 19 Aug 2020, 18:31 last edited by
                #6

                I understand and what I stated will do that, it just did not stat the iterations required. At each mouse wheel move event, you must find the location of the mouse. Generally that is a scale reduction or increase for each direction of the mouse wheel rotation. Thus you have what you require. Increment the scale for the increase/decrease and scale the pixmap. Now you must create a rectangle that is centered on the current mouse location which you will use the select a portion of the scaled pixmap to draw.

                I just saw your statement about the top left corner is stationary and that means that the rectangle you are using is not centered on the mouse location. Just move the center of that rectangle to the mouse location. Try that.

                1 Reply Last reply
                1
                • P Offline
                  P Offline
                  Perdrix
                  wrote on 20 Aug 2020, 02:01 last edited by
                  #7

                  I think I've got a conceptual gap here (mine). The rectangle I've got to play with is the Widget's rectangle. I already have the mouse position (that's recorded as the zoom is started).

                  So where does this rectangle centred on the mouse come from (yes, OK I create that programmatically - I assume the same size as my widget), how do I map it back into the original drawing rectangle of the widget.

                  Thanks
                  David

                  O 1 Reply Last reply 20 Aug 2020, 13:31
                  0
                  • P Perdrix
                    20 Aug 2020, 02:01

                    I think I've got a conceptual gap here (mine). The rectangle I've got to play with is the Widget's rectangle. I already have the mouse position (that's recorded as the zoom is started).

                    So where does this rectangle centred on the mouse come from (yes, OK I create that programmatically - I assume the same size as my widget), how do I map it back into the original drawing rectangle of the widget.

                    Thanks
                    David

                    O Offline
                    O Offline
                    ofmrew
                    wrote on 20 Aug 2020, 13:31 last edited by
                    #8

                    @Perdrix Remember that Qt uses a coordinate system that has the top left corner as 0,0 with the y-axis increasing downward and the x-axis increasing to the right.

                    You are using painter.drawPixmap(QPointF(0, 0), m_pixmap); to draw the pixmap which means that the scaled pixmap is drawn starting at the top left, try drawing not from 0,0 but from -center x, -center y. That should shift the pixmap so that center point is now centered.

                    1 Reply Last reply
                    0
                    • P Offline
                      P Offline
                      Perdrix
                      wrote on 20 Aug 2020, 13:54 last edited by
                      #9

                      I changed the code to read:

                          QPainter painter;
                          painter.begin(this);
                          painter.setRenderHint(QPainter::Antialiasing);
                          painter.setRenderHint(QPainter::SmoothPixmapTransform);
                          
                          //QPointF whereScaled = m_where / (m_zoom * m_scale);
                          //qDebug() << "m_where:" << m_where.x() << m_where.y();
                          //qDebug() << whereScaled.x() << " " << whereScaled.y();
                      
                          //painter.translate(-m_where);
                          painter.scale(m_zoom*m_scale, m_zoom*m_scale);
                          //painter.translate(m_where);
                      
                          painter.drawPixmap(QPointF(-m_where.x(), -m_where.y()), m_pixmap);
                      
                          painter.setPen(QPen(QColor(255, 0, 0, alpha), 0.25, Qt::SolidLine, Qt::FlatCap, Qt::BevelJoin));
                          painter.setBrush(Qt::NoBrush);
                          painter.drawRect(QRectF(0, 0, m_pixmap.width(), m_pixmap.height()).adjusted(-2, -2, 2, 2));
                          painter.end();
                      

                      But that didn't work :(

                      1 Reply Last reply
                      0
                      • O Offline
                        O Offline
                        ofmrew
                        wrote on 20 Aug 2020, 14:12 last edited by
                        #10

                        What did you get? I need to know the following: I assume that the mouse was centered on the star so did the star move and to where? Try to draw something at the center to confirm that it is the center.

                        1 Reply Last reply
                        0
                        • P Offline
                          P Offline
                          Perdrix
                          wrote on 20 Aug 2020, 21:05 last edited by
                          #11

                          Before zooming mouse pointer pointing to star marked with yellow arrowhead

                          abaf0e81-1e27-45d3-a228-fdc4c865e867-image.png

                          after zooming with mousewheel

                          e0e196d7-3544-4279-a44b-11de5b07d252-image.png

                          Yellow arrow shows mouse pointer position (unchanged), but nowhere near the star it was over.

                          David

                          1 Reply Last reply
                          0
                          • O Offline
                            O Offline
                            ofmrew
                            wrote on 21 Aug 2020, 13:11 last edited by
                            #12

                            The problem, as I see it, is that draw pixmap method that you are using will draw the pixmap at the given point. Look at the method drawPixmap(target rectangle, pixmap, source rectangle). In your case the target rectangle is the widget rectangle and the source rectangle is that portion of the astrophotographic image that is centered on the star. The mouse location is relative to the widget, but the source rectangle must be centered on the star location in the image and not the mouse location on the widget. That means that some calculations starting with the dimensions of the image (pixmap) must be used to map a point on the screen to a point on the image, the star location. That mapping is what you are missing.

                            How do you calculate the mapping? You must reverse all of the transformations that have been made to the image as it is currently displayed to reach a starting point or define a starting point, e.g., require that the star be selected when the image is first displayed. The question is do you want the zooming the work for any case or do you want the zooming to work only from a specified starting point--the latter is the simple case.

                            However there is another complicating factor the aspect ratio mismatch. In all likelihood the aspect ratios of the rectangle of the widget and the image will no match, so to solve that problem use the drawPixmap method that uses point target, pixmap and source rectangle. I am not sure if any scaling is involved with this method.

                            I have probably given you more information about the problem than you wanted to know, but I hope you find it helpful.

                            I would suggest that you start with a very simple example that you can expand upon. If the images you provided display the complete image then the mapping is a simple ratio of the mouse location on the widget to the dimensions of image to find the star location. Try that.

                            1 Reply Last reply
                            0
                            • P Offline
                              P Offline
                              Perdrix
                              wrote on 22 Aug 2020, 09:38 last edited by Perdrix
                              #13

                              I finally beat this into submission, here are the critical parts of the code:

                              in the header file:

                              typedef QWidget
                                      Inherited;
                              
                              private:
                                  bool initialised;
                                  qreal m_scale, m_zoom;
                                  QPointF m_origin;
                                  QPixmap & m_pixmap;
                                  QPointF m_pointInPixmap;
                              
                                  inline bool mouseOverImage(QPointF loc)
                                  {
                                      qreal x = loc.x(), y = loc.y(), ox = m_origin.x(), oy = m_origin.y();
                                      return (
                                                  (x >= ox) &&
                                                  (x <= ox + (m_pixmap.width() * m_scale)) &&
                                                  (y >= oy) &&
                                                  (y <= oy + (m_pixmap.height() * m_scale)));
                                      
                                  };
                              

                              And the main C++ code:

                              DSSImageWidget::DSSImageWidget(QPixmap& p, QWidget* parent)
                                  : QWidget(parent),
                                  initialised(false),
                                  m_scale(1.0),
                                  m_zoom(1.0),
                                  m_origin(0.0, 0.0),
                                  m_pixmap(p),
                                  m_pointInPixmap((m_pixmap.width() / 2), (m_pixmap.height() / 2))
                              
                              {
                                  setAttribute(Qt::WA_MouseTracking);
                                 
                                  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
                              }
                              
                              void DSSImageWidget::resizeEvent(QResizeEvent* e)
                              {
                                  QSize sz = e->size();
                                  qreal pixWidth = m_pixmap.width();
                                  qreal pixHeight = m_pixmap.height();
                                  qreal hScale = (qreal)sz.width() / pixWidth;
                                  qreal vScale = (qreal)sz.height() / pixHeight;
                                  m_scale = std::min(hScale, vScale);
                              
                                  qreal xoffset = 0.0, yoffset = 0.0;
                                  if ((pixWidth * m_scale) < sz.width())
                                  {
                                      xoffset = (sz.width() - (pixWidth * m_scale)) / 2.0;
                              
                                  }
                                  if ((pixHeight * m_scale) < sz.height())
                                  {
                                      yoffset = (sz.height() - (pixHeight * m_scale)) / 2.0;
                                  }
                                  m_origin = QPointF(xoffset, yoffset);
                              
                                  update();
                                  Inherited::resizeEvent(e);
                              }
                              
                              void DSSImageWidget::paintEvent(QPaintEvent* event)
                              {
                                  QPainter painter;
                               
                                  qDebug() << "pointInPixmap: " << m_pointInPixmap.x() << m_pointInPixmap.y();
                                  //
                                  // Now calcualate the rectangle we're interested in
                                  //
                                  qreal width = m_pixmap.width();
                                  qreal height = m_pixmap.height();
                                  qreal x = m_pointInPixmap.x();
                                  qreal y = m_pointInPixmap.y();
                                  QRectF  sourceRect(
                                      x - (x / m_zoom),
                                      y - (y / m_zoom),
                                      width / m_zoom,
                                      height / m_zoom
                                  );
                              
                                  qDebug() << "sourceRect: " << sourceRect.x() << sourceRect.y() << sourceRect.width() << sourceRect.height();
                              
                                  //
                                  // Now calculate the rectangle that is the intersection of this rectangle and the pixmap's rectangle.
                                  //
                                  sourceRect &= m_pixmap.rect();
                              
                                  painter.begin(this);
                                  painter.setRenderHint(QPainter::Antialiasing);
                                  painter.setRenderHint(QPainter::SmoothPixmapTransform);
                                  
                                  painter.translate(m_origin);
                                  painter.scale(m_zoom*m_scale, m_zoom*m_scale);
                                  painter.translate(-m_origin);
                              
                                  //painter.drawPixmap(QPointF(0.0, 0.0), m_pixmap, sourceRect);
                                  painter.drawPixmap(m_origin, m_pixmap, sourceRect);
                              
                                  painter.end();
                              }
                              
                              #if QT_CONFIG(wheelevent)
                              void DSSImageWidget::wheelEvent(QWheelEvent* e)
                              {
                                  qreal degrees = -e->angleDelta().y() / 8.0;
                                  //
                                  // If zooming in and zoom factor is currently 1.0
                                  // then remember mouse location
                                  //
                                  if ((degrees > 0) && (m_zoom == 1.0))
                                  {
                                      QPointF mouseLocation = e->position();
                              
                                      if (mouseOverImage(mouseLocation))
                                      {
                                          m_pointInPixmap = QPointF((mouseLocation-m_origin) / m_scale);
                                      }
                                      else
                                      {
                                          m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
                                      }
                                  }
                                  qreal steps = degrees / 60.0;
                                  qreal factor = m_zoom * std::pow(1.125, steps);
                                  
                                  m_zoom = std::clamp(factor, 1.0, 5.0);
                              
                                  if (degrees < 0 && m_zoom == 1.0)
                                  {
                                      m_pointInPixmap = QPointF((m_pixmap.width() / 2), (m_pixmap.height() / 2));
                                  }
                                  update();
                                  Inherited::wheelEvent(e);
                              }
                              #endif
                              

                              Hope this helps someone else. David

                              1 Reply Last reply
                              0

                              • Login

                              • Login or register to search.
                              • First post
                                Last post
                              0
                              • Categories
                              • Recent
                              • Tags
                              • Popular
                              • Users
                              • Groups
                              • Search
                              • Get Qt Extensions
                              • Unsolved