]> git.sesse.net Git - kdenlive/blob - src/onmonitoritems/rotoscoping/splineitem.cpp
64eedb03c347190cd293bef18b15b3f8abd87f5b
[kdenlive] / src / onmonitoritems / rotoscoping / splineitem.cpp
1 /***************************************************************************
2  *   Copyright (C) 2011 by Till Theato (root@ttill.de)                     *
3  *   This file is part of Kdenlive (www.kdenlive.org).                     *
4  *                                                                         *
5  *   Kdenlive is free software: you can redistribute it and/or modify      *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation, either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   Kdenlive is distributed in the hope that it will be useful,           *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
17  ***************************************************************************/
18
19 #include "splineitem.h"
20 #include "bpointitem.h"
21 #include "nearestpoint.h"
22
23 #include <QGraphicsScene>
24 #include <QCursor>
25 #include <QGraphicsSceneMouseEvent>
26
27
28 inline QPointF closestPointInRect(QPointF point, QRectF rect)
29 {
30     QPointF closest;
31     rect = rect.normalized();
32     closest.setX(qBound<qreal>(rect.left(), point.x(), rect.right()));
33     closest.setY(qBound<qreal>(rect.top(), point.y(), rect.bottom()));
34     return closest;
35 }
36
37 void deCasteljau(BPoint *p1, BPoint *p2, BPoint *res, double t)
38 {
39     QPointF ab, bc, cd;
40
41     ab = QLineF(p1->p, p1->h2).pointAt(t);
42     bc = QLineF(p1->h2, p2->h1).pointAt(t);
43     cd = QLineF(p2->h1, p2->p).pointAt(t);
44
45     res->h1 = QLineF(ab, bc).pointAt(t);
46     res->h2 = QLineF(bc, cd).pointAt(t);
47     res->p = QLineF(res->h1, res->h2).pointAt(t);
48
49     p1->h2 = ab;
50     p2->h1 = cd;
51 }
52
53
54 SplineItem::SplineItem(const QList< BPoint >& points, QGraphicsItem* parent, QGraphicsScene *scene) :
55     QGraphicsPathItem(parent, scene)
56 {
57     QPen framepen(Qt::SolidLine);
58     framepen.setColor(Qt::yellow);
59     setPen(framepen);
60     setBrush(Qt::NoBrush);
61     setAcceptHoverEvents(true);
62
63     m_closed = true;
64     if (points.isEmpty()) {
65         m_closed = false;
66         grabMouse();
67         return;
68     }
69
70     QPainterPath path(points.at(0).p);
71     int j;
72     for (int i = 0; i < points.count(); ++i) {
73         new BPointItem(points.at(i), this);
74         j = (i + 1) % points.count();
75         path.cubicTo(points.at(i).h2, points.at(j).h1, points.at(j).p);
76     }
77     setPath(path);
78 }
79
80 int SplineItem::type() const
81 {
82     return Type;
83 }
84
85 void SplineItem::updateSpline()
86 {
87     QPainterPath path(qgraphicsitem_cast<BPointItem *>(childItems().at(0))->getPoint().p);
88
89     BPoint p1, p2;
90     int j;
91     for (int i = 0; i < childItems().count() - !m_closed; ++i) {
92         j = (i + 1) % childItems().count();
93         p1 = qgraphicsitem_cast<BPointItem *>(childItems().at(i))->getPoint();
94         p2 = qgraphicsitem_cast<BPointItem *>(childItems().at(j))->getPoint();
95         path.cubicTo(p1.h2, p2.h1, p2.p);
96     }
97     setPath(path);
98
99     if (m_closed)
100         emit changed();
101 }
102
103 QList <BPoint> SplineItem::getPoints()
104 {
105     QList <BPoint> points;
106     foreach (QGraphicsItem *child, childItems())
107         points << qgraphicsitem_cast<BPointItem *>(child)->getPoint();
108     return points;
109 }
110
111 void SplineItem::removeChild(QGraphicsItem* child)
112 {
113     if (childItems().count() > 2) {
114         scene()->removeItem(child);
115         delete child;
116         updateSpline();
117     }
118 }
119
120 void SplineItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
121 {
122     event->setAccepted(m_closed);
123     QGraphicsItem::mousePressEvent(event);
124
125     if (event->isAccepted())
126         return;
127
128     if (m_closed) {
129         QRectF r(event->scenePos() - QPointF(6, 6), QSizeF(12, 12));
130         if (event->button() == Qt::LeftButton && path().intersects(r) && !path().contains(r)) {
131             double t = 0;
132             BPointItem *i1, *i2;
133             BPoint p, p1, p2;
134             int ix = getClosestPointOnCurve(event->scenePos(), &t);
135             i1 = qgraphicsitem_cast<BPointItem *>(childItems().at(ix));
136             i2 = qgraphicsitem_cast<BPointItem *>(childItems().at((ix + 1) % childItems().count()));
137             p1 = i1->getPoint();
138             p2 = i2->getPoint();
139
140             deCasteljau(&p1, &p2, &p, t);
141
142             i1->setPoint(p1);
143             i2->setPoint(p2);
144
145 #if QT_VERSION >= 0x040600
146             BPointItem *i = new BPointItem(p, this);
147             i->stackBefore(i2);
148 #else
149             QList <BPoint> points;
150             BPointItem *item;
151             while (childItems().count()) {
152                 item = qgraphicsitem_cast<BPointItem *>(childItems().takeFirst());
153                 points.append(item->getPoint());
154                 delete item;
155             }
156             int j = 0;
157             for ( ; j < points.count(); ++j) {
158                 if (j == ix + 1)
159                     new BPointItem(p, this);
160                 new BPointItem(points.at(j), this);
161             }
162             if (j == ix + 1)
163                 new BPointItem(p, this);
164 #endif
165             updateSpline();
166         }
167     } else {
168         if (event->button() == Qt::RightButton) {
169             if (childItems().count() > 1) {
170                 // close the spline
171                 BPointItem *i1 = qgraphicsitem_cast<BPointItem *>(childItems().first());
172                 BPointItem *i2 = qgraphicsitem_cast<BPointItem *>(childItems().last());
173                 BPoint p1 = i1->getPoint();
174                 BPoint p2 = i2->getPoint();
175                 p1.h1 = QLineF(p1.p, p2.p).pointAt(.2);
176                 p2.h2 = QLineF(p1.p, p2.p).pointAt(.8);
177                 i1->setPoint(p1);
178                 i2->setPoint(p2);
179                 m_closed = true;
180                 ungrabMouse();
181                 updateSpline();
182             }
183         } else if (event->modifiers() == Qt::NoModifier) {
184             BPoint p;
185             p.p = p.h1 = p.h2 = event->scenePos();
186             if (childItems().count()) {
187                 BPointItem *i = qgraphicsitem_cast<BPointItem *>(childItems().last());
188                 BPoint prev = i->getPoint();
189                 prev.h2 = QLineF(prev.p, p.p).pointAt(.2);
190                 p.h1 = QLineF(prev.p, p.p).pointAt(.8);
191                 i->setPoint(prev);
192             }
193             new BPointItem(p, this);
194             updateSpline();
195         }
196     }
197 }
198
199 void SplineItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
200 {
201     QGraphicsItem::mouseMoveEvent(event);
202 }
203
204 void SplineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
205 {
206     QGraphicsItem::mouseReleaseEvent(event);
207 }
208
209 void SplineItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
210 {
211     QGraphicsItem::hoverMoveEvent(event);
212
213     QRectF r(event->scenePos() - QPointF(6, 6), QSizeF(12, 12));
214     if (path().intersects(r) && !path().contains(r))
215         setCursor(QCursor(Qt::PointingHandCursor));
216     else
217         unsetCursor();
218 }
219
220 int SplineItem::getClosestPointOnCurve(QPointF point, double *tFinal)
221 {
222     // TODO: proper minDiff
223     qreal diff = 10000, param = 0;
224     BPoint p1, p2;
225     int curveSegment = 0, j;
226     for (int i = 0; i < childItems().count(); ++i) {
227         j = (i + 1) % childItems().count();
228         p1 = qgraphicsitem_cast<BPointItem *>(childItems().at(i))->getPoint();
229         p2 = qgraphicsitem_cast<BPointItem *>(childItems().at(j))->getPoint();
230         QPolygonF bounding = QPolygonF() << p1.p << p1.h2 << p2.h1 << p2.p;
231         QPointF cl = closestPointInRect(point, bounding.boundingRect());
232         qreal d = (point - cl).manhattanLength();
233
234         if (d > diff)
235             continue;
236
237         Point2 b[4], p;
238         double t;
239
240         b[0].x = p1.p.x();
241         b[0].y = p1.p.y();
242         b[1].x = p1.h2.x();
243         b[1].y = p1.h2.y();
244         b[2].x = p2.h1.x();
245         b[2].y = p2.h1.y();
246         b[3].x = p2.p.x();
247         b[3].y = p2.p.y();
248
249         p.x = point.x();
250         p.y = point.y();
251
252         Point2 n = NearestPointOnCurve(p, b, &t);
253         cl.setX(n.x);
254         cl.setY(n.y);
255
256         d = (point - cl).manhattanLength();
257         if (d < diff) {
258             diff = d;
259             param = t;
260             curveSegment = i;
261         }
262     }
263
264     *tFinal = param;
265     return curveSegment;
266 }
267
268 #include "splineitem.moc"