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