5dc170e84f106abfe09d0e30d62e9c26e8440591
1 #define INKSCAPE_LPE_POWERSTROKE_CPP
2 /** \file
3 * @brief PowerStroke LPE implementation. Creates curves with modifiable stroke width.
4 */
5 /* Authors:
6 * Johan Engelen <j.b.c.engelen@utwente.nl>
7 *
8 * Copyright (C) 2010 Authors
9 *
10 * Released under GNU GPL, read the file 'COPYING' for more information
11 */
13 #include "live_effects/lpe-powerstroke.h"
15 #include "sp-shape.h"
16 #include "display/curve.h"
18 #include <2geom/path.h>
19 #include <2geom/piecewise.h>
20 #include <2geom/sbasis-geometric.h>
21 #include <2geom/transforms.h>
22 #include <2geom/bezier-utils.h>
25 /// @TODO Move this to 2geom
26 namespace Geom {
27 namespace Interpolate {
29 class Interpolator {
30 public:
31 Interpolator() {};
32 virtual ~Interpolator() {};
34 // virtual Piecewise<D2<SBasis> > interpolateToPwD2Sb(std::vector<Point> points) = 0;
35 virtual Path interpolateToPath(std::vector<Point> points) = 0;
37 private:
38 Interpolator(const Interpolator&);
39 Interpolator& operator=(const Interpolator&);
40 };
42 class Linear : public Interpolator {
43 public:
44 Linear() {};
45 virtual ~Linear() {};
47 virtual Path interpolateToPath(std::vector<Point> points) {
48 Path path;
49 path.start( points.at(0) );
50 for (unsigned int i = 1 ; i < points.size(); ++i) {
51 path.appendNew<Geom::LineSegment>(points.at(i));
52 }
53 return path;
54 };
56 private:
57 Linear(const Linear&);
58 Linear& operator=(const Linear&);
59 };
61 // this class is terrible
62 class CubicBezierFit : public Interpolator {
63 public:
64 CubicBezierFit() {};
65 virtual ~CubicBezierFit() {};
67 virtual Path interpolateToPath(std::vector<Point> points) {
68 unsigned int n_points = points.size();
69 // worst case gives us 2 segment per point
70 int max_segs = 8*n_points;
71 Geom::Point * b = g_new(Geom::Point, max_segs);
72 Geom::Point * points_array = g_new(Geom::Point, 4*n_points);
73 for (unsigned i = 0; i < n_points; ++i) {
74 points_array[i] = points.at(i);
75 }
77 double tolerance_sq = 0; // this value is just a random guess
79 int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, n_points,
80 tolerance_sq, max_segs);
82 Geom::Path fit;
83 if ( n_segs > 0)
84 {
85 fit.start(b[0]);
86 for (int c = 0; c < n_segs; c++) {
87 fit.appendNew<Geom::CubicBezier>(b[4*c+1], b[4*c+2], b[4*c+3]);
88 }
89 }
90 g_free(b);
91 g_free(points_array);
92 return fit;
93 };
95 private:
96 CubicBezierFit(const CubicBezierFit&);
97 CubicBezierFit& operator=(const CubicBezierFit&);
98 };
100 /// @todo invent name for this class
101 class CubicBezierJohan : public Interpolator {
102 public:
103 CubicBezierJohan() {};
104 virtual ~CubicBezierJohan() {};
106 virtual Path interpolateToPath(std::vector<Point> points) {
107 Path fit;
108 fit.start(points.at(0));
109 for (unsigned int i = 1; i < points.size(); ++i) {
110 Point p0 = points.at(i-1);
111 Point p1 = points.at(i);
112 Point dx = Point(p1[X] - p0[X], 0);
113 fit.appendNew<CubicBezier>(p0+0.2*dx, p1-0.2*dx, p1);
114 }
115 return fit;
116 };
118 private:
119 CubicBezierJohan(const CubicBezierJohan&);
120 CubicBezierJohan& operator=(const CubicBezierJohan&);
121 };
123 } //namespace Interpolate
124 } //namespace Geom
126 namespace Inkscape {
127 namespace LivePathEffect {
129 LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
130 Effect(lpeobject),
131 offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this),
132 sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve."), "sort_points", &wr, this, true)
133 {
134 show_orig_path = true;
136 /// @todo offset_points are initialized with empty path, is that bug-save?
138 registerParameter( dynamic_cast<Parameter *>(&offset_points) );
139 registerParameter( dynamic_cast<Parameter *>(&sort_points) );
140 }
142 LPEPowerStroke::~LPEPowerStroke()
143 {
145 }
148 void
149 LPEPowerStroke::doOnApply(SPLPEItem *lpeitem)
150 {
151 std::vector<Geom::Point> points;
152 Geom::Path::size_type size = SP_SHAPE(lpeitem)->curve->get_pathvector().front().size_open();
153 points.push_back( Geom::Point(0,0) );
154 points.push_back( Geom::Point(0.5*size,0) );
155 points.push_back( Geom::Point(size,0) );
156 offset_points.param_set_and_write_new_value(points);
157 }
159 static bool compare_offsets (Geom::Point first, Geom::Point second)
160 {
161 return first[Geom::X] < second[Geom::X];
162 }
165 Geom::Piecewise<Geom::D2<Geom::SBasis> >
166 LPEPowerStroke::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
167 {
168 using namespace Geom;
170 offset_points.set_pwd2(pwd2_in);
172 Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
173 Piecewise<D2<SBasis> > n = rot90(der);
174 offset_points.set_pwd2_normal(n);
176 // see if we should treat the path as being closed.
177 bool closed_path = false;
178 if ( are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
179 closed_path = true;
180 }
182 Piecewise<D2<SBasis> > output;
183 if (!closed_path) {
184 // perhaps use std::list instead of std::vector?
185 std::vector<Geom::Point> ts(offset_points.data().size() + 2);
186 // first and last point coincide with input path (for now at least)
187 ts.front() = Point(pwd2_in.domain().min(),0);
188 ts.back() = Point(pwd2_in.domain().max(),0);
189 for (unsigned int i = 0; i < offset_points.data().size(); ++i) {
190 ts.at(i+1) = offset_points.data().at(i);
191 }
193 if (sort_points) {
194 sort(ts.begin(), ts.end(), compare_offsets);
195 }
197 // create stroke path where points (x,y) := (t, offset)
198 Geom::Interpolate::CubicBezierJohan interpolator;
199 Path strokepath = interpolator.interpolateToPath(ts);
200 Path mirroredpath = strokepath.reverse() * Geom::Scale(1,-1);
202 strokepath.append(mirroredpath, Geom::Path::STITCH_DISCONTINUOUS);
203 strokepath.close();
205 D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
206 Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
207 Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
209 output = compose(pwd2_in,x) + y*compose(n,x);
210 } else {
211 // path is closed
213 // perhaps use std::list instead of std::vector?
214 std::vector<Geom::Point> ts = offset_points.data();
215 if (sort_points) {
216 sort(ts.begin(), ts.end(), compare_offsets);
217 }
218 // add extra points for interpolation between first and last point
219 Point first_point = ts.front();
220 Point last_point = ts.back();
221 ts.insert(ts.begin(), last_point - Point(pwd2_in.domain().extent() ,0));
222 ts.push_back( first_point + Point(pwd2_in.domain().extent() ,0) );
223 // create stroke path where points (x,y) := (t, offset)
224 Geom::Interpolate::CubicBezierJohan interpolator;
225 Path strokepath = interpolator.interpolateToPath(ts);
227 // output 2 separate paths
228 D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
229 Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
230 Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
231 // find time values for which x lies outside path domain
232 // and only take portion of x and y that lies within those time values
233 std::vector< double > rtsmin = roots (x - pwd2_in.domain().min());
234 std::vector< double > rtsmax = roots (x - pwd2_in.domain().max());
235 if ( !rtsmin.empty() && !rtsmax.empty() ) {
236 x = portion(x, rtsmin.at(0), rtsmax.at(0));
237 y = portion(y, rtsmin.at(0), rtsmax.at(0));
238 }
239 output = compose(pwd2_in,x) + y*compose(n,x);
240 x = reverse(x);
241 y = reverse(y);
242 output.concat(compose(pwd2_in,x) - y*compose(n,x));
243 }
245 return output;
246 }
248 /* ######################## */
250 } //namespace LivePathEffect
251 } /* namespace Inkscape */
253 /*
254 Local Variables:
255 mode:c++
256 c-file-style:"stroustrup"
257 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
258 indent-tabs-mode:nil
259 fill-column:99
260 End:
261 */
262 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :