]> git.sesse.net Git - kdenlive/blob - src/onmonitoritems/rotoscoping/splineitem.cpp
06228b02b1f8d7e566f29ed3ef64281c86044b47
[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     if (points.isEmpty())
64         return;
65
66     QPainterPath path(points.at(0).p);
67     int j;
68     for (int i = 0; i < points.count(); ++i) {
69         new BPointItem(points.at(i), this);
70         j = (i + 1) % points.count();
71         path.cubicTo(points.at(i).h2, points.at(j).h1, points.at(j).p);
72     }
73     setPath(path);
74 }
75
76 int SplineItem::type() const
77 {
78     return Type;
79 }
80
81 void SplineItem::updateSpline()
82 {
83     QPainterPath path(qgraphicsitem_cast<BPointItem *>(childItems().at(0))->getPoint().p);
84
85     BPoint p1, p2;
86     int j;
87     for (int i = 0; i < childItems().count(); ++i) {
88         j = (i + 1) % childItems().count();
89         p1 = qgraphicsitem_cast<BPointItem *>(childItems().at(i))->getPoint();
90         p2 = qgraphicsitem_cast<BPointItem *>(childItems().at(j))->getPoint();
91         path.cubicTo(p1.h2, p2.h1, p2.p);
92     }
93     setPath(path);
94
95     emit changed();
96 }
97
98 QList <BPoint> SplineItem::getPoints()
99 {
100     QList <BPoint> points;
101     foreach (QGraphicsItem *child, childItems())
102         points << qgraphicsitem_cast<BPointItem *>(child)->getPoint();
103     return points;
104 }
105
106 void SplineItem::removeChild(QGraphicsItem* child)
107 {
108     if (childItems().count() > 2) {
109         scene()->removeItem(child);
110         delete child;
111         updateSpline();
112     }
113 }
114
115 void SplineItem::mousePressEvent(QGraphicsSceneMouseEvent* event)
116 {
117     QGraphicsItem::mousePressEvent(event);
118
119     if (!event->isAccepted()) {
120         QRectF r(event->scenePos() - QPointF(6, 6), QSizeF(12, 12));
121         if (path().intersects(r) && !path().contains(r)) {
122             double t = 0;
123             BPointItem *i1, *i2;
124             BPoint p, p1, p2;
125             int ix = getClosestPointOnCurve(event->scenePos(), &t);
126             i1 = qgraphicsitem_cast<BPointItem *>(childItems().at(ix));
127             i2 = qgraphicsitem_cast<BPointItem *>(childItems().at((ix + 1) % childItems().count()));
128             p1 = i1->getPoint();
129             p2 = i2->getPoint();
130
131             deCasteljau(&p1, &p2, &p, t);
132
133             i1->setPoint(p1);
134             i2->setPoint(p2);
135
136 #if QT_VERSION >= 0x040600
137             BPointItem *i = new BPointItem(p, this);
138             i->stackBefore(i2);
139 #else
140             QList <BPoint> points;
141             BPointItem *item;
142             while (childItems().count()) {
143                 item = qgraphicsitem_cast<BPointItem *>(childItems().takeFirst());
144                 points.append(item->getPoint());
145                 delete item;
146             }
147             int j = 0;
148             for ( ; j < points.count(); ++j) {
149                 if (j == ix + 1)
150                     new BPointItem(p, this);
151                 new BPointItem(points.at(j), this);
152             }
153             if (j == ix + 1)
154                 new BPointItem(p, this);
155 #endif
156
157         }
158     }
159 }
160
161 void SplineItem::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
162 {
163     QGraphicsItem::mouseMoveEvent(event);
164 }
165
166 void SplineItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
167 {
168     QGraphicsItem::mouseReleaseEvent(event);
169 }
170
171 void SplineItem::hoverMoveEvent(QGraphicsSceneHoverEvent* event)
172 {
173     QGraphicsItem::hoverMoveEvent(event);
174
175     QRectF r(event->scenePos() - QPointF(6, 6), QSizeF(12, 12));
176     if (path().intersects(r) && !path().contains(r))
177         setCursor(QCursor(Qt::PointingHandCursor));
178     else
179         unsetCursor();
180 }
181
182 int SplineItem::getClosestPointOnCurve(QPointF point, double *tFinal)
183 {
184     // TODO: proper minDiff
185     qreal diff = 10000, param = 0;
186     BPoint p1, p2;
187     int curveSegment = 0, j;
188     for (int i = 0; i < childItems().count(); ++i) {
189         j = (i + 1) % childItems().count();
190         p1 = qgraphicsitem_cast<BPointItem *>(childItems().at(i))->getPoint();
191         p2 = qgraphicsitem_cast<BPointItem *>(childItems().at(j))->getPoint();
192         QPolygonF bounding = QPolygonF() << p1.p << p1.h2 << p2.h1 << p2.p;
193         QPointF cl = closestPointInRect(point, bounding.boundingRect());
194         qreal d = (point - cl).manhattanLength();
195
196         if (d > diff)
197             continue;
198
199         Point2 b[4], p;
200         double t;
201
202         b[0].x = p1.p.x();
203         b[0].y = p1.p.y();
204         b[1].x = p1.h2.x();
205         b[1].y = p1.h2.y();
206         b[2].x = p2.h1.x();
207         b[2].y = p2.h1.y();
208         b[3].x = p2.p.x();
209         b[3].y = p2.p.y();
210
211         p.x = point.x();
212         p.y = point.y();
213
214         Point2 n = NearestPointOnCurve(p, b, &t);
215         cl.setX(n.x);
216         cl.setY(n.y);
217
218         d = (point - cl).manhattanLength();
219         if (d < diff) {
220             diff = d;
221             param = t;
222             curveSegment = i;
223         }
224     }
225
226     *tFinal = param;
227     return curveSegment;
228 }
229
230 #include "splineitem.moc"