Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
bezier.cpp
Go to the documentation of this file.
1/*
5 * Authors:
6 * MenTaLguY <mental@rydia.net>
7 * Michael Sloan <mgsloan@gmail.com>
8 * Nathan Hurst <njh@njhurst.com>
9 * Krzysztof Kosiński <tweenk.pl@gmail.com>
10 *
11 * Copyright 2007-2015 Authors
12 *
13 * This library is free software; you can redistribute it and/or
14 * modify it either under the terms of the GNU Lesser General Public
15 * License version 2.1 as published by the Free Software Foundation
16 * (the "LGPL") or, at your option, under the terms of the Mozilla
17 * Public License Version 1.1 (the "MPL"). If you do not alter this
18 * notice, a recipient may use your version of this file under either
19 * the MPL or the LGPL.
20 *
21 * You should have received a copy of the LGPL along with this library
22 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * You should have received a copy of the MPL along with this library
25 * in the file COPYING-MPL-1.1
26 *
27 * The contents of this file are subject to the Mozilla Public License
28 * Version 1.1 (the "License"); you may not use this file except in
29 * compliance with the License. You may obtain a copy of the License at
30 * http://www.mozilla.org/MPL/
31 *
32 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
33 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
34 * the specific language governing rights and limitations.
35 *
36 */
37
38#include <2geom/bezier.h>
39#include <2geom/solver.h>
40#include <2geom/concepts.h>
41#include <2geom/choose.h>
42
43// Workaround for GCC null-analyser false positives.
44// See https://gitlab.com/inkscape/lib2geom/-/issues/64
45#if defined(__GNUC__) && !defined(__clang__)
46#pragma GCC diagnostic ignored "-Wnonnull"
47#endif
48
49namespace Geom {
50
51std::vector<Coord> Bezier::valueAndDerivatives(Coord t, unsigned n_derivs) const {
52 /* This is inelegant, as it uses several extra stores. I think there might be a way to
53 * evaluate roughly in situ. */
54
55 // initialize return vector with zeroes, such that we only need to replace the non-zero derivs
56 std::vector<Coord> val_n_der(n_derivs + 1, Coord(0.0));
57
58 // initialize temp storage variables
59 std::valarray<Coord> d_(order()+1);
60 for(unsigned i = 0; i < size(); i++) {
61 d_[i] = c_[i];
62 }
63
64 unsigned nn = n_derivs + 1;
65 if(n_derivs > order()) {
66 nn = order()+1; // only calculate the non zero derivs
67 }
68 for(unsigned di = 0; di < nn; di++) {
69 //val_n_der[di] = (casteljau_subdivision(t, &d_[0], NULL, NULL, order() - di));
70 val_n_der[di] = bernstein_value_at(t, &d_[0], order() - di);
71 for(unsigned i = 0; i < order() - di; i++) {
72 d_[i] = (order()-di)*(d_[i+1] - d_[i]);
73 }
74 }
75
76 return val_n_der;
77}
78
79void Bezier::subdivide(Coord t, Bezier *left, Bezier *right) const
80{
81 if (left) {
82 left->c_.resize(size());
83 if (right) {
84 right->c_.resize(size());
85 casteljau_subdivision<double>(t, &c_[0],
86 &left->c_[0], &right->c_[0], order());
87 } else {
88 casteljau_subdivision<double>(t, &c_[0],
89 &left->c_[0], nullptr, order());
90 }
91 } else if (right) {
92 right->c_.resize(size());
93 casteljau_subdivision<double>(t, &c_[0],
94 nullptr, &right->c_[0], order());
95 }
96}
97
98std::pair<Bezier, Bezier> Bezier::subdivide(Coord t) const
99{
100 std::pair<Bezier, Bezier> ret;
101 subdivide(t, &ret.first, &ret.second);
102 return ret;
103}
104
105std::vector<Coord> Bezier::roots() const
106{
107 std::vector<Coord> solutions;
109 std::sort(solutions.begin(), solutions.end());
110 return solutions;
111}
112
113std::vector<Coord> Bezier::roots(Interval const &ivl) const
114{
115 std::vector<Coord> solutions;
116 find_bernstein_roots(&c_[0], order(), solutions, 0, ivl.min(), ivl.max());
117 std::sort(solutions.begin(), solutions.end());
118 return solutions;
119}
120
122{
123 Bezier fd(Order(order() - k));
124 int n = fd.size();
125
126 for (int i = 0; i < n; i++) {
127 fd[i] = 0;
128 int b = (i & 1) ? -1 : 1; // b = (-1)^j binomial(n, j - i)
129 for (int j = i; j < n; j++) {
130 fd[i] += c_[j] * b;
131 binomial_increment_k(b, n, j - i);
132 b = -b;
133 }
134 }
135 return fd;
136}
137
139{
140 Bezier ed(Order(order()+1));
141 unsigned n = size();
142 ed[0] = c_[0];
143 ed[n] = c_[n-1];
144 for(unsigned i = 1; i < n; i++) {
145 ed[i] = (i*c_[i-1] + (n - i)*c_[i])/(n);
146 }
147 return ed;
148}
149
151{
152 if(order() == 0) return *this;
153 Bezier ed(Order(order()-1));
154 unsigned n = size();
155 ed[0] = c_[0];
156 ed[n-1] = c_[n]; // ensure exact endpoints
157 unsigned middle = n/2;
158 for(unsigned i = 1; i < middle; i++) {
159 ed[i] = (n*c_[i] - i*ed[i-1])/(n-i);
160 }
161 for(unsigned i = n-1; i >= middle; i--) {
162 ed[i] = (n*c_[i] - i*ed[n-i])/(i);
163 }
164 return ed;
165}
166
167Bezier Bezier::elevate_to_degree(unsigned newDegree) const
168{
169 Bezier ed = *this;
170 for(unsigned i = degree(); i < newDegree; i++) {
171 ed = ed.elevate_degree();
172 }
173 return ed;
174}
175
177{
178 if(order() == 0) return *this;
179 unsigned n = order();
180 Bezier b(Order(n-1));
181 for(unsigned i = 0; i < n; i++) {
182 b[i] = (n*c_[i+1])/(i+1);
183 }
184 return b;
185}
186
188{
189 SBasis sb;
190 bezier_to_sbasis(sb, *this);
191 return sb;
192 //return bezier_to_sbasis(&c_[0], order());
193}
194
196{
197 if (c_.size() > other.size()) {
198 c_ += other.elevate_to_degree(degree()).c_;
199 } else if (c_.size() < other.size()) {
200 *this = elevate_to_degree(other.degree());
201 c_ += other.c_;
202 } else {
203 c_ += other.c_;
204 }
205 return *this;
206}
207
209{
210 if (c_.size() > other.size()) {
211 c_ -= other.elevate_to_degree(degree()).c_;
212 } else if (c_.size() < other.size()) {
213 *this = elevate_to_degree(other.degree());
214 c_ -= other.c_;
215 } else {
216 c_ -= other.c_;
217 }
218 return *this;
219}
220
221Bezier operator*(Bezier const &f, Bezier const &g)
222{
223 int m = f.order();
224 int n = g.order();
225 Bezier h(Bezier::Order(m+n));
226 // h_k = sum_(i+j=k) (m i)f_i (n j)g_j / (m+n k)
227
228 int mci = 1;
229 for (int i = 0; i <= m; i++) {
230 double const fi = mci * f[i];
231
232 int ncj = 1;
233 for (int j = 0; j <= n; j++) {
234 h[i + j] += fi * ncj * g[j];
235 binomial_increment_k(ncj, n, j);
236 }
237
238 binomial_increment_k(mci, m, i);
239 }
240
241 int mnck = 1;
242 for (int k = 0; k <= m + n; k++) {
243 h[k] /= mnck;
244 binomial_increment_k(mnck, m + n, k);
245 }
246
247 return h;
248}
249
250Bezier portion(Bezier const &a, double from, double to)
251{
252 Bezier ret(a);
253
254 bool reverse_result = false;
255 if (from > to) {
256 std::swap(from, to);
257 reverse_result = true;
258 }
259
260 do {
261 if (from == 0) {
262 if (to == 1) {
263 break;
264 }
265 casteljau_subdivision<double>(to, &ret.c_[0], &ret.c_[0], nullptr, ret.order());
266 break;
267 }
268 casteljau_subdivision<double>(from, &ret.c_[0], NULL, &ret.c_[0], ret.order());
269 if (to == 1) break;
270 casteljau_subdivision<double>((to - from) / (1 - from), &ret.c_[0], &ret.c_[0], NULL, ret.order());
271 // to protect against numerical inaccuracy in the above expression, we manually set
272 // the last coefficient to a value evaluated directly from the original polynomial
273 ret.c_[ret.order()] = a.valueAt(to);
274 } while(0);
275
276 if (reverse_result) {
277 std::reverse(&ret.c_[0], &ret.c_[0] + ret.c_.size());
278 }
279 return ret;
280}
281
283{
284 //if(a.order() == 1) return Bezier(0.0);
285 if(a.order() == 1) return Bezier(a.c_[1]-a.c_[0]);
286 Bezier der(Bezier::Order(a.order()-1));
287
288 for(unsigned i = 0; i < a.order(); i++) {
289 der.c_[i] = a.order()*(a.c_[i+1] - a.c_[i]);
290 }
291 return der;
292}
293
295{
296 Bezier inte(Bezier::Order(a.order()+1));
297
298 inte[0] = 0;
299 for(unsigned i = 0; i < inte.order(); i++) {
300 inte[i+1] = inte[i] + a[i]/(inte.order());
301 }
302 return inte;
303}
304
306{
307 return Interval::from_array(&b.c_[0], b.size());
308}
309
311{
312 OptInterval ret(b.at0(), b.at1());
313 std::vector<Coord> r = derivative(b).roots();
314 for (double i : r) {
315 ret->expandTo(b.valueAt(i));
316 }
317 return ret;
318}
319
321{
322 //return bounds_local(b.toSBasis(), i);
323 if (i) {
324 return bounds_fast(portion(b, i->min(), i->max()));
325 } else {
326 return OptInterval();
327 }
328}
329
330/*
331 * The general bézier of degree n is
332 *
333 * p(t) = sum_{i = 0...n} binomial(n, i) t^i (1 - t)^(n - i) x[i]
334 *
335 * It can be written explicitly as a polynomial in t as
336 *
337 * p(t) = sum_{i = 0...n} binomial(n, i) t^i [ sum_{j = 0...i} binomial(i, j) (-1)^(i - j) x[j] ]
338 *
339 * Its derivative is
340 *
341 * p'(t) = n sum_{i = 1...n} binomial(n - 1, i - 1) t^(i - 1) [ sum_{j = 0...i} binomial(i, j) (-1)^(i - j) x[j] ]
342 *
343 * This is used by the various specialisations below as an optimisation for low degree n <= 3.
344 * In the remaining cases, the generic implementation is used which resorts to iteration.
345 */
346
348{
349 range.expandTo(x2);
350
351 if (range.contains(x1)) {
352 // The interval contains all control points, and therefore the entire curve.
353 return;
354 }
355
356 // p'(t) / 2 = at + b
357 auto a = (x2 - x1) - (x1 - x0);
358 auto b = x1 - x0;
359
360 // t = -b / a
361 if (std::abs(a) > EPSILON) {
362 auto t = -b / a;
363 if (t > 0.0 && t < 1.0) {
364 auto s = 1.0 - t;
365 auto x = s * s * x0 + 2 * s * t * x1 + t * t * x2;
366 range.expandTo(x);
367 }
368 }
369}
370
372{
373 range.expandTo(x3);
374
375 if (range.contains(x1) && range.contains(x2)) {
376 // The interval contains all control points, and therefore the entire curve.
377 return;
378 }
379
380 // p'(t) / 3 = at^2 + 2bt + c
381 auto a = (x3 - x0) - 3 * (x2 - x1);
382 auto b = (x2 - x1) - (x1 - x0);
383 auto c = x1 - x0;
384
385 auto expand = [&] (Coord t) {
386 if (t > 0.0 && t < 1.0) {
387 auto s = 1.0 - t;
388 auto x = s * s * s * x0 + 3 * s * s * t * x1 + 3 * t * t * s * x2 + t * t * t * x3;
389 range.expandTo(x);
390 }
391 };
392
393 // t = (-b ± sqrt(b^2 - ac)) / a
394 if (std::abs(a) < EPSILON) {
395 if (std::abs(b) > EPSILON) {
396 expand(-c / (2 * b));
397 }
398 } else {
399 auto d2 = b * b - a * c;
400 if (d2 >= 0.0) {
401 auto bsign = b >= 0.0 ? 1 : -1;
402 auto tmp = -(b + bsign * std::sqrt(d2));
403 expand(tmp / a);
404 expand(c / tmp); // Using Vieta's formula: product of roots == c/a
405 }
406 }
407}
408
409} // namespace Geom
410
411/*
412 Local Variables:
413 mode:c++
414 c-file-style:"stroustrup"
415 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
416 indent-tabs-mode:nil
417 fill-column:99
418 End:
419*/
420// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Bernstein-Bezier polynomial.
Calculation of binomial cefficients.
Polynomial in Bernstein-Bezier basis.
Definition bezier.h:126
Bezier deflate() const
Definition bezier.cpp:176
std::vector< Coord > valueAndDerivatives(Coord t, unsigned n_derivs) const
Definition bezier.cpp:51
Bezier elevate_to_degree(unsigned newDegree) const
Definition bezier.cpp:167
unsigned degree() const
Definition bezier.h:146
void subdivide(Coord t, Bezier *left, Bezier *right) const
Definition bezier.cpp:79
Bezier forward_difference(unsigned k) const
Definition bezier.cpp:121
void find_bezier_roots(std::vector< double > &solutions, double l, double r) const
std::vector< Coord > roots() const
Definition bezier.cpp:105
Bezier reduce_degree() const
Definition bezier.cpp:150
Bezier elevate_degree() const
Definition bezier.cpp:138
Bezier & operator+=(double v)
Definition bezier.h:305
unsigned size() const
Definition bezier.h:147
Coord at0() const
Definition bezier.h:272
SBasis toSBasis() const
Definition bezier.cpp:187
std::valarray< Coord > c_
Definition bezier.h:128
unsigned order() const
Definition bezier.h:145
Coord valueAt(double t) const
Definition bezier.h:277
Bezier & operator-=(double v)
Definition bezier.h:309
Coord at1() const
Definition bezier.h:274
constexpr void expandTo(C val)
Extend the interval to include the given number.
constexpr C min() const
constexpr C max() const
constexpr bool contains(C val) const
Check whether the interval includes this number.
Range of real numbers that is never empty.
Definition interval.h:59
static Interval from_array(Coord const *c, unsigned n)
Create an interval from a C-style array of values it should contain.
Definition interval.h:86
Range of real numbers that can be empty.
Definition interval.h:199
Polynomial in symmetric power basis.
Definition sbasis.h:70
Template concepts used by 2Geom.
double c[8][4]
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
Various utility functions.
Definition affine.h:22
OptInterval bounds_exact(Bezier const &b)
Definition bezier.cpp:310
OptInterval bounds_local(Bezier const &b, OptInterval const &i)
Definition bezier.cpp:320
constexpr void binomial_increment_k(T &b, int n, int k)
Given a multiple of binomial(n, k), modify it to the same multiple of binomial(n, k + 1).
Definition choose.h:61
void find_bernstein_roots(double const *w, unsigned degree, std::vector< double > &solutions, unsigned depth, double left_t=0, double right_t=1, bool use_secant=true)
Bezier operator*(Bezier const &f, Bezier const &g)
Definition bezier.cpp:221
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
Bezier integral(Bezier const &a)
Definition bezier.cpp:294
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
T bernstein_value_at(double t, T const *c_, unsigned n)
Compute the value of a Bernstein-Bezier polynomial.
Definition bezier.h:55
OptInterval bounds_fast(Bezier const &b)
Definition bezier.cpp:305
void bezier_expand_to_image(Interval &range, Coord x0, Coord x1, Coord x2)
Expand an interval to the image of a Bézier-Bernstein polynomial, assuming it already contains the in...
Definition bezier.cpp:347
SBasis bezier_to_sbasis(Coord const *handles, unsigned order)
std::vector< double > & solutions
Finding roots of Bernstein-Bezier polynomials.