diff options
| author | Johan B. C. Engelen <jbc.engelen@swissonline.ch> | 2011-09-29 16:26:22 +0000 |
|---|---|---|
| committer | Johan Engelen <goejendaagh@zonnet.nl> | 2011-09-29 16:26:22 +0000 |
| commit | cecb1678464fc59eb8284c9afd19a6cbb3110b56 (patch) | |
| tree | b18bed1227eb5fcbb1b6f8c182b2a82228ef77fe /src | |
| parent | fix for building when WITH_LIBWPG couldn't be found. (diff) | |
| download | inkscape-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.cpp | 373 | ||||
| -rw-r--r-- | src/live_effects/lpe-powerstroke.h | 10 |
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; |
