Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QGraphicsItem resize with keeping aspect ratio



  • Hello.
    I took the code from the @KillerSmath 's answer. Corrected a little for displaying pictures.
    if anyone knows, tell me how to calculate the position of the MovableCircle so that the initial aspect ratio is preserved

    class MovableCircle : public QGraphicsObject
    {
        Q_OBJECT
    public:
        enum ECirclePos {
            eTopLeft = 0,
            eTopRight,
            eBottomRight,
            eBottomLeft,
        };
    
        explicit MovableCircle(ECirclePos cp, double ar, QGraphicsItem *parent = 0);
    
    private:
        QRectF boundingRect() const;
        QPainterPath shape() const;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget);
        void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
        void mousePressEvent(QGraphicsSceneMouseEvent *event);
        void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
        QPointF _shiftMouseCoords;
    
    private:
        double aspectRatio_;
        ECirclePos circlePos_;
    signals:
        void circleMoved();
    };
    

    I want to calculate position of the MovableCircle here:

    void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        auto pos = mapToScene(event->pos() + _shiftMouseCoords);
    //    pos.setX(qMin(pos.x(), pos.y()));
    //    pos.setY(qMin(pos.x(), pos.y()));
    
        LOG_WARNING(logger, "Circle Pos: ", circlePos_, ", ", pos.x(), " ", pos.y());
        setPos(pos);
        emit circleMoved();
    }
    

    MoveItem.h:

    class MoveItem : public QObject, public QGraphicsItem
    {
        Q_OBJECT
        Q_INTERFACES(QGraphicsItem)
    public:
        explicit MoveItem(uint64_t& zc, QGraphicsItem *parent = 0);
        ~MoveItem();
    
    signals:
    protected:
        QRectF boundingRect() const override;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
    
    private:
        QPointF shiftMouseCoords_;
        QImage qimage_;
        QPixmap pixmap_;
        uint64_t& zCounter_;
        MovableCircle *_topLeftCircle, *_topRightCircle, *_bottomLeftCircle, *_bottomRightCircle;
        QSizeF _size;
    public slots:
    };
    

    MoveItem.cpp:

    MoveItem::MoveItem(uint64_t& zc, QGraphicsItem *parent) :
        QGraphicsItem(parent), zCounter_(zc)
    {
        setZValue(zCounter_);
        qimage_ = QImage("cat.png");
        pixmap_ = QPixmap::fromImage(qimage_);
        _size = pixmap_.size();
    
        setAcceptHoverEvents(true);
        setFlag(QGraphicsItem::ItemIsMovable, true);
    
         double ar = _size.width() / _size.height();
         // Top Left
         _topLeftCircle = new MovableCircle(MovableCircle::eTopLeft, ar, this);
         _topLeftCircle->setPos(0, 0);
         // Top Right
         _topRightCircle = new MovableCircle(MovableCircle::eTopRight, ar, this);
         _topRightCircle->setPos(_size.width(), 0);
         // Bottom Right
         _bottomRightCircle = new MovableCircle(MovableCircle::eBottomRight, ar, this);
         _bottomRightCircle->setPos(_size.width(), _size.height());
         // Bottom Left
         _bottomLeftCircle = new MovableCircle(MovableCircle::eBottomLeft, ar, this);
         _bottomLeftCircle->setPos(0, _size.height());
    
         // Signals
         connect(_topLeftCircle, &MovableCircle::circleMoved, this, [this](){
             _bottomLeftCircle->setPos( _topLeftCircle->pos().x(), _bottomLeftCircle->pos().y());
             _topRightCircle->setPos(_topRightCircle->pos().x(), _topLeftCircle->pos().y());
             update(); // force to Repaint
         });
    
         connect(_topRightCircle, &MovableCircle::circleMoved, this, [this](){
             _topLeftCircle->setPos(_topLeftCircle->pos().x(), _topRightCircle->pos().y());
             _bottomRightCircle->setPos(_topRightCircle->pos().x(), _bottomRightCircle->pos().y());
             update(); // force to Repaint
         });
    
         connect(_bottomLeftCircle, &MovableCircle::circleMoved, this, [this](){
             _topLeftCircle->setPos(_bottomLeftCircle->pos().x(), _topLeftCircle->pos().y());
             _bottomRightCircle->setPos(_bottomRightCircle->pos().x(), _bottomLeftCircle->pos().y());
             update(); // force to Repaint
         });
    
         connect(_bottomRightCircle, &MovableCircle::circleMoved, this, [this](){
             _bottomLeftCircle->setPos(_bottomLeftCircle->pos().x(), _bottomRightCircle->pos().y());
             _topRightCircle->setPos(_bottomRightCircle->pos().x(), _topRightCircle->pos().y());
             update(); // force to Repaint
         });
    }
    
    QRectF MoveItem::boundingRect() const
    {
        qreal distX = sqrt(pow(_topLeftCircle->x() - _topRightCircle->x(),2) +
                           pow(_topLeftCircle->y() - _topRightCircle->y(),2)); // eucledian distance
    
        qreal distY = sqrt(pow(_topLeftCircle->x() - _bottomLeftCircle->x(),2) +
                           pow(_topLeftCircle->y() - _bottomLeftCircle->y(),2)); // eucledian distance
    
    
        return QRectF(qMin(_topLeftCircle->pos().x(), _topRightCircle->pos().x()) ,
                      qMin(_topLeftCircle->pos().y(), _bottomLeftCircle->pos().y()),
                      distX, distY);
    }
    
    void MoveItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
         painter->drawImage(boundingRect(), qimage_);
        Q_UNUSED(widget);
    }
    

    gif example:
    tenor.gif

    regards, max



  • This post is deleted!


  • I've implemented part of the algorithm for eBottomRight and eTopLeft, but it still works very bad and doesn't work for BottomLeft and TopRight points. If i find a solution and it will be look like Krita or PureRef resize behaviour I will post here.

    void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        auto pos = mapToScene(event->pos() + _shiftMouseCoords);
    
        qreal xl = (pos.x() == 0) ? .1 : pos.x();
        qreal yl = (pos.y() == 0) ? .1 : pos.y();
    
        qreal arl = qAbs(xl / yl);
    
        if (circlePos_ == eBottomRight) {
            if (arl > aspectRatio_) {
                pos.setX(yl * aspectRatio_);
            } else {
                pos.setY(xl / aspectRatio_);
    
            }
        }
    
        if (circlePos_ == eTopLeft) {
            LOG_WARNING(logger, "Circle Pos: ", circlePos_, ", ", pos.x(), " ", pos.y());
            LOG_WARNING(logger, "Init Aspect Ratio: ", aspectRatio_, ", Current AspectRatio:", arl);
            if (arl > aspectRatio_) {
                LOG_DEBUG(logger, "> Before: ", pos.x(), ", ", pos.y());
                pos.setY(xl / aspectRatio_);
                LOG_DEBUG(logger, "> After: ", pos.x(), ", ", pos.y());
            } else {
                LOG_DEBUG(logger, "< Before: ", pos.x(), ", ", pos.y());
                pos.setX(yl * aspectRatio_);
                LOG_DEBUG(logger, "< After: ", pos.x(), ", ", pos.y());
            }
        }
    
        setPos(pos);
        emit circleMoved();
    }
    

    example gif:
    https://media.giphy.com/media/7XBNv61efV7S9DbgJO/giphy.gif



  • @angeleyes said in QGraphicsItem resize with keeping aspect ratio:

    I've implemented part of the algorithm for eBottomRight and eTopLeft, but it still works very bad and doesn't work for BottomLeft and TopRight points

    How should it look like? What should your circles do? Resize or move the image?
    Like here (https://youtu.be/4tsL3wJtNqQ?t=182)?

    You current version (the gif) doesn't look too bad except these "jumps".

    If you just want to resize while keeping the aspect ratio why don't you save the ratio and set it to your width or height respectively while moving your circle in X or Y direction? You don't need to calculate the position of every single circle... Or am I missing something?



  • @Pl45m4 said in QGraphicsItem resize with keeping aspect ratio:

    Like here (https://youtu.be/4tsL3wJtNqQ?t=182)?

    exactly(resize with shift modificator)

    You current version (the gif) doesn't look too bad except these "jumps".

    I think my code and algorithm is not correct.

    If you just want to resize while keeping the aspect ratio why don't you save the ratio and set it to your width or height respectively while moving your circle in X or Y direction? You don't need to calculate the position of every single circle... Or am I missing something?

    Yes I calculate only pressed circle

    thanks for reply



  • I found a solution using vector math.(thanks to my colleague Dima Chernikov)

    ABCD - our picture.

    K' - cursor point.

    D2 - the point we are looking for(new position of D)

    gif example: https://media.giphy.com/media/uffXKjNNy5ykzpvsR2/giphy.gif

    (circlePos_ == eBottomLeft) in code
    1488.jpg

    code: (I will most likely redo it later using templates. but now it is more clear for understanding)

    void MovableCircle::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        auto pos = mapToScene(event->pos() + _shiftMouseCoords);
    
        qreal xl = pos.x();
        qreal yl = pos.y();
        auto rect = parentItem()->boundingRect();
    
        QPointF a(rect.x(), rect.y());
        QPointF b(rect.x() + rect.width(), rect.y());
        QPointF c(rect.x() + rect.width(), rect.y() + rect.height());
        QPointF d(rect.x(), rect.y() + rect.height());
    
    
        if (circlePos_ == eTopRight) {
            Vec2d dc(c.x()-d.x(), c.y()-d.y());
            Vec2d cb(b.x()-c.x(), b.y()-c.y());
            Vec2d db(b.x()-d.x(), b.y()-d.y());
            Vec2d dk(pos.x()-d.x(), pos.y()-d.y());
    
            auto dc_len = dc.length();
            auto cb_len = cb.length();
            auto db_len = db.length();
            auto dk_len = dk.length();
    
            auto dkdb_dot = Vec2d<qreal>::dot(db, dk);
            auto cos_kdb = dkdb_dot/(db_len*dk_len);
            auto dd2_len = dk_len * cos_kdb;
    
            auto x =(dd2_len * dc_len) / (std::sqrt(cb_len * cb_len + dc_len * dc_len));
            auto y = std::sqrt(dd2_len * dd2_len - x * x);
    
            if (x < 10 || y < 10) return;
            pos.setX(d.x()+x);
            pos.setY(d.y()-y);
        }
    
        if (circlePos_ == eBottomRight) {
            Vec2d ad(d.x()-a.x(), d.y()-a.y());
            Vec2d dc(c.x()-d.x(), c.y()-d.y());
            Vec2d ac(c.x()-a.x(), c.y()-a.y());
            Vec2d ak(pos.x()-a.x(), pos.y()-a.y());
    
            auto ad_len = ad.length();
            auto dc_len = dc.length();
            auto ac_len = ac.length();
            auto ak_len = ak.length();
    
            auto akac_dot = Vec2d<qreal>::dot(ac, ak);
            auto cos_kac = akac_dot/(ac_len*ak_len);
            auto ad2_len = ak_len * cos_kac;
    
            auto x =(ad2_len * dc_len) / (std::sqrt(ad_len * ad_len + dc_len * dc_len));
            auto y = std::sqrt(ad2_len * ad2_len - x * x);
    
            if (x < 10 || y < 10) return;
            pos.setX(a.x()+x);
            pos.setY(a.y()+y);
        }
    
        if (circlePos_ == eTopLeft) {
            qDebug()<<this->parentItem()->boundingRect();
            Vec2d cb(b.x()-c.x(), b.y()-c.y());
            Vec2d ba(a.x()-b.x(), a.y()-b.y());
            Vec2d ca(a.x()-c.x(), a.y()-c.y());
            Vec2d ck(pos.x()-c.x(), pos.y()-c.y());
    
            auto cb_len = cb.length();
            auto ba_len = ba.length();
            auto ca_len = ca.length();
            auto ck_len = ck.length();
    
            auto ckca_dot = Vec2d<qreal>::dot(ca, ck);
            auto cos_kca = ckca_dot/(ca_len*ck_len);
            auto cd2_len = ck_len * cos_kca;
    
            auto y =(cd2_len * cb_len) / (std::sqrt(ba_len * ba_len + cb_len * cb_len));
            auto x = std::sqrt(cd2_len * cd2_len - y * y);
    
            if (x < 10 || y < 10) return;
            pos.setX(c.x()-x);
            pos.setY(c.y()-y);
        }
    
        if (circlePos_ == eBottomLeft) {
            qDebug()<<this->parentItem()->boundingRect();
            Vec2d ba(a.x()-b.x(), a.y()-b.y());
            Vec2d ad(d.x()-a.x(), d.y()-a.y());
            Vec2d bd(d.x()-b.x(), d.y()-b.y());
            Vec2d bk(pos.x()-b.x(), pos.y()-b.y());
    
            auto ba_len = ba.length();
            auto ad_len = ad.length();
            auto bd_len = bd.length();
            auto bk_len = bk.length();
    
            auto bkbd_dot = Vec2d<qreal>::dot(bd, bk);
            auto cos_kdb = bkbd_dot/(bd_len*bk_len);
            auto bd2_len = bk_len * cos_kdb;
    
            auto x =(bd2_len * ba_len) / (std::sqrt(ad_len * ad_len + ba_len * ba_len));
            auto y = std::sqrt(bd2_len * bd2_len - x * x);
    
            if (x < 10 || y < 10) return;
            pos.setX(b.x()-x);
            pos.setY(b.y()+y);
        }
    
        setPos(pos);
        emit circleMoved();
    }
    

Log in to reply