lpe-taperstroke.cpp revision a9f579cc260027d89b33d51735963a98cc77d5e9
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye * Taper Stroke path effect, provided as an alternative to Power Strokes
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye * for otherwise constant-width paths.
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye * Liam P White <inkscapebrony@gmail.com>
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye * Copyright (C) 2014 Authors
c0550b01024b910b8c1468811c0ea663b10b1372Trond Norbye * Released under GNU GPL, read the file 'COPYING' for more information
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlentemplate<typename T>
14a41f02433890d19b2f871156271e3388cd0845Jens Elknerinline bool withinRange(T value, T low, T high) {
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen class KnotHolderEntityAttachBegin : public LPEKnotHolderEntity {
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner KnotHolderEntityAttachBegin(LPETaperStroke * effect) : LPEKnotHolderEntity(effect) {}
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner class KnotHolderEntityAttachEnd : public LPEKnotHolderEntity {
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco KnotHolderEntityAttachEnd(LPETaperStroke * effect) : LPEKnotHolderEntity(effect) {}
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco virtual void knot_set(Geom::Point const &p, Geom::Point const &origin, guint state);
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Koscostatic const Util::EnumData<unsigned> JoinType[] = {
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco {JOIN_EXTRAPOLATE, N_("Extrapolated"), "extrapolated"},
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Koscostatic const Util::EnumDataConverter<unsigned> JoinTypeConverter(JoinType, sizeof (JoinType)/sizeof(*JoinType));
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos KoscoLPETaperStroke::LPETaperStroke(LivePathEffectObject *lpeobject) :
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco line_width(_("Stroke width:"), _("The (non-tapered) width of the path"), "stroke_width", &wr, this, 1.),
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco attach_start(_("Start offset:"), _("Taper distance from path start"), "attach_start", &wr, this, 0.2),
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco attach_end(_("End offset:"), _("The ending position of the taper"), "end_offset", &wr, this, 0.2),
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco smoothing(_("Taper smoothing:"), _("Amount of smoothing to apply to the tapers"), "smoothing", &wr, this, 0.5),
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco join_type(_("Join type:"), _("Join type for non-smooth nodes"), "jointype", JoinTypeConverter, &wr, this, JOIN_EXTRAPOLATE),
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco miter_limit(_("Miter limit:"), _("Limit for miter joins"), "miter_limit", &wr, this, 100.)
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner// from LPEPowerStroke -- sets fill if stroke color because we will
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen// be converting to a fill to make the new join.
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlenvoid LPETaperStroke::doOnApply(SPLPEItem const* lpeitem)
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner SPLPEItem* item = const_cast<SPLPEItem*>(lpeitem);
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner double width = (lpeitem && lpeitem->style) ? lpeitem->style->stroke_width.computed : 1.;
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen SPCSSAttr *css = sp_repr_css_attr_new ();
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen if (lpeitem->style->stroke.isPaintserver()) {
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen SPPaintServer * server = lpeitem->style->getStrokePaintServer();
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen sp_repr_css_set_property (css, "fill", str.c_str());
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen } else if (lpeitem->style->stroke.isColor()) {
9c4ded641ae76132f262728f5d64e30fb004ae47Lubos Kosco sp_svg_write_color (c, sizeof(c), lpeitem->style->stroke.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->stroke_opacity.value)));
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco sp_repr_css_set_property(css, "fill-rule", "nonzero");
9c4ded641ae76132f262728f5d64e30fb004ae47Lubos Kosco sp_repr_css_set_property(css, "stroke", "none");
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen sp_desktop_apply_css_recursive(item, css, true);
99b4056e2c5b0a51f7f480ebcefb1f917613ce2aLubos Kosco printf("WARNING: It only makes sense to apply Taper stroke to paths (not groups).\n");
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen// from LPEPowerStroke -- sets stroke color from existing fill color
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlenvoid LPETaperStroke::doOnRemove(SPLPEItem const* lpeitem)
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen SPLPEItem *item = const_cast<SPLPEItem*>(lpeitem);
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen SPCSSAttr *css = sp_repr_css_attr_new ();
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen if (lpeitem->style->fill.isPaintserver()) {
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner SPPaintServer * server = lpeitem->style->getFillPaintServer();
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen sp_repr_css_set_property (css, "stroke", str.c_str());
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen } else if (lpeitem->style->fill.isColor()) {
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner sp_svg_write_color (c, sizeof(c), lpeitem->style->fill.value.color.toRGBA32(SP_SCALE24_TO_FLOAT(lpeitem->style->fill_opacity.value)));
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner sp_repr_css_set_property (css, "stroke", "none");
e655c7911217c7948ee6d9eb73bff3a712b0fa70Lubos Kosco sp_repr_css_set_property (css, "stroke-width", os.str().c_str());
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen sp_repr_css_set_property(css, "fill", "none");
59b6a8c0cc6ef741a7180504b3c371e67c2aa338Knut Anders Hatlen sp_desktop_apply_css_recursive(item, css, true);
99b4056e2c5b0a51f7f480ebcefb1f917613ce2aLubos Kosco// leave Geom::Path
14a41f02433890d19b2f871156271e3388cd0845Jens Elknerstatic Geom::Path return_at_first_cusp(Geom::Path const & path_in, double /*smooth_tolerance*/ = 0.05)
9a4361e23046cda58b9a5b8f4e11910dc433badaLubos Kosco if (Geom::get_nodetype(path_in[i], path_in[i + 1]) != Geom::NODE_SMOOTH ) {
14a41f02433890d19b2f871156271e3388cd0845Jens ElknerPiecewise<D2<SBasis> > stretch_along(Piecewise<D2<SBasis> > pwd2_in, Geom::Path pattern, double width);
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner// actual effect
60281ca3d1db22f5d2204e1be4975504853072a7Lubos KoscoGeom::PathVector LPETaperStroke::doEffect_path(Geom::PathVector const& path_in)
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner Geom::Path first_cusp = return_at_first_cusp(path_in[0]);
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner Geom::Path last_cusp = return_at_first_cusp(path_in[0].reverse());
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner bool zeroStart = false; // [distance from start taper knot -> start of path] == 0
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner bool zeroEnd = false; // [distance from end taper knot -> end of path] == 0
14a41f02433890d19b2f871156271e3388cd0845Jens Elkner // there is a pretty good chance that people will try to drag the knots
metInMiddle = true;
metInMiddle = true;
metInMiddle = true;
zeroStart = true;
zeroEnd = true;
// now for the actual tapering. the stretch_along method (stolen from PaP) is used to accomplish this
if (!zeroStart) {
pat_str << "M 1,0 C " << 1 - (double)smoothing << ",0 0,0.5 0,0.5 0,0.5 " << 1 - (double)smoothing << ",1 1,1";
// if this condition happens to evaluate false, i.e. there was no space for a path to be drawn, it is simply skipped.
if (!metInMiddle) {
throwaway_path = half_outline(pathv_out[1], fabs(line_width)/2., miter_limit, static_cast<LineJoinType>(join_type.get_value()));
if (!zeroEnd) {
pat_str_1 << "M 0,1 C " << (double)smoothing << ",1 1,0.5 1,0.5 1,0.5 " << double(smoothing) << ",0 0,0";
if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint()) && real_path.size() >= 1) {
if (!metInMiddle) {
throwaway_path = half_outline(pathv_out[1].reverse(), fabs(line_width)/2., miter_limit, static_cast<LineJoinType>(join_type.get_value()));
if (!Geom::are_near(real_path.finalPoint(), throwaway_path.initialPoint()) && real_path.size() >= 1) {
return real_pathv;
return out;
Piecewise<D2<SBasis> > stretch_along(Piecewise<D2<SBasis> > pwd2_in, Geom::Path pattern, double prop_scale)
using namespace Geom;
return pwd2_in;
double xspace = 0;
double noffset = 0;
double toffset = 0;
int nbCopies = 0;
x*=scaling;
x += toffset;
double offs = 0;
for (int i=0; i<nbCopies; i++) {
return output;
return pwd2_in;
void LPETaperStroke::addKnotHolderEntities(KnotHolder *knotholder, SPDesktop *desktop, SPItem *item)
e->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _("Start point of the taper"), SP_KNOT_SHAPE_CIRCLE);
f->create(desktop, item, knotholder, Inkscape::CTRL_TYPE_UNKNOWN, _("End point of the taper"), SP_KNOT_SHAPE_CIRCLE);
namespace TpS {
void KnotHolderEntityAttachBegin::knot_set(Geom::Point const &p, Geom::Point const&/*origin*/, guint state)
using namespace Geom;
// FIXME: this should not directly ask for updating the item. It should write to SVG, which triggers updating.
void KnotHolderEntityAttachEnd::knot_set(Geom::Point const &p, Geom::Point const& /*origin*/, guint state)
using namespace Geom;