From 9a04c985ec628dc749a8a82d94bcf47d482a4f63 Mon Sep 17 00:00:00 2001 From: "Liam P. White" Date: Thu, 20 Mar 2014 17:09:57 -0400 Subject: Fix a linker error ("static") (hopefully) Resolve compiler errors with GTK3+ (bzr r13090.1.28) --- src/live_effects/Makefile_insert | 1 + src/live_effects/effect.cpp | 2 +- src/live_effects/lpe-taperstroke.cpp | 162 ++++-- src/live_effects/lpe-taperstroke.h | 9 +- src/live_effects/pathoutlineprovider.cpp | 840 +++++++++++++++++++++++++++++++ src/live_effects/pathoutlineprovider.h | 791 +---------------------------- src/ui/dialog/objects.cpp | 3 +- 7 files changed, 999 insertions(+), 809 deletions(-) create mode 100755 src/live_effects/pathoutlineprovider.cpp mode change 100755 => 100644 src/live_effects/pathoutlineprovider.h (limited to 'src') diff --git a/src/live_effects/Makefile_insert b/src/live_effects/Makefile_insert index b19f417ae..ac7c33e2f 100644 --- a/src/live_effects/Makefile_insert +++ b/src/live_effects/Makefile_insert @@ -93,6 +93,7 @@ ink_common_sources += \ live_effects/lpe-fill-between-many.h \ live_effects/lpe-ellipse_5pts.cpp \ live_effects/lpe-ellipse_5pts.h \ + live_effects/pathoutlineprovider.cpp \ live_effects/lpe-jointype.cpp \ live_effects/lpe-jointype.h \ live_effects/lpe-taperstroke.cpp \ diff --git a/src/live_effects/effect.cpp b/src/live_effects/effect.cpp index 337fe631f..d6840e5b8 100644 --- a/src/live_effects/effect.cpp +++ b/src/live_effects/effect.cpp @@ -5,7 +5,7 @@ * Released under GNU GPL, read the file 'COPYING' for more information */ -#define LPE_ENABLE_TEST_EFFECTS //uncomment for toy effects +//#define LPE_ENABLE_TEST_EFFECTS //uncomment for toy effects #ifdef HAVE_CONFIG_H # include "config.h" diff --git a/src/live_effects/lpe-taperstroke.cpp b/src/live_effects/lpe-taperstroke.cpp index 2cdc09376..a862417d7 100644 --- a/src/live_effects/lpe-taperstroke.cpp +++ b/src/live_effects/lpe-taperstroke.cpp @@ -13,11 +13,20 @@ #include "live_effects/lpe-taperstroke.h" -// You might need to include other 2geom files. You can add them here: #include <2geom/path.h> #include <2geom/shape.h> +#include <2geom/path.h> +#include <2geom/circle.h> +#include <2geom/sbasis-to-bezier.h> #include "pathoutlineprovider.h" #include "display/curve.h" +#include "sp-shape.h" +#include "style.h" +#include "xml/repr.h" +#include "sp-paint-server.h" +#include "svg/svg-color.h" +#include "desktop-style.h" +#include "svg/css-ostringstream.h" #include "svg/svg.h" //#include @@ -58,7 +67,7 @@ LPETaperStroke::LPETaperStroke(LivePathEffectObject *lpeobject) : line_width(_("Stroke width"), _("The (non-tapered) width of the path"), "stroke_width", &wr, this, 3), attach_start(_("Start offset"), _("Taper distance from path start"), "attach_start", &wr, this, 0.2), attach_end(_("End offset"), _("The ending position of the taper"), "end_offset", &wr, this, 0.2), - smoothing(_("Taper smoothing"), _("Amount of smoothing to apply to the tapers"), "smoothing", &wr, this, 0.2), + smoothing(_("Taper smoothing"), _("Amount of smoothing to apply to the tapers"), "smoothing", &wr, this, 0.5), join_type(_("Join type"), _("Join type for non-smooth nodes"), "jointype", JoinTypeConverter, &wr, this, LINEJOIN_EXTRAPOLATED), miter_limit(_("Miter limit"), _("Limit for miter joins"), "miter_limit", &wr, this, 30.) { @@ -83,21 +92,93 @@ LPETaperStroke::~LPETaperStroke() } -unsigned curveOrder (const Geom::Curve* curve_in) +//from LPEPowerStroke -- sets fill if stroke color because we will +//be converting to a fill to make the new join. + +void LPETaperStroke::doOnApply(SPLPEItem const* lpeitem) { - using namespace Geom; - //cast it - const CubicBezier *cbc = dynamic_cast(curve_in); - if (cbc) return 3; - const QuadraticBezier * qbc = dynamic_cast(curve_in); - if (qbc) return 2; - const BezierCurveN<1U> * lbc = dynamic_cast *>(curve_in); - if (lbc) return 1; - //BezierCurveN<0> * dbc = dynamic_cast *> (curve_in); - return 0; + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem* item = const_cast(lpeitem); + double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed : 1.; + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (lpeitem->style->stroke.isSet()) { + if (lpeitem->style->stroke.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getStrokePaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "fill", str.c_str()); + } + } else if (lpeitem->style->stroke.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "fill", c); + } else { + sp_repr_css_set_property (css, "fill", "none"); + } + } else { + sp_repr_css_unset_property (css, "fill"); + } + + sp_repr_css_set_property(css, "stroke", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + + line_width.param_set_value(width); + } else { + g_warning("LPE Join Type can only be applied to paths (not groups)."); + } } -Geom::Path return_at_first_cusp (Geom::Path const & path_in, double smooth_tolerance = 0.01) +//from LPEPowerStroke -- sets stroke color from existing fill color + +void LPETaperStroke::doOnRemove(SPLPEItem const* lpeitem) +{ + + if (SP_IS_SHAPE(lpeitem)) { + SPLPEItem *item = const_cast(lpeitem); + + SPCSSAttr *css = sp_repr_css_attr_new (); + if (lpeitem->style->fill.isSet()) { + if (lpeitem->style->fill.isPaintserver()) { + SPPaintServer * server = lpeitem->style->getFillPaintServer(); + if (server) { + Glib::ustring str; + str += "url(#"; + str += server->getId(); + str += ")"; + sp_repr_css_set_property (css, "stroke", str.c_str()); + } + } else if (lpeitem->style->fill.isColor()) { + gchar c[64]; + sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value))); + sp_repr_css_set_property (css, "stroke", c); + } else { + sp_repr_css_set_property (css, "stroke", "none"); + } + } else { + sp_repr_css_unset_property (css, "stroke"); + } + + Inkscape::CSSOStringStream os; + os << fabs(line_width); + sp_repr_css_set_property (css, "stroke-width", os.str().c_str()); + + sp_repr_css_set_property(css, "fill", "none"); + + sp_desktop_apply_css_recursive(item, css, true); + sp_repr_css_attr_unref (css); + item->updateRepr(); + } +} + +//actual effect impl here + +Geom::Path return_at_first_cusp (Geom::Path const & path_in, double smooth_tolerance = 0.05) { Geom::Path path_out = Geom::Path(); @@ -108,7 +189,7 @@ Geom::Path return_at_first_cusp (Geom::Path const & path_in, double smooth_toler break; //determine order of curve - int order = curveOrder(&path_in[i]); + int order = Outline::bezierOrder(&path_in[i]); Geom::Point start_point; Geom::Point cross_point = path_in[i].finalPoint(); @@ -131,7 +212,7 @@ Geom::Path return_at_first_cusp (Geom::Path const & path_in, double smooth_toler start_point = path_in[i].initialPoint(); } - order = curveOrder(&path_in[i+1]); + order = Outline::bezierOrder(&path_in[i+1]); switch (order) { @@ -155,7 +236,7 @@ Geom::Curve * subdivide_at(const Geom::Curve* curve_in, Geom::Coord time, bool f { //the only reason for this function is the lack of a subdivide function in the Curve class. //you have to cast to Beziers to be able to use subdivide(t) - unsigned order = curveOrder(curve_in); + unsigned order = Outline::bezierOrder(curve_in); Geom::Curve* curve_out = curve_in->duplicate(); switch (order) { @@ -211,22 +292,25 @@ Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in) if (attach_end <= 0) { attach_end.param_set_value( 0.0001 ); } + + //don't let it be integer + if (double(unsigned(attach_start)) == attach_start) { + attach_start.param_set_value(attach_start - 0.0001); + } + if (double(unsigned(attach_end)) == attach_end) { + attach_end.param_set_value(attach_end - 0.0001); + } - /*if (size != return_at_first_cusp(path_in[0]).size()) { //will get to this in a bit - //check to see if either knot was dragged past their allowed amount - volatile unsigned size_p_start = (unsigned)attach_start; - volatile unsigned size_p_end = (unsigned)attach_end; - - //maximum allowed value in either direction is return_at_first_cusp(path_in[0]).size - volatile unsigned allowed_p_start = return_at_first_cusp(path_in[0]).size(); - volatile unsigned allowed_p_end = return_at_first_cusp(path_in[0].reverse()).size(); - - if (size_p_start > allowed_p_start) { - attach_start.param_set_value(allowed_p_start - 0.0001); - } else if (size_p_end > allowed_p_end) { - attach_end.param_set_value(allowed_p_end - 0.0001); - } - }*/ + unsigned allowed_start = return_at_first_cusp(path_in[0]).size(); + + unsigned allowed_end = return_at_first_cusp(path_in[0].reverse()).size(); + + if ((unsigned)attach_start >= allowed_start) { + attach_start.param_set_value((double)allowed_start - 0.0001); + } + if ((unsigned)attach_end >= allowed_end) { + attach_end.param_set_value((double)allowed_end - 0.0001); + } //Path::operator () means get point at time t start_attach_point = return_at_first_cusp(path_in[0])(attach_start); @@ -242,8 +326,8 @@ Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in) Geom::PathVector real_pathv; //Construct the pattern (pat_str stands for pattern string) - char* pat_str = new char[200]; - sprintf(pat_str, "M 1,0 1,1 C %5.5f,1 0,0.5 0,0.5 0,0.5 %5.5f,0 1,0 Z", (double)smoothing, (double)smoothing); + char pat_str[200]; + sprintf(pat_str, "M 1,0 1,1 C %5.5f,1 0,0.5 0,0.5 0,0.5 %5.5f,0 1,0 Z", 1 - (double)smoothing, 1 - (double)smoothing); Geom::PathVector pat_vec = sp_svg_read_pathv(pat_str); Geom::Piecewise > pwd2; @@ -253,12 +337,13 @@ Geom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in) Geom::PathVector sht_path; sht_path.push_back(pathv_out[1]); - sht_path = Outline::outlinePath_extr(sht_path, line_width, LINEJOIN_STRAIGHT, butt_straight, miter_limit); + sht_path = Outline::PathVectorOutline(sht_path, line_width, butt_straight, static_cast(join_type.get_value()) , miter_limit); real_pathv.push_back(sht_path[0]); - sprintf(pat_str, "M 0,0 0,1 C %5.5f,1 1,0.5 1,0.5 1,0.5 %5.5f,0 0,0 Z", (double)smoothing, (double)smoothing); - pat_vec = sp_svg_read_pathv(pat_str); + char pat_str_1[200]; + sprintf(pat_str_1, "M 0,0 0,1 C %5.5f,1 1,0.5 1,0.5 1,0.5 %5.5f,0 0,0 Z", (double)smoothing, (double)smoothing); + pat_vec = sp_svg_read_pathv(pat_str_1); pwd2 = Geom::Piecewise > (); pwd2.concat(stretch_along(pathv_out[2].toPwSb(), pat_vec[0], line_width)); @@ -298,7 +383,7 @@ Geom::PathVector LPETaperStroke::doEffect_simplePath(Geom::PathVector const & pa trimmed_start.append(path_in[0] [i]); } - #define OVERLAP 0.001 + #define OVERLAP (0.001 / (line_width < 1 ? 1 : line_width)) trimmed_start.append(*subdivide_at(curve_start, (attach_start - loc) + OVERLAP, true)); curve_start = subdivide_at(curve_start, attach_start - loc, false); @@ -451,6 +536,7 @@ Geom::Piecewise > stretch_along(Geom::Piecewise +#include <2geom/path.h> +#include <2geom/circle.h> +#include <2geom/sbasis-to-bezier.h> +#include <2geom/shape.h> +#include <2geom/transforms.h> +#include <2geom/path-sink.h> + +namespace Geom +{ + /** + * Refer to: Weisstein, Eric W. "Circle-Circle Intersection." + From MathWorld--A Wolfram Web Resource. + http://mathworld.wolfram.com/Circle-CircleIntersection.html + * + * @return 0 if no intersection + * @return 1 if one circle is contained in the other + * @return 2 if intersections are found (they are written to p0 and p1) + */ + static int circle_circle_intersection(Circle const &circle0, Circle const &circle1, + Point & p0, Point & p1) + { + Point X0 = circle0.center(); + double r0 = circle0.ray(); + Point X1 = circle1.center(); + double r1 = circle1.ray(); + + /* dx and dy are the vertical and horizontal distances between + * the circle centers. + */ + Point D = X1 - X0; + + /* Determine the straight-line distance between the centers. */ + double d = L2(D); + + /* Check for solvability. */ + if (d > (r0 + r1)) + { + /* no solution. circles do not intersect. */ + return 0; + } + if (d <= fabs(r0 - r1)) + { + /* no solution. one circle is contained in the other */ + return 1; + } + + /* 'point 2' is the point where the line through the circle + * intersection points crosses the line between the circle + * centers. + */ + + /* Determine the distance from point 0 to point 2. */ + double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ; + + /* Determine the coordinates of point 2. */ + Point p2 = X0 + D * (a/d); + + /* Determine the distance from point 2 to either of the + * intersection points. + */ + double h = std::sqrt((r0*r0) - (a*a)); + + /* Now determine the offsets of the intersection points from + * point 2. + */ + Point r = (h/d)*rot90(D); + + /* Determine the absolute intersection points. */ + p0 = p2 + r; + p1 = p2 - r; + + return 2; + } + /** + * Find circle that touches inside of the curve, with radius matching the curvature, at time value \c t. + * Because this method internally uses unitTangentAt, t should be smaller than 1.0 (see unitTangentAt). + */ + static Circle touching_circle( D2 const &curve, double t, double tol=0.01 ) + { + D2 dM=derivative(curve); + if ( are_near(L2sq(dM(t)),0.) ) { + dM=derivative(dM); + } + if ( are_near(L2sq(dM(t)),0.) ) { // try second time + dM=derivative(dM); + } + Piecewise > unitv = unitVector(dM,tol); + Piecewise dMlength = dot(Piecewise >(dM),unitv); + Piecewise k = cross(derivative(unitv),unitv); + k = divide(k,dMlength,tol,3); + double curv = k(t); // note that this value is signed + + Geom::Point normal = unitTangentAt(curve, t).cw(); + double radius = 1/curv; + Geom::Point center = curve(t) + radius*normal; + return Geom::Circle(center, fabs(radius)); + } + + static std::vector split_at_cusps(const Geom::Path& in) + { + Geom::PathVector out = Geom::PathVector(); + Geom::Path temp = Geom::Path(); + + for (unsigned path_descr = 0; path_descr < in.size(); path_descr++) + { + temp = Geom::Path(); + temp.append(in[path_descr]); + out.push_back(temp); + } + + return out; + } + + static Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2 const & sbasis_in) + { + std::vector temp; + sbasis_to_bezier(temp, sbasis_in, 4); + return Geom::CubicBezier( temp ); + } + + static boost::optional intersection_point( Geom::Point const & origin_a, Geom::Point const & vector_a, + Geom::Point const & origin_b, Geom::Point const & vector_b) + { + Geom::Coord denom = cross(vector_b, vector_a); + if (!Geom::are_near(denom,0.)){ + Geom::Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom; + return origin_a + t * vector_a; + } + return boost::none; + } +} + +namespace Outline +{ + +typedef Geom::D2 D2SB; +typedef Geom::Piecewise PWD2; + +unsigned bezierOrder (const Geom::Curve* curve_in) +{ + using namespace Geom; + //cast it + const CubicBezier *cbc = dynamic_cast(curve_in); + if (cbc) return 3; + const QuadraticBezier * qbc = dynamic_cast(curve_in); + if (qbc) return 2; + const BezierCurveN<1U> * lbc = dynamic_cast *>(curve_in); + if (lbc) return 1; + return 0; +} + +//returns true if the angle formed by the curves and their handles +//is >180 clockwise, otherwise false. +bool outside_angle (const Geom::Curve* cbc1, const Geom::Curve* cbc2) +{ + Geom::Point start_point = cbc1->initialPoint(); + Geom::Point end_point = cbc2->finalPoint(); + unsigned order = bezierOrder(cbc1); + switch (order) + { + case 3: + start_point = ( dynamic_cast(cbc1) )->operator [] (2); + break; + case 2: + start_point = ( dynamic_cast(cbc1) )->operator [] (1); + break; + } + order = bezierOrder(cbc2); + switch (order) + { + case 3: + end_point = ( dynamic_cast(cbc2) )->operator [] (1); + break; + case 2: + end_point = ( dynamic_cast(cbc2) )->operator[] (1); + break; + } + return false; +} + +void extrapolate_curves(Geom::Path& path_builder, Geom::Curve* cbc1, Geom::Curve*cbc2, Geom::Point endPt, double miter_limit) +{ + + Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); + if (cross.empty()) + { + Geom::Path pth; + pth.append(*cbc1); + + //Geom::Point tang1 = Geom::unitTangentAt(pth.toPwSb()[0], 1); + + pth = Geom::Path(); + pth.append( *cbc2 ); + Geom::Point tang2 = Geom::unitTangentAt(pth.toPwSb()[0], 0); + + + Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(cbc1->toSBasis()), 0.); + Geom::Circle circle2 = Geom::touching_circle(cbc2->toSBasis(), 0); + + Geom::Point points[2]; + int solutions = Geom::circle_circle_intersection(circle1, circle2, points[0], points[1]); + if (solutions == 2) + { + Geom::Point sol(0,0); + if ( dot(tang2,points[0]-endPt) > 0 ) + { + // points[0] is bad, choose points[1] + sol = points[1]; + } + else if ( dot(tang2,points[1]-endPt) > 0 ) { // points[0] could be good, now check points[1] + // points[1] is bad, choose points[0] + sol = points[0]; + } + else + { + // both points are good, choose nearest + sol = ( distanceSq(endPt, points[0]) < distanceSq(endPt, points[1]) ) ? + points[0] : points[1]; + } + Geom::EllipticalArc *arc0 = circle1.arc(cbc1->finalPoint(), 0.5*(cbc1->finalPoint()+sol), sol, true); + Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); + + if (arc0) + { + path_builder.append (arc0->toSBasis()); + delete arc0; + arc0 = NULL; + } + + if (arc1) + { + path_builder.append (arc1->toSBasis()); + delete arc1; + arc1 = NULL; + } + } + else + { + path_builder.appendNew (endPt); + } + } + else + { + path_builder.appendNew (endPt); + } +} + Geom::Path half_outline_extrp(const Geom::Path& path_in, double line_width, ButtType linecap_type, double miter_limit) + { + Geom::PathVector pv = split_at_cusps(path_in); + unsigned m; + Path path_outline = Path(); + Path path_tangent = Path(); + + Geom::Point initialPoint; + Geom::Point endPoint; + + Geom::Path path_builder = Geom::Path(); + Geom::PathVector * pathvec; + + //load the first portion in before the loop starts + { + path_outline = Path(); + path_outline.LoadPath(pv[0], Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); + //now half of first cusp has been loaded + + pathvec = path_tangent.MakePathVector(); + path_tangent = Path(); + + //instead of array accessing twice, dereferencing used for clarity + initialPoint = (*pathvec)[0].initialPoint(); + + path_builder.start(initialPoint); + path_builder.append( (*pathvec)[0] ); + + path_outline = Path(); + path_outline.LoadPath(pv[1], Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); + + delete pathvec; pathvec = NULL; + pathvec = path_tangent.MakePathVector(); + path_tangent = Path(); + + Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); + Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); + + extrapolate_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); + + path_builder.append( (*pathvec)[0] ); + + //always set pointers null after deleting + delete pathvec; pathvec = NULL; + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + } + + for (m = 2; m < pv.size(); m++) + { + path_outline = Path(); + path_outline.LoadPath(pv[m], Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); + + delete pathvec; pathvec = NULL; + pathvec = path_tangent.MakePathVector(); + + Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); + Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); + + extrapolate_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); + path_builder.append( (*pathvec)[0] ); + + delete pathvec; pathvec = NULL; + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + } + + return path_builder; + } + + //Create a reflected outline join. + //Note: it is generally recommended to let half_outline do this for you! + //path_builder: the path to append the curves to + //cbc1: the curve before the join + //cbc2: the curve after the join + //endPt: the point to end at + //miter_limit: the miter parameter + void reflect_curves(Geom::Path& path_builder, Geom::Curve* cbc1, Geom::Curve* cbc2, Geom::Point endPt, double miter_limit) + { + //the most important work for the reflected join is done here + + //determine where we are in the path. If we're on the inside, ignore + //and just lineTo. On the outside, we'll do a little reflection magic :) + Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); + if (cross.empty()) + { + //probably on the outside of the corner + Geom::Path pth; + pth.append(*cbc1); + + Geom::Point tang1 = Geom::unitTangentAt(pth.toPwSb()[0], 1); + + //reflect curves along the bevel + D2SB newcurve1 = pth.toPwSb()[0] * + Geom::reflection ( -Geom::rot90(tang1) , + cbc1->finalPoint() ); + + Geom::CubicBezier bzr1 = sbasis_to_cubicbezier(Geom::reverse(newcurve1)); + + pth = Geom::Path(); + pth.append( *cbc2 ); + Geom::Point tang2 = Geom::unitTangentAt(pth.toPwSb()[0], 0); + + D2SB newcurve2 = pth.toPwSb()[0] * + Geom::reflection ( -Geom::rot90(tang2) , + cbc2->initialPoint() ); + Geom::CubicBezier bzr2 = sbasis_to_cubicbezier(Geom::reverse(newcurve2)); + + cross = Geom::crossings(bzr1, bzr2); + if ( cross.empty() ) + { + //std::cout << "Oops, no crossings!" << std::endl; + //curves didn't cross; default to miter + /*boost::optional p = intersection_point (cbc1->finalPoint(), tang1, + cbc2->initialPoint(), tang2); + if (p) + { + path_builder.appendNew (*p); + }*/ + //bevel + path_builder.appendNew( endPt ); + } + else + { + //join + std::pair sub1 = bzr1.subdivide(cross[0].ta); + std::pair sub2 = bzr2.subdivide(cross[0].tb); + + //@TODO joins have a strange tendency to cross themselves twice. Check this. + + //sections commented out are for general stability + path_builder.appendNew (sub1.first[1], sub1.first[2], /*sub1.first[3]*/ sub2.second[0] ); + path_builder.appendNew (sub2.second[1], sub2.second[2], /*sub2.second[3]*/ endPt ); + } + } + else // cross.empty() + { + //probably on the inside of the corner + path_builder.appendNew ( endPt ); + } + } + + /** @brief Converts a path to one half of an outline. + * path_in: The input path to use. (To create the other side use path_in.reverse() ) + * line_width: the line width to use (usually you want to divide this by 2) + * linecap_type: (not used here) the cap to apply. Passed to libvarot. + * miter_limit: the miter parameter + */ + Geom::Path half_outline(const Geom::Path& path_in, double line_width, ButtType linecap_type, double miter_limit) + { + Geom::PathVector pv = split_at_cusps(path_in); + unsigned m; + Path path_outline = Path(); + Path path_tangent = Path(); + //needed for closing the path + Geom::Point initialPoint; + Geom::Point endPoint; + + //some issues prevented me from using a PathBuilder here + //it seems like PathBuilder::peek() gave me a null reference exception + //and I was unable to get a stack trace on Windows, so had to switch to Linux + //to see what the hell was wrong. :( + //I wasted five hours opening it in IDAPro, VS2012, and GDB Windows + + /*Program received signal SIGSEGV, Segmentation fault. + 0x00000000006539ac in get_curves (this=0x0) + at /usr/include/c++/4.6/bits/locale_facets.h:1077 + 1077 { return __c; } + */ + + Geom::Path path_builder = Geom::Path(); + Geom::PathVector * pathvec; + + //load the first portion in before the loop starts + { + path_outline = Path(); + path_outline.LoadPath(pv[0], Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); + //now half of first cusp has been loaded + + pathvec = path_tangent.MakePathVector(); + path_tangent = Path(); + + //instead of array accessing twice, dereferencing used for clarity + initialPoint = (*pathvec)[0].initialPoint(); + + path_builder.start(initialPoint); + path_builder.append( (*pathvec)[0] ); + + path_outline = Path(); + path_outline.LoadPath(pv[1], Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); + + delete pathvec; pathvec = NULL; + pathvec = path_tangent.MakePathVector(); + path_tangent = Path(); + + Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); + Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); + + reflect_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); + + path_builder.append( (*pathvec)[0] ); + + //always set pointers null after deleting + delete pathvec; pathvec = NULL; + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + } + + for (m = 2; m < pv.size(); m++) + { + path_outline = Path(); + path_outline.LoadPath(pv[m], Geom::Affine(), false, false); + path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); + + delete pathvec; pathvec = NULL; + pathvec = path_tangent.MakePathVector(); + + Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); + Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); + + reflect_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); + path_builder.append( (*pathvec)[0] ); + + delete pathvec; pathvec = NULL; + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + } + + return path_builder; + } + + Geom::PathVector outlinePath(const Geom::PathVector& path_in, double line_width, JoinType join, ButtType butt, double miter_lim) + { + Path p = Path(); + Path outlinepath = Path(); + for (unsigned i = 0; i < path_in.size(); i++) + { + p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true)); + } + + Geom::PathVector path_out; + for (unsigned lmnop = 0; lmnop < path_in.size(); lmnop++) + { + if (path_in[lmnop].size() > 1) + { + Geom::Path p_init; + Geom::Path p_rev; + Geom::PathBuilder pb = Geom::PathBuilder(); + + if ( !path_in[lmnop].closed() ) + { + p_init = Outline::half_outline( path_in[lmnop], -line_width, butt, + miter_lim ); + p_rev = Outline::half_outline( path_in[lmnop].reverse(), -line_width, butt, + miter_lim ); + + pb.moveTo(p_init.initialPoint() ); + pb.append(p_init); + + //cap + if (butt == butt_straight) { + pb.lineTo(p_rev.initialPoint() ); + } else if (butt == butt_round) { + pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_rev.initialPoint() ); + } else if (butt == butt_square) { + //don't know what to do + pb.lineTo(p_rev.initialPoint() ); + } else if (butt == butt_pointy) { + //don't know what to do + pb.lineTo(p_rev.initialPoint() ); + } + + pb.append(p_rev); + + if (butt == butt_straight) { + pb.lineTo(p_init.initialPoint() ); + } else if (butt == butt_round) { + pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_init.initialPoint() ); + } else if (butt == butt_square) { + //don't know what to do + pb.lineTo(p_init.initialPoint() ); + } else if (butt == butt_pointy) { + //don't know what to do + //Geom::Point end_deriv = Geom::unitTangentAt( Geom::reverse(path_in[lmnop].toPwSb()[path_in[lmnop].size()]), 0); + //double radius = 0.5 * Geom::distance(path_in[lmnop].finalPoint(), p_rev.initialPoint()); + + pb.lineTo(p_init.initialPoint() ); + } + } + else + { + //final join + //refer to half_outline for documentation + Geom::Path p_almost = path_in[lmnop]; + p_almost.appendNew ( path_in[lmnop].initialPoint() ); + p_init = Outline::half_outline( p_almost, -line_width, butt, + miter_lim ); + p_rev = Outline::half_outline( p_almost.reverse(), -line_width, butt, + miter_lim ); + p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); + + //this is a kludge, because I can't find how to make this work properly + bool lastIsLinear = ( (Geom::distance(path_in[lmnop].finalPoint(), + path_in[lmnop] [path_in[lmnop].size() - 1].finalPoint())) == + (path_in[lmnop] [path_in[lmnop].size()].length())); + + p_almost = p_init; + if (lastIsLinear) + { + p_almost.erase_last(); p_almost.erase_last(); + } + + //outside test + Geom::Curve* cbc1 = p_almost[p_almost.size() - 1].duplicate(); + Geom::Curve* cbc2 = p_almost[0].duplicate(); + + Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); + + if (cross.empty()) + { + //this is the outside path + + //reuse the old one + p_init = p_almost; + Outline::reflect_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); + pb.moveTo(p_init.initialPoint()); pb.append(p_init); + } + else + { + //inside, carry on :-) + pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); + } + + p_almost = p_rev; + if (lastIsLinear) + { + p_almost.erase(p_almost.begin() ); + p_almost.erase(p_almost.begin() ); + } + + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + + cbc1 = p_almost[p_almost.size() - 1].duplicate(); + cbc2 = p_almost[0].duplicate(); + + cross = Geom::crossings(*cbc1, *cbc2); + + if (cross.empty()) + { + //outside path + + p_init = p_almost; + reflect_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); + pb.moveTo(p_init.initialPoint()); pb.append(p_init); + } + else + { + //inside + pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); + } + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + } + //pb.closePath(); + pb.flush(); + Geom::PathVector pv_np = pb.peek(); + //hack + for (unsigned abcd = 0; abcd < pv_np.size(); abcd++) + { + path_out.push_back( pv_np[abcd] ); + } + } + else + { + p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); + p.Outline(&outlinepath, line_width / 2, join, butt, miter_lim); + std::vector *pv_p = outlinepath.MakePathVector(); + //hack + path_out.push_back( (*pv_p)[0].reverse() ); + delete pv_p; + } + } + return path_out; + } + Geom::PathVector outlinePath_extr(const Geom::PathVector& path_in, double line_width, LineJoinType join, ButtType butt, double miter_lim) + { + Path p = Path(); + Path outlinepath = Path(); + for (unsigned i = 0; i < path_in.size(); i++) + { + p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true)); + } + + Geom::PathVector path_out; + for (unsigned lmnop = 0; lmnop < path_in.size(); lmnop++) + { + if (path_in[lmnop].size() > 1) + { + Geom::Path p_init; + Geom::Path p_rev; + Geom::PathBuilder pb = Geom::PathBuilder(); + + if ( !path_in[lmnop].closed() ) + { + p_init = Outline::half_outline_extrp( path_in[lmnop], -line_width, butt, + miter_lim ); + p_rev = Outline::half_outline_extrp( path_in[lmnop].reverse(), -line_width, butt, + miter_lim ); + + pb.moveTo(p_init.initialPoint() ); + pb.append(p_init); + + //cap + if (butt == butt_straight) { + pb.lineTo(p_rev.initialPoint() ); + } else if (butt == butt_round) { + pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_rev.initialPoint() ); + } else if (butt == butt_square) { + //don't know what to do + pb.lineTo(p_rev.initialPoint() ); + } else if (butt == butt_pointy) { + //don't know what to do + pb.lineTo(p_rev.initialPoint() ); + } + + pb.append(p_rev); + + if (butt == butt_straight) { + pb.lineTo(p_init.initialPoint() ); + } else if (butt == butt_round) { + pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_init.initialPoint() ); + } else if (butt == butt_square) { + //don't know what to do + pb.lineTo(p_init.initialPoint() ); + } else if (butt == butt_pointy) { + //don't know what to do + //Geom::Point end_deriv = Geom::unitTangentAt( Geom::reverse(path_in[lmnop].toPwSb()[path_in[lmnop].size()]), 0); + //double radius = 0.5 * Geom::distance(path_in[lmnop].finalPoint(), p_rev.initialPoint()); + + pb.lineTo(p_init.initialPoint() ); + } + } + else + { + //final join + //refer to half_outline for documentation + Geom::Path p_almost = path_in[lmnop]; + p_almost.appendNew ( path_in[lmnop].initialPoint() ); + p_init = Outline::half_outline_extrp( p_almost, -line_width, butt, + miter_lim ); + p_rev = Outline::half_outline_extrp( p_almost.reverse(), -line_width, butt, + miter_lim ); + p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); + + //this is a kludge, because I can't find how to make this work properly + bool lastIsLinear = ( (Geom::distance(path_in[lmnop].finalPoint(), + path_in[lmnop] [path_in[lmnop].size() - 1].finalPoint())) == + (path_in[lmnop] [path_in[lmnop].size()].length())); + + p_almost = p_init; + if (lastIsLinear) + { + p_almost.erase_last(); p_almost.erase_last(); + } + + //outside test + Geom::Curve* cbc1 = p_almost[p_almost.size() - 1].duplicate(); + Geom::Curve* cbc2 = p_almost[0].duplicate(); + + Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); + + if (cross.empty()) + { + //this is the outside path + + //reuse the old one + p_init = p_almost; + Outline::extrapolate_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); + pb.moveTo(p_init.initialPoint()); pb.append(p_init); + } + else + { + //inside, carry on :-) + pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); + } + + p_almost = p_rev; + if (lastIsLinear) + { + p_almost.erase(p_almost.begin() ); + p_almost.erase(p_almost.begin() ); + } + + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + + cbc1 = p_almost[p_almost.size() - 1].duplicate(); + cbc2 = p_almost[0].duplicate(); + + cross = Geom::crossings(*cbc1, *cbc2); + + if (cross.empty()) + { + //outside path + + p_init = p_almost; + extrapolate_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); + pb.moveTo(p_init.initialPoint()); pb.append(p_init); + } + else + { + //inside + pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); + } + delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; + } + //pb.closePath(); + pb.flush(); + Geom::PathVector pv_np = pb.peek(); + //hack + for (unsigned abcd = 0; abcd < pv_np.size(); abcd++) + { + path_out.push_back( pv_np[abcd] ); + } + } + else + { + p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); + p.Outline(&outlinepath, line_width / 2, join_pointy, butt, miter_lim); + std::vector *pv_p = outlinepath.MakePathVector(); + //hack + path_out.push_back( (*pv_p)[0].reverse() ); + delete pv_p; + } + } + return path_out; + } + +Geom::PathVector PathVectorOutline(Geom::PathVector const & path_in, double line_width, ButtType linecap_type, + join_typ linejoin_type, double miter_limit) +{ + std::vector path_out = std::vector(); + if (path_in.empty()) + { + return path_out; + } + Path p = Path(); + Path outlinepath = Path(); + for (unsigned i = 0; i < path_in.size(); i++) + { + p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true)); + } + + #define miter_lim fabs(line_width * miter_limit) + + //magic! + if (linejoin_type <= 2) + { + p.Outline(&outlinepath, line_width / 2, linejoin_type, + linecap_type, miter_lim); + //fix memory leak + std::vector *pv_p = outlinepath.MakePathVector(); + path_out = *pv_p; + delete pv_p; + + } else if (linejoin_type == 3) { + //reflected arc join + path_out = outlinePath(path_in, line_width, linejoin_type, + linecap_type , miter_lim); + + } else if (linejoin_type == 4) { + //extrapolated arc join + path_out = outlinePath_extr(path_in, line_width, LINEJOIN_STRAIGHT, linecap_type, miter_lim); + + } + + #undef miter_lim + return path_out; +} + +} // namespace Outline + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : diff --git a/src/live_effects/pathoutlineprovider.h b/src/live_effects/pathoutlineprovider.h old mode 100755 new mode 100644 index 8aa2e38ad..1a363c35b --- a/src/live_effects/pathoutlineprovider.h +++ b/src/live_effects/pathoutlineprovider.h @@ -1,766 +1,25 @@ -#pragma once - -#include <2geom/path.h> -#include <2geom/circle.h> -#include <2geom/sbasis-to-bezier.h> -#include <2geom/shape.h> -#include <2geom/transforms.h> -#include <2geom/path-sink.h> - -#include -#include - -enum LineJoinType { - LINEJOIN_STRAIGHT, - LINEJOIN_ROUND, - LINEJOIN_POINTY, - LINEJOIN_REFLECTED, - LINEJOIN_EXTRAPOLATED -}; - -namespace Geom -{ - /** - * Refer to: Weisstein, Eric W. "Circle-Circle Intersection." - From MathWorld--A Wolfram Web Resource. - http://mathworld.wolfram.com/Circle-CircleIntersection.html - * - * @return 0 if no intersection - * @return 1 if one circle is contained in the other - * @return 2 if intersections are found (they are written to p0 and p1) - */ - static int circle_circle_intersection(Circle const &circle0, Circle const &circle1, - Point & p0, Point & p1) - { - Point X0 = circle0.center(); - double r0 = circle0.ray(); - Point X1 = circle1.center(); - double r1 = circle1.ray(); - - /* dx and dy are the vertical and horizontal distances between - * the circle centers. - */ - Point D = X1 - X0; - - /* Determine the straight-line distance between the centers. */ - double d = L2(D); - - /* Check for solvability. */ - if (d > (r0 + r1)) - { - /* no solution. circles do not intersect. */ - return 0; - } - if (d <= fabs(r0 - r1)) - { - /* no solution. one circle is contained in the other */ - return 1; - } - - /* 'point 2' is the point where the line through the circle - * intersection points crosses the line between the circle - * centers. - */ - - /* Determine the distance from point 0 to point 2. */ - double a = ((r0*r0) - (r1*r1) + (d*d)) / (2.0 * d) ; - - /* Determine the coordinates of point 2. */ - Point p2 = X0 + D * (a/d); - - /* Determine the distance from point 2 to either of the - * intersection points. - */ - double h = std::sqrt((r0*r0) - (a*a)); - - /* Now determine the offsets of the intersection points from - * point 2. - */ - Point r = (h/d)*rot90(D); - - /* Determine the absolute intersection points. */ - p0 = p2 + r; - p1 = p2 - r; - - return 2; - } - /** - * Find circle that touches inside of the curve, with radius matching the curvature, at time value \c t. - * Because this method internally uses unitTangentAt, t should be smaller than 1.0 (see unitTangentAt). - */ - static Circle touching_circle( D2 const &curve, double t, double tol=0.01 ) - { - D2 dM=derivative(curve); - if ( are_near(L2sq(dM(t)),0.) ) { - dM=derivative(dM); - } - if ( are_near(L2sq(dM(t)),0.) ) { // try second time - dM=derivative(dM); - } - Piecewise > unitv = unitVector(dM,tol); - Piecewise dMlength = dot(Piecewise >(dM),unitv); - Piecewise k = cross(derivative(unitv),unitv); - k = divide(k,dMlength,tol,3); - double curv = k(t); // note that this value is signed - - Geom::Point normal = unitTangentAt(curve, t).cw(); - double radius = 1/curv; - Geom::Point center = curve(t) + radius*normal; - return Geom::Circle(center, fabs(radius)); - } - - static std::vector split_at_cusps(const Geom::Path& in) - { - Geom::PathVector out = Geom::PathVector(); - Geom::Path temp = Geom::Path(); - - for (unsigned path_descr = 0; path_descr < in.size(); path_descr++) - { - temp = Geom::Path(); - temp.append(in[path_descr]); - out.push_back(temp); - } - - return out; - } - - static Geom::CubicBezier sbasis_to_cubicbezier(Geom::D2 const & sbasis_in) - { - std::vector temp; - sbasis_to_bezier(temp, sbasis_in, 4); - return Geom::CubicBezier( temp ); - } - - static boost::optional intersection_point( Geom::Point const & origin_a, Geom::Point const & vector_a, - Geom::Point const & origin_b, Geom::Point const & vector_b) - { - Geom::Coord denom = cross(vector_b, vector_a); - if (!Geom::are_near(denom,0.)){ - Geom::Coord t = (cross(origin_a,vector_b) + cross(vector_b,origin_b)) / denom; - return origin_a + t * vector_a; - } - return boost::none; - } -} - -namespace Outline -{ - - typedef Geom::D2 D2SB; - typedef Geom::Piecewise PWD2; - - static void extrapolate_curves(Geom::Path& path_builder, Geom::Curve* cbc1, Geom::Curve*cbc2, Geom::Point endPt, double miter_limit) -{ - Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); - if (cross.empty()) - { - Geom::Path pth; - pth.append(*cbc1); - - Geom::Point tang1 = Geom::unitTangentAt(pth.toPwSb()[0], 1); - - pth = Geom::Path(); - pth.append( *cbc2 ); - Geom::Point tang2 = Geom::unitTangentAt(pth.toPwSb()[0], 0); - - - Geom::Circle circle1 = Geom::touching_circle(Geom::reverse(cbc1->toSBasis()), 0.); - Geom::Circle circle2 = Geom::touching_circle(cbc2->toSBasis(), 0); - - Geom::Point points[2]; - int solutions = Geom::circle_circle_intersection(circle1, circle2, points[0], points[1]); - if (solutions == 2) - { - Geom::Point sol(0,0); - if ( dot(tang2,points[0]-endPt) > 0 ) - { - // points[0] is bad, choose points[1] - sol = points[1]; - } - else if ( dot(tang2,points[1]-endPt) > 0 ) { // points[0] could be good, now check points[1] - // points[1] is bad, choose points[0] - sol = points[0]; - } - else - { - // both points are good, choose nearest - sol = ( distanceSq(endPt, points[0]) < distanceSq(endPt, points[1]) ) ? - points[0] : points[1]; - } - Geom::EllipticalArc *arc0 = circle1.arc(cbc1->finalPoint(), 0.5*(cbc1->finalPoint()+sol), sol, true); - Geom::EllipticalArc *arc1 = circle2.arc(sol, 0.5*(sol+endPt), endPt, true); - - if (arc0) - { - path_builder.append (arc0->toSBasis()); - delete arc0; - arc0 = NULL; - } - - if (arc1) - { - path_builder.append (arc1->toSBasis()); - delete arc1; - arc1 = NULL; - } - } - else - { - path_builder.appendNew (endPt); - } - } - else - { - path_builder.appendNew (endPt); - } -} - static Geom::Path half_outline_extrp(const Geom::Path& path_in, double line_width, ButtType linecap_type, double miter_limit) - { - Geom::PathVector pv = split_at_cusps(path_in); - unsigned m; - Path path_outline = Path(); - Path path_tangent = Path(); - - Geom::Point initialPoint; - Geom::Point endPoint; - - Geom::Path path_builder = Geom::Path(); - Geom::PathVector * pathvec; - - //load the first portion in before the loop starts - { - path_outline = Path(); - path_outline.LoadPath(pv[0], Geom::Affine(), false, false); - path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); - //now half of first cusp has been loaded - - pathvec = path_tangent.MakePathVector(); - path_tangent = Path(); - - //instead of array accessing twice, dereferencing used for clarity - initialPoint = (*pathvec)[0].initialPoint(); - - path_builder.start(initialPoint); - path_builder.append( (*pathvec)[0] ); - - path_outline = Path(); - path_outline.LoadPath(pv[1], Geom::Affine(), false, false); - path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); - - delete pathvec; pathvec = NULL; - pathvec = path_tangent.MakePathVector(); - path_tangent = Path(); - - Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); - Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); - - extrapolate_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); - - path_builder.append( (*pathvec)[0] ); - - //always set pointers null after deleting - delete pathvec; pathvec = NULL; - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - } - - for (m = 2; m < pv.size(); m++) - { - path_outline = Path(); - path_outline.LoadPath(pv[m], Geom::Affine(), false, false); - path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); - - delete pathvec; pathvec = NULL; - pathvec = path_tangent.MakePathVector(); - - Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); - Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); - - extrapolate_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); - path_builder.append( (*pathvec)[0] ); - - delete pathvec; pathvec = NULL; - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - } - - return path_builder; - } - - //Create a reflected outline join. - //Note: it is generally recommended to let half_outline do this for you! - //path_builder: the path to append the curves to - //cbc1: the curve before the join - //cbc2: the curve after the join - //endPt: the point to end at - //miter_limit: the miter parameter - static void reflect_curves(Geom::Path& path_builder, Geom::Curve* cbc1, Geom::Curve* cbc2, Geom::Point endPt, double miter_limit) - { - //the most important work for the reflected join is done here - - //determine where we are in the path. If we're on the inside, ignore - //and just lineTo. On the outside, we'll do a little reflection magic :) - Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); - if (cross.empty()) - { - //probably on the outside of the corner - Geom::Path pth; - pth.append(*cbc1); - - Geom::Point tang1 = Geom::unitTangentAt(pth.toPwSb()[0], 1); - - //reflect curves along the bevel - D2SB newcurve1 = pth.toPwSb()[0] * - Geom::reflection ( -Geom::rot90(tang1) , - cbc1->finalPoint() ); - - Geom::CubicBezier bzr1 = sbasis_to_cubicbezier(Geom::reverse(newcurve1)); - - pth = Geom::Path(); - pth.append( *cbc2 ); - Geom::Point tang2 = Geom::unitTangentAt(pth.toPwSb()[0], 0); - - D2SB newcurve2 = pth.toPwSb()[0] * - Geom::reflection ( -Geom::rot90(tang2) , - cbc2->initialPoint() ); - Geom::CubicBezier bzr2 = sbasis_to_cubicbezier(Geom::reverse(newcurve2)); - - cross = Geom::crossings(bzr1, bzr2); - if ( cross.empty() ) - { - //std::cout << "Oops, no crossings!" << std::endl; - //curves didn't cross; default to miter - /*boost::optional p = intersection_point (cbc1->finalPoint(), tang1, - cbc2->initialPoint(), tang2); - if (p) - { - path_builder.appendNew (*p); - }*/ - //bevel - path_builder.appendNew( endPt ); - } - else - { - //join - std::pair sub1 = bzr1.subdivide(cross[0].ta); - std::pair sub2 = bzr2.subdivide(cross[0].tb); - - //@TODO joins have a strange tendency to cross themselves twice. Check this. - - //sections commented out are for general stability - path_builder.appendNew (sub1.first[1], sub1.first[2], /*sub1.first[3]*/ sub2.second[0] ); - path_builder.appendNew (sub2.second[1], sub2.second[2], /*sub2.second[3]*/ endPt ); - } - } - else // cross.empty() - { - //probably on the inside of the corner - path_builder.appendNew ( endPt ); - } - } - - /** @brief Converts a path to one half of an outline. - * path_in: The input path to use. (To create the other side use path_in.reverse() ) - * line_width: the line width to use (usually you want to divide this by 2) - * linecap_type: (not used here) the cap to apply. Passed to libvarot. - * miter_limit: the miter parameter - */ - static Geom::Path half_outline(const Geom::Path& path_in, double line_width, ButtType linecap_type, double miter_limit) - { - Geom::PathVector pv = split_at_cusps(path_in); - unsigned m; - Path path_outline = Path(); - Path path_tangent = Path(); - //needed for closing the path - Geom::Point initialPoint; - Geom::Point endPoint; - - //some issues prevented me from using a PathBuilder here - //it seems like PathBuilder::peek() gave me a null reference exception - //and I was unable to get a stack trace on Windows, so had to switch to Linux - //to see what the hell was wrong. :( - //I wasted five hours opening it in IDAPro, VS2012, and GDB Windows - - /*Program received signal SIGSEGV, Segmentation fault. - 0x00000000006539ac in get_curves (this=0x0) - at /usr/include/c++/4.6/bits/locale_facets.h:1077 - 1077 { return __c; } - */ - - Geom::Path path_builder = Geom::Path(); - Geom::PathVector * pathvec; - - //load the first portion in before the loop starts - { - path_outline = Path(); - path_outline.LoadPath(pv[0], Geom::Affine(), false, false); - path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); - //now half of first cusp has been loaded - - pathvec = path_tangent.MakePathVector(); - path_tangent = Path(); - - //instead of array accessing twice, dereferencing used for clarity - initialPoint = (*pathvec)[0].initialPoint(); - - path_builder.start(initialPoint); - path_builder.append( (*pathvec)[0] ); - - path_outline = Path(); - path_outline.LoadPath(pv[1], Geom::Affine(), false, false); - path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); - - delete pathvec; pathvec = NULL; - pathvec = path_tangent.MakePathVector(); - path_tangent = Path(); - - Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); - Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); - - reflect_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); - - path_builder.append( (*pathvec)[0] ); - - //always set pointers null after deleting - delete pathvec; pathvec = NULL; - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - } - - for (m = 2; m < pv.size(); m++) - { - path_outline = Path(); - path_outline.LoadPath(pv[m], Geom::Affine(), false, false); - path_outline.OutsideOutline(&path_tangent, line_width / 2, join_straight, linecap_type, 10); - - delete pathvec; pathvec = NULL; - pathvec = path_tangent.MakePathVector(); - - Geom::Curve *cbc1 = path_builder[path_builder.size() - 1].duplicate(); - Geom::Curve *cbc2 = (*pathvec)[0][0].duplicate(); - - reflect_curves(path_builder, cbc1, cbc2, (*pathvec)[0].initialPoint(), miter_limit ); - path_builder.append( (*pathvec)[0] ); - - delete pathvec; pathvec = NULL; - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - } - - return path_builder; - } - - static Geom::PathVector outlinePath(const Geom::PathVector& path_in, double line_width, JoinType join, ButtType butt, double miter_lim) - { - Path p = Path(); - Path outlinepath = Path(); - for (unsigned i = 0; i < path_in.size(); i++) - { - p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true)); - } - - Geom::PathVector path_out; - for (unsigned lmnop = 0; lmnop < path_in.size(); lmnop++) - { - if (path_in[lmnop].size() > 1) - { - Geom::Path p_init; - Geom::Path p_rev; - Geom::PathBuilder pb = Geom::PathBuilder(); - - if ( !path_in[lmnop].closed() ) - { - p_init = Outline::half_outline( path_in[lmnop], -line_width, butt, - miter_lim ); - p_rev = Outline::half_outline( path_in[lmnop].reverse(), -line_width, butt, - miter_lim ); - - pb.moveTo(p_init.initialPoint() ); - pb.append(p_init); - - //cap - if (butt == butt_straight) { - pb.lineTo(p_rev.initialPoint() ); - } else if (butt == butt_round) { - pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_rev.initialPoint() ); - } else if (butt == butt_square) { - //don't know what to do - pb.lineTo(p_rev.initialPoint() ); - } else if (butt == butt_pointy) { - //don't know what to do - pb.lineTo(p_rev.initialPoint() ); - } - - pb.append(p_rev); - - if (butt == butt_straight) { - pb.lineTo(p_init.initialPoint() ); - } else if (butt == butt_round) { - pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_init.initialPoint() ); - } else if (butt == butt_square) { - //don't know what to do - pb.lineTo(p_init.initialPoint() ); - } else if (butt == butt_pointy) { - //don't know what to do - //Geom::Point end_deriv = Geom::unitTangentAt( Geom::reverse(path_in[lmnop].toPwSb()[path_in[lmnop].size()]), 0); - //double radius = 0.5 * Geom::distance(path_in[lmnop].finalPoint(), p_rev.initialPoint()); - - pb.lineTo(p_init.initialPoint() ); - } - } - else - { - //final join - //refer to half_outline for documentation - Geom::Path p_almost = path_in[lmnop]; - p_almost.appendNew ( path_in[lmnop].initialPoint() ); - p_init = Outline::half_outline( p_almost, -line_width, butt, - miter_lim ); - p_rev = Outline::half_outline( p_almost.reverse(), -line_width, butt, - miter_lim ); - p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); - - //this is a kludge, because I can't find how to make this work properly - bool lastIsLinear = ( (Geom::distance(path_in[lmnop].finalPoint(), - path_in[lmnop] [path_in[lmnop].size() - 1].finalPoint())) == - (path_in[lmnop] [path_in[lmnop].size()].length())); - - p_almost = p_init; - if (lastIsLinear) - { - p_almost.erase_last(); p_almost.erase_last(); - } - - //outside test - Geom::Curve* cbc1 = p_almost[p_almost.size() - 1].duplicate(); - Geom::Curve* cbc2 = p_almost[0].duplicate(); - - Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); - - if (cross.empty()) - { - //this is the outside path - - //reuse the old one - p_init = p_almost; - Outline::reflect_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); - pb.moveTo(p_init.initialPoint()); pb.append(p_init); - } - else - { - //inside, carry on :-) - pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); - } - - p_almost = p_rev; - if (lastIsLinear) - { - p_almost.erase(p_almost.begin() ); - p_almost.erase(p_almost.begin() ); - } - - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - - cbc1 = p_almost[p_almost.size() - 1].duplicate(); - cbc2 = p_almost[0].duplicate(); - - cross = Geom::crossings(*cbc1, *cbc2); - - if (cross.empty()) - { - //outside path - - p_init = p_almost; - reflect_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); - pb.moveTo(p_init.initialPoint()); pb.append(p_init); - } - else - { - //inside - pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); - } - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - } - //pb.closePath(); - pb.flush(); - Geom::PathVector pv_np = pb.peek(); - //hack - for (unsigned abcd = 0; abcd < pv_np.size(); abcd++) - { - path_out.push_back( pv_np[abcd] ); - } - } - else - { - p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); - p.Outline(&outlinepath, line_width / 2, join, butt, miter_lim); - std::vector *pv_p = outlinepath.MakePathVector(); - //hack - path_out.push_back( (*pv_p)[0].reverse() ); - delete pv_p; - } - } - return path_out; - } - static Geom::PathVector outlinePath_extr(const Geom::PathVector& path_in, double line_width, LineJoinType join, ButtType butt, double miter_lim) - { - Path p = Path(); - Path outlinepath = Path(); - for (unsigned i = 0; i < path_in.size(); i++) - { - p.LoadPath(path_in[i], Geom::Affine(), false, ( (i==0) ? false : true)); - } - - Geom::PathVector path_out; - for (unsigned lmnop = 0; lmnop < path_in.size(); lmnop++) - { - if (path_in[lmnop].size() > 1) - { - Geom::Path p_init; - Geom::Path p_rev; - Geom::PathBuilder pb = Geom::PathBuilder(); - - if ( !path_in[lmnop].closed() ) - { - p_init = Outline::half_outline_extrp( path_in[lmnop], -line_width, butt, - miter_lim ); - p_rev = Outline::half_outline_extrp( path_in[lmnop].reverse(), -line_width, butt, - miter_lim ); - - pb.moveTo(p_init.initialPoint() ); - pb.append(p_init); - - //cap - if (butt == butt_straight) { - pb.lineTo(p_rev.initialPoint() ); - } else if (butt == butt_round) { - pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_rev.initialPoint() ); - } else if (butt == butt_square) { - //don't know what to do - pb.lineTo(p_rev.initialPoint() ); - } else if (butt == butt_pointy) { - //don't know what to do - pb.lineTo(p_rev.initialPoint() ); - } - - pb.append(p_rev); - - if (butt == butt_straight) { - pb.lineTo(p_init.initialPoint() ); - } else if (butt == butt_round) { - pb.arcTo((-line_width) / 2, (-line_width) / 2, 0., true, true, p_init.initialPoint() ); - } else if (butt == butt_square) { - //don't know what to do - pb.lineTo(p_init.initialPoint() ); - } else if (butt == butt_pointy) { - //don't know what to do - //Geom::Point end_deriv = Geom::unitTangentAt( Geom::reverse(path_in[lmnop].toPwSb()[path_in[lmnop].size()]), 0); - //double radius = 0.5 * Geom::distance(path_in[lmnop].finalPoint(), p_rev.initialPoint()); - - pb.lineTo(p_init.initialPoint() ); - } - } - else - { - //final join - //refer to half_outline for documentation - Geom::Path p_almost = path_in[lmnop]; - p_almost.appendNew ( path_in[lmnop].initialPoint() ); - p_init = Outline::half_outline_extrp( p_almost, -line_width, butt, - miter_lim ); - p_rev = Outline::half_outline_extrp( p_almost.reverse(), -line_width, butt, - miter_lim ); - p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); - - //this is a kludge, because I can't find how to make this work properly - bool lastIsLinear = ( (Geom::distance(path_in[lmnop].finalPoint(), - path_in[lmnop] [path_in[lmnop].size() - 1].finalPoint())) == - (path_in[lmnop] [path_in[lmnop].size()].length())); - - p_almost = p_init; - if (lastIsLinear) - { - p_almost.erase_last(); p_almost.erase_last(); - } - - //outside test - Geom::Curve* cbc1 = p_almost[p_almost.size() - 1].duplicate(); - Geom::Curve* cbc2 = p_almost[0].duplicate(); - - Geom::Crossings cross = Geom::crossings(*cbc1, *cbc2); - - if (cross.empty()) - { - //this is the outside path - - //reuse the old one - p_init = p_almost; - Outline::extrapolate_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); - pb.moveTo(p_init.initialPoint()); pb.append(p_init); - } - else - { - //inside, carry on :-) - pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); - } - - p_almost = p_rev; - if (lastIsLinear) - { - p_almost.erase(p_almost.begin() ); - p_almost.erase(p_almost.begin() ); - } - - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - - cbc1 = p_almost[p_almost.size() - 1].duplicate(); - cbc2 = p_almost[0].duplicate(); - - cross = Geom::crossings(*cbc1, *cbc2); - - if (cross.empty()) - { - //outside path - - p_init = p_almost; - extrapolate_curves(p_init, cbc1, cbc2, p_almost.initialPoint(), miter_lim ); - pb.moveTo(p_init.initialPoint()); pb.append(p_init); - } - else - { - //inside - pb.moveTo(p_almost.initialPoint()); pb.append(p_almost); - } - delete cbc1; delete cbc2; cbc1 = cbc2 = NULL; - } - //pb.closePath(); - pb.flush(); - Geom::PathVector pv_np = pb.peek(); - //hack - for (unsigned abcd = 0; abcd < pv_np.size(); abcd++) - { - path_out.push_back( pv_np[abcd] ); - } - } - else - { - p.LoadPath(path_in[lmnop], Geom::Affine(), false, false); - p.Outline(&outlinepath, line_width / 2, join_pointy, butt, miter_lim); - std::vector *pv_p = outlinepath.MakePathVector(); - //hack - path_out.push_back( (*pv_p)[0].reverse() ); - delete pv_p; - } - } - return path_out; - } - - -} // namespace Outline - -/* - Local Variables: - mode:c++ - c-file-style:"stroustrup" - c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) - indent-tabs-mode:nil - fill-column:99 - End: -*/ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +#ifndef _SEEN_PATH_OUTLINE_H +#define _SEEN_PATH_OUTLINE_H + +#include +#include + +enum LineJoinType { + LINEJOIN_STRAIGHT, + LINEJOIN_ROUND, + LINEJOIN_POINTY, + LINEJOIN_REFLECTED, + LINEJOIN_EXTRAPOLATED +}; + +namespace Outline +{ + unsigned bezierOrder (const Geom::Curve* curve_in); + std::vector PathVectorOutline(std::vector const & path_in, double line_width, ButtType linecap_type, + join_typ linejoin_type, double miter_limit); + + Geom::PathVector outlinePath(const Geom::PathVector& path_in, double line_width, JoinType join, ButtType butt, double miter_lim); + Geom::PathVector outlinePath_extr(const Geom::PathVector& path_in, double line_width, LineJoinType join, ButtType butt, double miter_lim); +} + +#endif // _SEEN_PATH_OUTLINE_H diff --git a/src/ui/dialog/objects.cpp b/src/ui/dialog/objects.cpp index 338233042..85583a0e7 100644 --- a/src/ui/dialog/objects.cpp +++ b/src/ui/dialog/objects.cpp @@ -1887,8 +1887,9 @@ ObjectsPanel::ObjectsPanel() : btn = Gtk::manage( new Gtk::Button() ); btn->set_tooltip_text(_("Collapse All")); #if GTK_CHECK_VERSION(3,10,0) - btn->set_from_icon_name(INKSCAPE_ICON("gtk-unindent-ltr"), Gtk::ICON_SIZE_SMALL_TOOLBAR); + btn->set_image_from_icon_name(INKSCAPE_ICON("gtk-unindent-ltr"), Gtk::ICON_SIZE_SMALL_TOOLBAR); #else + image_remove = Gtk::manage(new Gtk::Image()); image_remove->set_from_icon_name(INKSCAPE_ICON("gtk-unindent-ltr"), Gtk::ICON_SIZE_SMALL_TOOLBAR); btn->set_image(*image_remove); #endif -- cgit v1.2.3