summaryrefslogtreecommitdiffstats
path: root/src/live_effects/lpe-powerstroke.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/live_effects/lpe-powerstroke.cpp')
-rw-r--r--src/live_effects/lpe-powerstroke.cpp420
1 files changed, 256 insertions, 164 deletions
diff --git a/src/live_effects/lpe-powerstroke.cpp b/src/live_effects/lpe-powerstroke.cpp
index 5dc170e84..25cb72b42 100644
--- a/src/live_effects/lpe-powerstroke.cpp
+++ b/src/live_effects/lpe-powerstroke.cpp
@@ -1,16 +1,17 @@
-#define INKSCAPE_LPE_POWERSTROKE_CPP
-/** \file
- * @brief PowerStroke LPE implementation. Creates curves with modifiable stroke width.
+/**
+ * @file
+ * PowerStroke LPE implementation. Creates curves with modifiable stroke width.
*/
/* Authors:
- * Johan Engelen <j.b.c.engelen@utwente.nl>
+ * Johan Engelen <j.b.c.engelen@alumnus.utwente.nl>
*
- * Copyright (C) 2010 Authors
+ * Copyright (C) 2010-2011 Authors
*
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include "live_effects/lpe-powerstroke.h"
+#include "live_effects/lpe-powerstroke-interpolators.h"
#include "sp-shape.h"
#include "display/curve.h"
@@ -20,123 +21,72 @@
#include <2geom/sbasis-geometric.h>
#include <2geom/transforms.h>
#include <2geom/bezier-utils.h>
+#include <2geom/svg-elliptical-arc.h>
+#include <2geom/sbasis-to-bezier.h>
+#include <2geom/svg-path.h>
-/// @TODO Move this to 2geom
-namespace Geom {
-namespace Interpolate {
-
-class Interpolator {
-public:
- Interpolator() {};
- virtual ~Interpolator() {};
-
-// virtual Piecewise<D2<SBasis> > interpolateToPwD2Sb(std::vector<Point> points) = 0;
- virtual Path interpolateToPath(std::vector<Point> points) = 0;
+namespace Inkscape {
+namespace LivePathEffect {
-private:
- Interpolator(const Interpolator&);
- Interpolator& operator=(const Interpolator&);
+static const Util::EnumData<unsigned> InterpolatorTypeData[] = {
+ {Geom::Interpolate::INTERP_LINEAR , N_("Linear"), "Linear"},
+ {Geom::Interpolate::INTERP_CUBICBEZIER , N_("CubicBezierFit"), "CubicBezierFit"},
+ {Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN , N_("CubicBezierJohan"), "CubicBezierJohan"},
+ {Geom::Interpolate::INTERP_SPIRO , N_("SpiroInterpolator"), "SpiroInterpolator"}
};
+static const Util::EnumDataConverter<unsigned> InterpolatorTypeConverter(InterpolatorTypeData, sizeof(InterpolatorTypeData)/sizeof(*InterpolatorTypeData));
-class Linear : public Interpolator {
-public:
- Linear() {};
- virtual ~Linear() {};
-
- virtual Path interpolateToPath(std::vector<Point> points) {
- Path path;
- path.start( points.at(0) );
- for (unsigned int i = 1 ; i < points.size(); ++i) {
- path.appendNew<Geom::LineSegment>(points.at(i));
- }
- return path;
- };
-
-private:
- Linear(const Linear&);
- Linear& operator=(const Linear&);
+enum LineCapType {
+ LINECAP_BUTT,
+ LINECAP_SQUARE,
+ LINECAP_ROUND,
+ LINECAP_PEAK
};
-
-// this class is terrible
-class CubicBezierFit : public Interpolator {
-public:
- CubicBezierFit() {};
- virtual ~CubicBezierFit() {};
-
- virtual Path interpolateToPath(std::vector<Point> points) {
- unsigned int n_points = points.size();
- // worst case gives us 2 segment per point
- int max_segs = 8*n_points;
- Geom::Point * b = g_new(Geom::Point, max_segs);
- Geom::Point * points_array = g_new(Geom::Point, 4*n_points);
- for (unsigned i = 0; i < n_points; ++i) {
- points_array[i] = points.at(i);
- }
-
- double tolerance_sq = 0; // this value is just a random guess
-
- int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, n_points,
- tolerance_sq, max_segs);
-
- Geom::Path fit;
- if ( n_segs > 0)
- {
- fit.start(b[0]);
- for (int c = 0; c < n_segs; c++) {
- fit.appendNew<Geom::CubicBezier>(b[4*c+1], b[4*c+2], b[4*c+3]);
- }
- }
- g_free(b);
- g_free(points_array);
- return fit;
- };
-
-private:
- CubicBezierFit(const CubicBezierFit&);
- CubicBezierFit& operator=(const CubicBezierFit&);
+static const Util::EnumData<unsigned> LineCapTypeData[] = {
+ {LINECAP_BUTT , N_("Butt"), "butt"},
+ {LINECAP_SQUARE, N_("Square"), "square"},
+ {LINECAP_ROUND , N_("Round"), "round"},
+ {LINECAP_PEAK , N_("Peak"), "peak"}
};
+static const Util::EnumDataConverter<unsigned> LineCapTypeConverter(LineCapTypeData, sizeof(LineCapTypeData)/sizeof(*LineCapTypeData));
-/// @todo invent name for this class
-class CubicBezierJohan : public Interpolator {
-public:
- CubicBezierJohan() {};
- virtual ~CubicBezierJohan() {};
-
- virtual Path interpolateToPath(std::vector<Point> points) {
- Path fit;
- fit.start(points.at(0));
- for (unsigned int i = 1; i < points.size(); ++i) {
- Point p0 = points.at(i-1);
- Point p1 = points.at(i);
- Point dx = Point(p1[X] - p0[X], 0);
- fit.appendNew<CubicBezier>(p0+0.2*dx, p1-0.2*dx, p1);
- }
- return fit;
- };
-
-private:
- CubicBezierJohan(const CubicBezierJohan&);
- CubicBezierJohan& operator=(const CubicBezierJohan&);
+enum LineCuspType {
+ LINECUSP_BEVEL,
+ LINECUSP_ROUND,
+ LINECUSP_SHARP
};
-
-} //namespace Interpolate
-} //namespace Geom
-
-namespace Inkscape {
-namespace LivePathEffect {
+static const Util::EnumData<unsigned> LineCuspTypeData[] = {
+ {LINECUSP_BEVEL , N_("Beveled"), "bevel"},
+ {LINECUSP_ROUND , N_("Rounded"), "round"},
+// not yet supported {LINECUSP_SHARP , N_("Sharp"), "sharp"}
+};
+static const Util::EnumDataConverter<unsigned> LineCuspTypeConverter(LineCuspTypeData, sizeof(LineCuspTypeData)/sizeof(*LineCuspTypeData));
LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) :
Effect(lpeobject),
offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this),
- sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve."), "sort_points", &wr, this, true)
+ sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve."), "sort_points", &wr, this, true),
+ interpolator_type(_("Interpolator type"), _("Determines which kind of interpolator will be used to interpolate between stroke width along the path."), "interpolator_type", InterpolatorTypeConverter, &wr, this, Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN),
+ interpolator_beta(_("Smoothness"), _("Sets the smoothness for the CubicBezierJohan interpolator. 0 = linear interpolation, 1 = smooth"), "interpolator_beta", &wr, this, 0.2),
+ start_linecap_type(_("Start line cap type"), _("Determines the shape of the path's start."), "start_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ROUND),
+ cusp_linecap_type(_("Cusp line cap type"), _("Determines the shape of the cusps along the path."), "cusp_linecap_type", LineCuspTypeConverter, &wr, this, LINECUSP_ROUND),
+ end_linecap_type(_("End line cap type"), _("Determines the shape of the path's end."), "end_linecap_type", LineCapTypeConverter, &wr, this, LINECAP_ROUND)
{
show_orig_path = true;
/// @todo offset_points are initialized with empty path, is that bug-save?
+ interpolator_beta.addSlider(true);
+ interpolator_beta.param_set_range(0.,1.);
+
registerParameter( dynamic_cast<Parameter *>(&offset_points) );
registerParameter( dynamic_cast<Parameter *>(&sort_points) );
+ registerParameter( dynamic_cast<Parameter *>(&interpolator_type) );
+ registerParameter( dynamic_cast<Parameter *>(&interpolator_beta) );
+ registerParameter( dynamic_cast<Parameter *>(&start_linecap_type) );
+ registerParameter( dynamic_cast<Parameter *>(&cusp_linecap_type) );
+ registerParameter( dynamic_cast<Parameter *>(&end_linecap_type) );
}
LPEPowerStroke::~LPEPowerStroke()
@@ -156,95 +106,237 @@ LPEPowerStroke::doOnApply(SPLPEItem *lpeitem)
offset_points.param_set_and_write_new_value(points);
}
+void
+LPEPowerStroke::adjustForNewPath(std::vector<Geom::Path> const & path_in)
+{
+ offset_points.recalculate_controlpoints_for_new_pwd2(path_in[0].toPwSb());
+}
+
static bool compare_offsets (Geom::Point first, Geom::Point second)
{
return first[Geom::X] < second[Geom::X];
}
-
-Geom::Piecewise<Geom::D2<Geom::SBasis> >
-LPEPowerStroke::doEffect_pwd2 (Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in)
+// find discontinuities in input path
+struct discontinuity_data {
+ Geom::Point der0; // unit derivative of 'left' side of cusp
+ Geom::Point der1; // unit derivative of 'right' side of cusp
+ double width; // intended stroke width at cusp
+};
+std::vector<discontinuity_data> find_discontinuities( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & der,
+ Geom::Piecewise<Geom::SBasis> const & x,
+ Geom::Piecewise<Geom::SBasis> const & y,
+ double eps=Geom::EPSILON )
{
- using namespace Geom;
-
- offset_points.set_pwd2(pwd2_in);
+ std::vector<discontinuity_data> vect;
+ for(unsigned i = 1; i < der.size(); i++) {
+ if ( ! are_near(der[i-1].at1(), der[i].at0(), eps) ) {
+ discontinuity_data data;
+ data.der0 = der[i-1].at1();
+ data.der1 = der[i].at0();
+ double t = der.cuts[i];
+ std::vector< double > rts = roots (x - t); /// @todo this has multiple solutions for general strokewidth paths (generated by spiro interpolator...), ignore for now
+ if (!rts.empty()) {
+ data.width = y(rts.front());
+ } else {
+ data.width = 1;
+ }
+ vect.push_back(data);
+ }
+ }
+ return vect;
+}
- Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
- Piecewise<D2<SBasis> > n = rot90(der);
- offset_points.set_pwd2_normal(n);
- // see if we should treat the path as being closed.
- bool closed_path = false;
- if ( are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) {
- closed_path = true;
+Geom::Path path_from_piecewise_fix_cusps( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & B,
+ std::vector<discontinuity_data> const & cusps,
+ LineCuspType cusp_linecap,
+ double tol=Geom::EPSILON)
+{
+/* per definition, each discontinuity should be fixed with a cusp-ending, as defined by cusp_linecap_type
+*/
+ Geom::PathBuilder pb;
+ if (B.size() == 0) {
+ return pb.peek().front();
}
- Piecewise<D2<SBasis> > output;
- if (!closed_path) {
- // perhaps use std::list instead of std::vector?
- std::vector<Geom::Point> ts(offset_points.data().size() + 2);
- // first and last point coincide with input path (for now at least)
- ts.front() = Point(pwd2_in.domain().min(),0);
- ts.back() = Point(pwd2_in.domain().max(),0);
- for (unsigned int i = 0; i < offset_points.data().size(); ++i) {
- ts.at(i+1) = offset_points.data().at(i);
- }
+ unsigned int cusp_i = 0;
+ Geom::Point start = B[0].at0();
+ pb.moveTo(start);
+ build_from_sbasis(pb, B[0], tol, false);
+ for (unsigned i=1; i < B.size(); i++) {
+ if (!are_near(B[i-1].at1(), B[i].at0(), tol) )
+ { // discontinuity found, so fix it :-)
+ discontinuity_data const &cusp = cusps[cusp_i];
+
+ switch (cusp_linecap) {
+ case LINECUSP_ROUND: // properly bugged ^_^
+ pb.arcTo( abs(cusp.width), abs(cusp.width),
+ angle_between(cusp.der0, cusp.der1), false, cusp.width < 0,
+ B[i].at0() );
+ break;
+ case LINECUSP_SHARP: // no clue yet what to do here :)
+ case LINECUSP_BEVEL:
+ default:
+ pb.lineTo(B[i].at0());
+ break;
+ }
- if (sort_points) {
- sort(ts.begin(), ts.end(), compare_offsets);
+ cusp_i++;
}
+ build_from_sbasis(pb, B[i], tol, false);
+ }
+ pb.finish();
+ return pb.peek().front();
+}
+
- // create stroke path where points (x,y) := (t, offset)
- Geom::Interpolate::CubicBezierJohan interpolator;
- Path strokepath = interpolator.interpolateToPath(ts);
- Path mirroredpath = strokepath.reverse() * Geom::Scale(1,-1);
+std::vector<Geom::Path>
+LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
+{
+ using namespace Geom;
- strokepath.append(mirroredpath, Geom::Path::STITCH_DISCONTINUOUS);
- strokepath.close();
+ std::vector<Geom::Path> path_out;
+ if (path_in.size() == 0) {
+ return path_out;
+ }
- D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
- Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
- Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+ // for now, only regard first subpath and ignore the rest
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[0].toPwSb();
- output = compose(pwd2_in,x) + y*compose(n,x);
- } else {
- // path is closed
+ Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
+ Piecewise<D2<SBasis> > n = rot90(der);
+ offset_points.set_pwd2(pwd2_in, n);
- // perhaps use std::list instead of std::vector?
- std::vector<Geom::Point> ts = offset_points.data();
- if (sort_points) {
- sort(ts.begin(), ts.end(), compare_offsets);
- }
+ std::vector<Geom::Point> ts = offset_points.data();
+ if (sort_points) {
+ sort(ts.begin(), ts.end(), compare_offsets);
+ }
+ if (path_in[0].closed()) {
// add extra points for interpolation between first and last point
Point first_point = ts.front();
Point last_point = ts.back();
ts.insert(ts.begin(), last_point - Point(pwd2_in.domain().extent() ,0));
ts.push_back( first_point + Point(pwd2_in.domain().extent() ,0) );
- // create stroke path where points (x,y) := (t, offset)
- Geom::Interpolate::CubicBezierJohan interpolator;
- Path strokepath = interpolator.interpolateToPath(ts);
-
- // output 2 separate paths
- D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
- Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
- Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
- // find time values for which x lies outside path domain
- // and only take portion of x and y that lies within those time values
- std::vector< double > rtsmin = roots (x - pwd2_in.domain().min());
- std::vector< double > rtsmax = roots (x - pwd2_in.domain().max());
- if ( !rtsmin.empty() && !rtsmax.empty() ) {
- x = portion(x, rtsmin.at(0), rtsmax.at(0));
- y = portion(y, rtsmin.at(0), rtsmax.at(0));
+ } else {
+ // first and last point have same distance from path as second and second to last points, respectively.
+ ts.insert(ts.begin(), Point(pwd2_in.domain().min(), ts.front()[Geom::Y]) );
+ ts.push_back( Point(pwd2_in.domain().max(), ts.back()[Geom::Y]) );
+ }
+ // create stroke path where points (x,y) := (t, offset)
+ Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(static_cast<Geom::Interpolate::InterpolatorType>(interpolator_type.get_value()));
+ if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast<Geom::Interpolate::CubicBezierJohan*>(interpolator)) {
+ johan->setBeta(interpolator_beta);
+ }
+ Geom::Path strokepath = interpolator->interpolateToPath(ts);
+ delete interpolator;
+
+ D2<Piecewise<SBasis> > patternd2 = make_cuts_independent(strokepath.toPwSb());
+ Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
+ Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
+ // find time values for which x lies outside path domain
+ // and only take portion of x and y that lies within those time values
+ std::vector< double > rtsmin = roots (x - pwd2_in.domain().min());
+ std::vector< double > rtsmax = roots (x - pwd2_in.domain().max());
+ if ( !rtsmin.empty() && !rtsmax.empty() ) {
+ x = portion(x, rtsmin.at(0), rtsmax.at(0));
+ y = portion(y, rtsmin.at(0), rtsmax.at(0));
+ }
+
+ std::vector<discontinuity_data> cusps = find_discontinuities(der, x, y);
+ LineCuspType cusp_linecap = static_cast<LineCuspType>(cusp_linecap_type.get_value());
+
+ Piecewise<D2<SBasis> > pwd2_out = compose(pwd2_in,x) + y*compose(n,x);
+ Piecewise<D2<SBasis> > mirrorpath = reverse(compose(pwd2_in,x) - y*compose(n,x));
+
+ Geom::Path fixed_path = path_from_piecewise_fix_cusps( pwd2_out, cusps, cusp_linecap, LPE_CONVERSION_TOLERANCE);
+ Geom::Path fixed_mirrorpath = path_from_piecewise_fix_cusps( mirrorpath, cusps, cusp_linecap, LPE_CONVERSION_TOLERANCE);
+
+ if (path_in[0].closed()) {
+ fixed_path.close(true);
+ path_out.push_back(fixed_path);
+ fixed_mirrorpath.close(true);
+ path_out.push_back(fixed_mirrorpath);
+ } else {
+ // add linecaps...
+ LineCapType end_linecap = static_cast<LineCapType>(end_linecap_type.get_value());
+ LineCapType start_linecap = static_cast<LineCapType>(start_linecap_type.get_value());
+ switch (end_linecap) {
+ case LINECAP_PEAK:
+ {
+ Geom::Point end_deriv = der.lastValue();
+ double radius = 0.5 * distance(pwd2_out.lastValue(), mirrorpath.firstValue());
+ Geom::Point midpoint = 0.5*(pwd2_out.lastValue() + mirrorpath.firstValue()) + radius*end_deriv;
+ fixed_path.appendNew<LineSegment>(midpoint);
+ fixed_path.appendNew<LineSegment>(mirrorpath.firstValue());
+ break;
+ }
+ case LINECAP_SQUARE:
+ {
+ Geom::Point end_deriv = der.lastValue();
+ double radius = 0.5 * distance(pwd2_out.lastValue(), mirrorpath.firstValue());
+ fixed_path.appendNew<LineSegment>( pwd2_out.lastValue() + radius*end_deriv );
+ fixed_path.appendNew<LineSegment>( mirrorpath.firstValue() + radius*end_deriv );
+ fixed_path.appendNew<LineSegment>( mirrorpath.firstValue() );
+ break;
+ }
+ case LINECAP_BUTT:
+ {
+ fixed_path.appendNew<LineSegment>( mirrorpath.firstValue() );
+ break;
+ }
+ case LINECAP_ROUND:
+ default:
+ {
+ double radius1 = 0.5 * distance(pwd2_out.lastValue(), mirrorpath.firstValue());
+ fixed_path.appendNew<SVGEllipticalArc>( radius1, radius1, M_PI/2., false, y.lastValue() < 0, mirrorpath.firstValue() );
+ break;
+ }
}
- output = compose(pwd2_in,x) + y*compose(n,x);
- x = reverse(x);
- y = reverse(y);
- output.concat(compose(pwd2_in,x) - y*compose(n,x));
+
+ fixed_path.append(fixed_mirrorpath, Geom::Path::STITCH_DISCONTINUOUS);
+
+ switch (start_linecap) {
+ case LINECAP_PEAK:
+ {
+ Geom::Point start_deriv = der.firstValue();
+ double radius = 0.5 * distance(pwd2_out.firstValue(), mirrorpath.lastValue());
+ Geom::Point midpoint = 0.5*(mirrorpath.lastValue() + pwd2_out.firstValue()) - radius*start_deriv;
+ fixed_path.appendNew<LineSegment>( midpoint );
+ fixed_path.appendNew<LineSegment>( pwd2_out.firstValue() );
+ break;
+ }
+ case LINECAP_SQUARE:
+ {
+ Geom::Point start_deriv = der.firstValue();
+ double radius = 0.5 * distance(pwd2_out.firstValue(), mirrorpath.lastValue());
+ fixed_path.appendNew<LineSegment>( mirrorpath.lastValue() - radius*start_deriv );
+ fixed_path.appendNew<LineSegment>( pwd2_out.firstValue() - radius*start_deriv );
+ fixed_path.appendNew<LineSegment>( pwd2_out.firstValue() );
+ break;
+ }
+ case LINECAP_BUTT:
+ {
+ fixed_path.appendNew<LineSegment>( pwd2_out.firstValue() );
+ break;
+ }
+ case LINECAP_ROUND:
+ default:
+ {
+ double radius2 = 0.5 * distance(pwd2_out.firstValue(), mirrorpath.lastValue());
+ fixed_path.appendNew<SVGEllipticalArc>( radius2, radius2, M_PI/2., false, y.firstValue() < 0, pwd2_out.firstValue() );
+ break;
+ }
+ }
+
+ fixed_path.close(true);
+ path_out.push_back(fixed_path);
}
- return output;
+ return path_out;
}
+
/* ######################## */
} //namespace LivePathEffect