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 preservedclass 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:
regards, max
-
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
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(); }