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