]> git.sesse.net Git - kdenlive/blob - src/beziercurve/cubicbezierspline.cpp
Bezier Spline: Add button to reset current spline (without affecting any other settings)
[kdenlive] / src / beziercurve / cubicbezierspline.cpp
1 /***************************************************************************
2  *   Copyright (C) 2010 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 "cubicbezierspline.h"
20
21 #include <KDebug>
22
23 /** @brief For sorting a Bezier spline. Whether a is before b. */
24 static bool pointLessThan(const BPoint &a, const BPoint &b)
25 {
26     return a.p.x() < b.p.x();
27 }
28
29 CubicBezierSpline::CubicBezierSpline(QObject* parent) :
30         QObject(parent),
31         m_validSpline(false),
32         m_precision(100)
33 {
34     BPoint start;
35     start.p.setX(0);
36     start.p.setY(0);
37     start.h1.setX(0);
38     start.h1.setY(0);
39     start.h2.setX(0.1);
40     start.h2.setY(0.1);
41     m_points.append(start);
42
43     BPoint end;
44     end.p.setX(1);
45     end.p.setY(1);
46     end.h1.setX(0.9);
47     end.h1.setY(0.9);
48     end.h2.setX(1);
49     end.h2.setY(1);
50     m_points.append(end);
51 }
52
53 CubicBezierSpline::CubicBezierSpline(const CubicBezierSpline& spline, QObject* parent) :
54         QObject(parent)
55 {
56     m_precision = spline.m_precision;
57     m_points = spline.m_points;
58     m_validSpline = false;
59 }
60
61 CubicBezierSpline& CubicBezierSpline::operator=(const CubicBezierSpline& spline)
62 {
63     m_precision = spline.m_precision;
64     m_points = spline.m_points;
65     m_validSpline = false;
66     return *this;
67 }
68
69 void CubicBezierSpline::fromString(const QString& spline)
70 {
71     m_points.clear();
72     m_validSpline = false;
73
74     QStringList bpoints = spline.split('|');
75     foreach(const QString &bpoint, bpoints) {
76         QStringList points = bpoint.split('#');
77         QList <QPointF> values;
78         foreach(const QString &point, points) {
79             QStringList xy = point.split(';');
80             if (xy.count() == 2)
81                 values.append(QPointF(xy.at(0).toDouble(), xy.at(1).toDouble()));
82         }
83         if (values.count() == 3) {
84             BPoint bp;
85             bp.h1 = values.at(0);
86             bp.p  = values.at(1);
87             bp.h2 = values.at(2);
88             m_points.append(bp);
89         }
90     }
91
92     keepSorted();
93     validatePoints();
94 }
95
96 QString CubicBezierSpline::toString() const
97 {
98     QStringList spline;
99     foreach(const BPoint &p, m_points) {
100         spline << (QString::number(p.h1.x()) + ";" + QString::number(p.h1.y())
101                         + "#" + QString::number(p.p.x())  + ";" + QString::number(p.p.y())
102                         + "#" + QString::number(p.h2.x()) + ";" + QString::number(p.h2.y()));
103     }
104     return spline.join("|");
105 }
106
107 int CubicBezierSpline::setPoint(int ix, const BPoint& point)
108 {
109     m_points[ix] = point;
110     keepSorted();
111     validatePoints();
112     m_validSpline = false;
113     return indexOf(point); // in case it changed
114 }
115
116 QList <BPoint> CubicBezierSpline::points()
117 {
118     return m_points;
119 }
120
121 void CubicBezierSpline::removePoint(int ix)
122 {
123     m_points.removeAt(ix);
124     m_validSpline = false;
125 }
126
127 int CubicBezierSpline::addPoint(const BPoint& point)
128 {
129     m_points.append(point);
130     keepSorted();
131     validatePoints();
132     m_validSpline = false;
133     return indexOf(point);
134 }
135
136 void CubicBezierSpline::setPrecision(int pre)
137 {
138     if (pre != m_precision) {
139         m_precision = pre;
140         m_validSpline = false;
141     }
142 }
143
144 int CubicBezierSpline::getPrecision()
145 {
146     return m_precision;
147 }
148
149 qreal CubicBezierSpline::value(qreal x, bool cont)
150 {
151     update();
152
153     if (!cont)
154         m_i = m_spline.constBegin();
155     if (m_i != m_spline.constBegin())
156         --m_i;
157
158     double diff = qAbs(x - m_i.key());
159     double y = m_i.value();
160     while (m_i != m_spline.constEnd()) {
161         if (qAbs(x - m_i.key()) > diff)
162             break;
163
164         diff = qAbs(x - m_i.key());
165         y = m_i.value();
166         ++m_i;
167     }
168     return qBound((qreal)0.0, y, (qreal)1.0);
169 }
170
171 void CubicBezierSpline::validatePoints()
172 {
173     BPoint p1, p2;
174     for (int i = 0; i < m_points.count() - 1; ++i) {
175         p1 = m_points.at(i);
176         p2 = m_points.at(i+1);
177         p1.h2.setX(qBound(p1.p.x(), p1.h2.x(), p2.p.x()));
178         p2.h1.setX(qBound(p1.p.x(), p2.h1.x(), p2.p.x()));
179         m_points[i] = p1;
180         m_points[i+1] = p2;
181     }
182 }
183
184 void CubicBezierSpline::keepSorted()
185 {
186     qSort(m_points.begin(), m_points.end(), pointLessThan);
187 }
188
189 QPointF CubicBezierSpline::point(double t, const QList< QPointF >& points)
190 {
191     // coefficients from Bernstein basis polynomial of degree 3
192     double c1 = (1-t) * (1-t) * (1-t);
193     double c2 = 3 * t * (1-t) * (1-t);
194     double c3 = 3 * t * t * (1-t);
195     double c4 = t * t * t;
196     
197     return QPointF(points[0].x()*c1 + points[1].x()*c2 + points[2].x()*c3 + points[3].x()*c4,
198                    points[0].y()*c1 + points[1].y()*c2 + points[2].y()*c3 + points[3].y()*c4);
199 }
200
201 void CubicBezierSpline::update()
202 {
203     if (m_validSpline)
204         return;
205
206     m_validSpline = true;
207     m_spline.clear();
208
209     QList <QPointF> points;
210     QPointF p;
211     for (int i = 0; i < m_points.count() - 1; ++i) {
212         points.clear();
213         points << m_points.at(i).p
214                 << m_points.at(i).h2
215                 << m_points.at(i+1).h1
216                 << m_points.at(i+1).p;
217
218         int numberOfValues = (int)((points[3].x() - points[0].x()) * m_precision * 5);
219         if (numberOfValues == 0)
220             numberOfValues = 1;
221         double step = 1 / (double)numberOfValues;
222         double t = 0;
223         while (t <= 1) {
224             p = point(t, points);
225             m_spline.insert(p.x(), p.y());
226             t += step;
227         }
228     }
229 }
230
231 int CubicBezierSpline::indexOf(const BPoint& p)
232 {
233     if (m_points.indexOf(p) == -1) {
234         // point changed during validation process
235         for (int i = 0; i < m_points.count(); ++i) {
236             // this condition should be sufficient, too
237             if (m_points.at(i).p == p.p)
238                 return i;
239         }
240     } else {
241         return m_points.indexOf(p);
242     }
243     return -1;
244 }
245
246 #include "cubicbezierspline.moc"