]> git.sesse.net Git - kdenlive/blob - src/onmonitoritems/rotoscoping/splineitem.cpp
ee29f159dfaf571ba435c9ecc82601d6ad8efcad
[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 #include "kdenlivesettings.h"
23
24 #include <QGraphicsScene>
25 #include <QCursor>
26 #include <QGraphicsSceneMouseEvent>
27
28
29 inline QPointF closestPointInRect(QPointF point, QRectF rect)
30 {
31     QPointF closest;
32     rect = rect.normalized();
33     closest.setX(qBound<qreal>(rect.left(), point.x(), rect.right()));
34     closest.setY(qBound<qreal>(rect.top(), point.y(), rect.bottom()));
35     return closest;
36 }
37
38 void deCasteljau(BPoint *p1, BPoint *p2, BPoint *res, double t)
39 {
40     QPointF ab, bc, cd;
41
42     ab = QLineF(p1->p, p1->h2).pointAt(t);
43     bc = QLineF(p1->h2, p2->h1).pointAt(t);
44     cd = QLineF(p2->h1, p2->p).pointAt(t);
45
46     res->h1 = QLineF(ab, bc).pointAt(t);
47     res->h2 = QLineF(bc, cd).pointAt(t);
48     res->p = QLineF(res->h1, res->h2).pointAt(t);
49
50     p1->h2 = ab;
51     p2->h1 = cd;
52 }
53
54
55 SplineItem::SplineItem(const QList< BPoint >& points, QGraphicsItem* parent, QGraphicsScene *scene) :
56     QGraphicsPathItem(parent, scene),
57     m_closed(false),
58     m_editing(false)
59 {
60     QPen framepen(Qt::SolidLine);
61     framepen.setColor(Qt::yellow);
62     setPen(framepen);
63     setBrush(Qt::NoBrush);
64     setAcceptHoverEvents(true);
65
66     setPoints(points);
67 }
68
69 int SplineItem::type() const
70 {
71     return Type;
72 }
73
74 bool SplineItem::editing()
75 {
76     return m_editing;
77 }
78
79 void SplineItem::updateSpline(bool editing)
80 {
81     QPainterPath path(qgraphicsitem_cast<BPointItem *>(childItems().at(0))->getPoint().p);
82
83     BPoint p1, p2;
84     int j;
85     for (int i = 0; i < childItems().count() - !m_closed; ++i) {
86         j = (i + 1) % childItems().count();
87         p1 = qgraphicsitem_cast<BPointItem *>(childItems().at(i))->getPoint();
88         p2 = qgraphicsitem_cast<BPointItem *>(childItems().at(j))->getPoint();
89         path.cubicTo(p1.h2, p2.h1, p2.p);
90     }
91     setPath(path);
92
93     m_editing = editing;
94
95     if (m_closed && (!editing || KdenliveSettings::monitorscene_directupdate()))
96         emit changed(editing);
97 }
98
99 QList <BPoint> SplineItem::getPoints()
100 {
101     QList <BPoint> points;
102     foreach (QGraphicsItem *child, childItems())
103         points << qgraphicsitem_cast<BPointItem *>(child)->getPoint();
104     return points;
105 }
106
107 void SplineItem::setPoints(const QList< BPoint >& points)
108 {
109     if (points.count() < 2) {
110         m_closed = false;
111         grabMouse();
112         return;
113     } else {
114         ungrabMouse();
115         m_closed = true;
116     }
117
118     qDeleteAll(childItems());
119     childItems().clear();
120
121     QPainterPath path(points.at(0).p);
122     int j;
123     for (int i = 0; i < points.count(); ++i) {
124         new BPointItem(points.at(i), this);
125         j = (i + 1) % points.count();
126         path.cubicTo(points.at(i).h2, points.at(j).h1, points.at(j).p);
127     }
128     setPath(path);
129 }
130
131 void SplineItem::removeChild(QGraphicsItem* child)
132 {
133     if (childItems().count() > 2) {
134         scene()->removeItem(child);
135         delete child;
136         updateSpline();
137     }
138 }
139
140 void SplineItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
141 {
142     event->setAccepted(m_closed);
143     QGraphicsItem::mousePressEvent(event);
144
145     if (event->isAccepted())
146         return;
147
148     if (m_closed) {
149         QRectF r(event->scenePos() - QPointF(6, 6), QSizeF(12, 12));
150         if (event->button() == Qt::LeftButton && path().intersects(r) && !path().contains(r)) {
151             double t = 0;
152             BPointItem *i1, *i2;
153             BPoint p, p1, p2;
154             int ix = getClosestPointOnCurve(event->scenePos(), &t);
155             i1 = qgraphicsitem_cast<BPointItem *>(childItems().at(ix));
156             i2 = qgraphicsitem_cast<BPointItem *>(childItems().at((ix + 1) % childItems().count()));
157             p1 = i1->getPoint();
158             p2 = i2->getPoint();
159
160             deCasteljau(&p1, &p2, &p, t);
161
162             i1->setPoint(p1);
163             i2->setPoint(p2);
164
165 #if QT_VERSION >= 0x040600
166             BPointItem *i = new BPointItem(p, this);
167             i->stackBefore(i2);
168 #else
169             QList <BPoint> points;
170             BPointItem *item;
171             while (childItems().count()) {
172                 item = qgraphicsitem_cast<BPointItem *>(childItems().takeFirst());
173                 points.append(item->getPoint());
174                 delete item;
175             }
176             int j = 0;
177             for ( ; j < points.count(); ++j) {
178                 if (j == ix + 1)
179                     new BPointItem(p, this);
180                 new BPointItem(points.at(j), this);
181             }
182             if (j == ix + 1)
183                 new BPointItem(p, this);
184 #endif
185             updateSpline();
186         }
187     } else {
188         if (event->button() == Qt::RightButton) {
189             if (childItems().count() > 1) {
190                 // close the spline
191                 BPointItem *i1 = qgraphicsitem_cast<BPointItem *>(childItems().first());
192                 BPointItem *i2 = qgraphicsitem_cast<BPointItem *>(childItems().last());
193                 BPoint p1 = i1->getPoint();
194                 BPoint p2 = i2->getPoint();
195                 p1.h1 = QLineF(p1.p, p2.p).pointAt(.2);
196                 p2.h2 = QLineF(p1.p, p2.p).pointAt(.8);
197                 i1->setPoint(p1);
198                 i2->setPoint(p2);
199                 m_closed = true;
200                 ungrabMouse();
201                 updateSpline();
202             }
203         } else if (event->modifiers() == Qt::NoModifier) {
204             BPoint p;
205             p.p = p.h1 = p.h2 = event->scenePos();
206             if (childItems().count()) {
207                 BPointItem *i = qgraphicsitem_cast<BPointItem *>(childItems().last());
208                 BPoint prev = i->getPoint();
209                 prev.h2 = QLineF(prev.p, p.p).pointAt(.2);
210                 p.h1 = QLineF(prev.p, p.p).pointAt(.8);
211                 i->setPoint(prev);
212             }
213             new BPointItem(p, this);
214             updateSpline();
215         }
216     }
217 }
218
219 void SplineItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
220 {
221     QGraphicsItem::mouseMoveEvent(event);
222 }
223
224 void SplineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
225 {
226     QGraphicsItem::mouseReleaseEvent(event);
227 }
228
229 void SplineItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
230 {
231     QGraphicsItem::hoverMoveEvent(event);
232
233     QRectF r(event->scenePos() - QPointF(6, 6), QSizeF(12, 12));
234     if (path().intersects(r) && !path().contains(r))
235         setCursor(QCursor(Qt::PointingHandCursor));
236     else
237         unsetCursor();
238 }
239
240 int SplineItem::getClosestPointOnCurve(QPointF point, double *tFinal)
241 {
242     // TODO: proper minDiff
243     qreal diff = 10000, param = 0;
244     BPoint p1, p2;
245     int curveSegment = 0, j;
246     for (int i = 0; i < childItems().count(); ++i) {
247         j = (i + 1) % childItems().count();
248         p1 = qgraphicsitem_cast<BPointItem *>(childItems().at(i))->getPoint();
249         p2 = qgraphicsitem_cast<BPointItem *>(childItems().at(j))->getPoint();
250         QPolygonF bounding = QPolygonF() << p1.p << p1.h2 << p2.h1 << p2.p;
251         QPointF cl = closestPointInRect(point, bounding.boundingRect());
252         qreal d = (point - cl).manhattanLength();
253
254         if (d > diff)
255             continue;
256
257         Point2 b[4], p;
258         double t;
259
260         b[0].x = p1.p.x();
261         b[0].y = p1.p.y();
262         b[1].x = p1.h2.x();
263         b[1].y = p1.h2.y();
264         b[2].x = p2.h1.x();
265         b[2].y = p2.h1.y();
266         b[3].x = p2.p.x();
267         b[3].y = p2.p.y();
268
269         p.x = point.x();
270         p.y = point.y();
271
272         Point2 n = NearestPointOnCurve(p, b, &t);
273         cl.setX(n.x);
274         cl.setY(n.y);
275
276         d = (point - cl).manhattanLength();
277         if (d < diff) {
278             diff = d;
279             param = t;
280             curveSegment = i;
281         }
282     }
283
284     *tFinal = param;
285     return curveSegment;
286 }
287
288 #include "splineitem.moc"