summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJohan B. C. Engelen <jbc.engelen@swissonline.ch>2011-09-29 16:26:22 +0000
committerJohan Engelen <goejendaagh@zonnet.nl>2011-09-29 16:26:22 +0000
commitcecb1678464fc59eb8284c9afd19a6cbb3110b56 (patch)
treeb18bed1227eb5fcbb1b6f8c182b2a82228ef77fe /src
parentfix for building when WITH_LIBWPG couldn't be found. (diff)
downloadinkscape-cecb1678464fc59eb8284c9afd19a6cbb3110b56.tar.gz
inkscape-cecb1678464fc59eb8284c9afd19a6cbb3110b56.zip
PowerStroke: handle cusps in some way. properly bugged for all types but "beveled"
(bzr r10652)
Diffstat (limited to 'src')
-rw-r--r--src/live_effects/lpe-powerstroke.cpp373
-rw-r--r--src/live_effects/lpe-powerstroke.h10
2 files changed, 167 insertions, 216 deletions
diff --git a/src/live_effects/lpe-powerstroke.cpp b/src/live_effects/lpe-powerstroke.cpp
index ca952785c..582ea2750 100644
--- a/src/live_effects/lpe-powerstroke.cpp
+++ b/src/live_effects/lpe-powerstroke.cpp
@@ -348,180 +348,118 @@ static bool compare_offsets (Geom::Point first, Geom::Point second)
return first[Geom::X] < second[Geom::X];
}
- // find discontinuities in piecewise
-std::vector<unsigned> find_discontinuities(Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in, double eps=Geom::EPSILON)
+// 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 )
{
- std::vector<unsigned> indices;
- for(unsigned i = 1; i < pwd2_in.size(); i++) {
- if ( ! are_near(pwd2_in[i-1].at1(), pwd2_in[i].at0(), eps) ) {
- indices.push_back(i);
+ 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.size() > 0) {
+ data.width = y(rts.front());
+ } else {
+ data.width = 1;
+ }
+ vect.push_back(data);
}
}
- return indices;
+ return vect;
}
-Geom::Piecewise<Geom::D2<Geom::SBasis> >
-LPEPowerStroke::doEffect_pwd2_open ( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & der,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & n )
-{
- using namespace Geom;
-
- Piecewise<D2<SBasis> > output;
-
- LineCapType start_linecap = static_cast<LineCapType>(start_linecap_type.get_value());
- LineCapType end_linecap = static_cast<LineCapType>(end_linecap_type.get_value());
-
- // perhaps use std::list instead of std::vector?
- std::vector<Geom::Point> ts(offset_points.data().size() + 2);
- for (unsigned int i = 0; i < offset_points.data().size(); ++i) {
- ts.at(i+1) = offset_points.data().at(i);
- }
- if (sort_points) {
- sort(ts.begin()+1, ts.end()-1, compare_offsets);
- }
-
- // first and last point have same distance from path as second and second to last points, respectively.
- ts.front() = Point(pwd2_in.domain().min(), (*(ts.begin()+1))[Geom::Y] );
- ts.back() = Point(pwd2_in.domain().max(), (*(ts.end()-2))[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()));
- 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));
+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();
}
- output = compose(pwd2_in,x) + y*compose(n,x);
-
- x = reverse(x);
- y = reverse(y);
- Piecewise<D2<SBasis> > mirrorpath = compose(pwd2_in,x) - y*compose(n,x);
-
- switch (end_linecap) {
- case LINECAP_PEAK:
- {
- Geom::Point end_deriv = der.lastValue();
- double radius = 0.5 * distance(output.lastValue(), mirrorpath.firstValue());
- Geom::Point midpoint = 0.5*(output.lastValue() + mirrorpath.firstValue()) + radius*end_deriv;
- Geom::LineSegment cap11(output.lastValue(), midpoint);
- Geom::LineSegment cap12(midpoint, mirrorpath.firstValue());
- output.continuousConcat(Piecewise<D2<SBasis> >(cap11.toSBasis()));
- output.continuousConcat(Piecewise<D2<SBasis> >(cap12.toSBasis()));
- break;
- }
- case LINECAP_SQUARE:
- {
- Geom::Point end_deriv = der.lastValue();
- double radius = 0.5 * distance(output.lastValue(), mirrorpath.firstValue());
- Geom::LineSegment cap11(output.lastValue(), output.lastValue() + radius*end_deriv);
- Geom::LineSegment cap12(output.lastValue() + radius*end_deriv, mirrorpath.firstValue() + radius*end_deriv);
- Geom::LineSegment cap13(mirrorpath.firstValue() + radius*end_deriv, mirrorpath.firstValue());
- output.continuousConcat(Piecewise<D2<SBasis> >(cap11.toSBasis()));
- output.continuousConcat(Piecewise<D2<SBasis> >(cap12.toSBasis()));
- output.continuousConcat(Piecewise<D2<SBasis> >(cap13.toSBasis()));
- break;
- }
- case LINECAP_BUTT:
- {
- Geom::LineSegment cap1(output.lastValue(), mirrorpath.firstValue());
- output.continuousConcat(Piecewise<D2<SBasis> >(cap1.toSBasis()));
- break;
- }
- case LINECAP_ROUND:
- default:
- {
- double radius1 = 0.5 * distance(output.lastValue(), mirrorpath.firstValue());
- Geom::SVGEllipticalArc cap1(output.lastValue(), radius1, radius1, M_PI/2., false, y.firstValue() < 0, mirrorpath.firstValue()); // note that y is reversed above!
- output.continuousConcat(Piecewise<D2<SBasis> >(cap1.toSBasis()));
- break;
- }
- }
+ 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];
- output.continuousConcat(mirrorpath);
+ 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;
+ }
- switch (start_linecap) {
- case LINECAP_PEAK:
- {
- Geom::Point start_deriv = der.firstValue();
- double radius = 0.5 * distance(output.firstValue(), output.lastValue());
- Geom::Point midpoint = 0.5*(output.lastValue() + output.firstValue()) - radius*start_deriv;
- Geom::LineSegment cap21(output.lastValue(), midpoint);
- Geom::LineSegment cap22(midpoint, output.firstValue());
- output.continuousConcat(Piecewise<D2<SBasis> >(cap21.toSBasis()));
- output.continuousConcat(Piecewise<D2<SBasis> >(cap22.toSBasis()));
- break;
- }
- case LINECAP_SQUARE:
- {
- Geom::Point start_deriv = der.firstValue();
- double radius = 0.5 * distance(output.firstValue(), output.lastValue());
- Geom::LineSegment cap21(output.lastValue(), output.lastValue() - radius*start_deriv);
- Geom::LineSegment cap22(output.lastValue() - radius*start_deriv, output.firstValue() - radius*start_deriv);
- Geom::LineSegment cap23(output.firstValue() - radius*start_deriv, output.firstValue());
- output.continuousConcat(Piecewise<D2<SBasis> >(cap21.toSBasis()));
- output.continuousConcat(Piecewise<D2<SBasis> >(cap22.toSBasis()));
- output.continuousConcat(Piecewise<D2<SBasis> >(cap23.toSBasis()));
- break;
- }
- case LINECAP_BUTT:
- {
- Geom::LineSegment cap2(output.lastValue(), output.firstValue());
- output.continuousConcat(Piecewise<D2<SBasis> >(cap2.toSBasis()));
- break;
- }
- case LINECAP_ROUND:
- default:
- {
- double radius2 = 0.5 * distance(output.firstValue(), output.lastValue());
- Geom::SVGEllipticalArc cap2(output.lastValue(), radius2, radius2, M_PI/2., false, y.lastValue() < 0, output.firstValue()); // note that y is reversed above!
- output.continuousConcat(Piecewise<D2<SBasis> >(cap2.toSBasis()));
- break;
+ cusp_i++;
}
+ build_from_sbasis(pb, B[i], tol, false);
}
-
- return output;
+ pb.finish();
+ return pb.peek().front();
}
-Geom::Piecewise<Geom::D2<Geom::SBasis> >
-LPEPowerStroke::doEffect_pwd2_closed ( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & /*der*/,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & n )
+
+std::vector<Geom::Path>
+LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
{
using namespace Geom;
- Piecewise<D2<SBasis> > output;
+ std::vector<Geom::Path> path_out;
+ if (path_in.size() == 0) {
+ return path_out;
+ }
- // path is closed
- // linecap parameter can be ignored
+ // for now, only regard first subpath and ignore the rest
+ Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[0].toPwSb();
+
+ offset_points.set_pwd2(pwd2_in);
+ Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
+ Piecewise<D2<SBasis> > n = rot90(der);
+ offset_points.set_pwd2_normal(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);
}
- // 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) );
+ 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) );
+ } 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()));
Geom::Path strokepath = interpolator->interpolateToPath(ts);
delete interpolator;
- // 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]);
@@ -533,78 +471,101 @@ LPEPowerStroke::doEffect_pwd2_closed ( Geom::Piecewise<Geom::D2<Geom::SBasis> >
x = portion(x, rtsmin.at(0), rtsmax.at(0));
y = portion(y, rtsmin.at(0), rtsmax.at(0));
}
- 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));
-
- return output;
-}
-
-std::vector<Geom::Path>
-LPEPowerStroke::doEffect_path (std::vector<Geom::Path> const & path_in)
-{
- using namespace Geom;
-
- std::vector<Geom::Path> path_out;
- for (unsigned int i=0; i < path_in.size(); i++) {
- Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_in = path_in[i].toPwSb();
-
- offset_points.set_pwd2(pwd2_in);
- Piecewise<D2<SBasis> > der = unitVector(derivative(pwd2_in));
- Piecewise<D2<SBasis> > n = rot90(der);
- offset_points.set_pwd2_normal(n);
+ std::vector<discontinuity_data> cusps = find_discontinuities(der, x, y);
+ LineCuspType cusp_linecap = static_cast<LineCuspType>(cusp_linecap_type.get_value());
- Geom::Piecewise<Geom::D2<Geom::SBasis> > pwd2_out;
- if (path_in[i].closed()) {
- pwd2_out = doEffect_pwd2_closed(pwd2_in, der, n);
- } else {
- pwd2_out = doEffect_pwd2_open(pwd2_in, der, n);
- }
-
- std::vector<Geom::Path> path = path_from_piecewise_fix_cusps( pwd2_out, LPE_CONVERSION_TOLERANCE);
- // add the output path vector to the already accumulated vector:
- for (unsigned int j=0; j < path.size(); j++) {
- path_out.push_back(path[j]);
+ 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;
+ }
}
- }
-
- return path_out;
-}
-std::vector<Geom::Path>
-LPEPowerStroke::path_from_piecewise_fix_cusps(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol) {
+ fixed_path.append(fixed_mirrorpath, Geom::Path::STITCH_DISCONTINUOUS);
-/* per definition, the input piecewise should be closed. each discontinuity should be fixed with a cusp-ending,
- as defined by cusp_linecap_type
-*/
- LineCuspType cusp_linecap = static_cast<LineCuspType>(cusp_linecap_type.get_value());
-
- Geom::PathBuilder pb;
- if(B.size() == 0) return pb.peek();
- 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 :-)
- switch (cusp_linecap) {
- LINECUSP_ROUND:
- LINECUSP_SHARP:
- LINECUSP_BEVEL:
+ 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:
- pb.lineTo(B[i].at0());
+ {
+ 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;
}
}
- build_from_sbasis(pb, B[i], tol, false);
+
+ fixed_path.close(true);
+ path_out.push_back(fixed_path);
}
- pb.closePath();
- pb.finish();
- return pb.peek();
+
+ return path_out;
}
+
/* ######################## */
} //namespace LivePathEffect
diff --git a/src/live_effects/lpe-powerstroke.h b/src/live_effects/lpe-powerstroke.h
index f941e844f..bcfbdadc0 100644
--- a/src/live_effects/lpe-powerstroke.h
+++ b/src/live_effects/lpe-powerstroke.h
@@ -30,16 +30,6 @@ public:
virtual void doOnApply(SPLPEItem *lpeitem);
private:
- Geom::Piecewise<Geom::D2<Geom::SBasis> >
- doEffect_pwd2_open ( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & der,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & n );
- Geom::Piecewise<Geom::D2<Geom::SBasis> >
- doEffect_pwd2_closed ( Geom::Piecewise<Geom::D2<Geom::SBasis> > const & pwd2_in,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & der,
- Geom::Piecewise<Geom::D2<Geom::SBasis> > const & n );
- std::vector<Geom::Path> path_from_piecewise_fix_cusps(Geom::Piecewise<Geom::D2<Geom::SBasis> > const &B, double tol);
-
PowerStrokePointArrayParam offset_points;
BoolParam sort_points;
EnumParam<unsigned> interpolator_type;