#define INKSCAPE_LPE_POWERSTROKE_CPP /** \file * @brief PowerStroke LPE implementation. Creates curves with modifiable stroke width. */ /* Authors: * Johan Engelen * * Copyright (C) 2010 Authors * * Released under GNU GPL, read the file 'COPYING' for more information */ #include "live_effects/lpe-powerstroke.h" #include "sp-shape.h" #include "display/curve.h" #include <2geom/path.h> #include <2geom/piecewise.h> #include <2geom/sbasis-geometric.h> #include <2geom/transforms.h> #include <2geom/bezier-utils.h> #include "live_effects/bezctx.h" #include "live_effects/bezctx_intf.h" #include "live_effects/spiro.h" /// @TODO Move this to 2geom namespace Geom { namespace Interpolate { enum InterpolatorType { INTERP_LINEAR, INTERP_CUBICBEZIER, INTERP_CUBICBEZIER_JOHAN, INTERP_SPIRO }; class Interpolator { public: Interpolator() {}; virtual ~Interpolator() {}; static Interpolator* create(InterpolatorType type); // virtual Piecewise > interpolateToPwD2Sb(std::vector points) = 0; virtual Geom::Path interpolateToPath(std::vector points) = 0; private: Interpolator(const Interpolator&); Interpolator& operator=(const Interpolator&); }; class Linear : public Interpolator { public: Linear() {}; virtual ~Linear() {}; virtual Path interpolateToPath(std::vector points) { Path path; path.start( points.at(0) ); for (unsigned int i = 1 ; i < points.size(); ++i) { path.appendNew(points.at(i)); } return path; }; private: Linear(const Linear&); Linear& operator=(const Linear&); }; // this class is terrible class CubicBezierFit : public Interpolator { public: CubicBezierFit() {}; virtual ~CubicBezierFit() {}; virtual Path interpolateToPath(std::vector points) { unsigned int n_points = points.size(); // worst case gives us 2 segment per point int max_segs = 8*n_points; Geom::Point * b = g_new(Geom::Point, max_segs); Geom::Point * points_array = g_new(Geom::Point, 4*n_points); for (unsigned i = 0; i < n_points; ++i) { points_array[i] = points.at(i); } double tolerance_sq = 0; // this value is just a random guess int const n_segs = Geom::bezier_fit_cubic_r(b, points_array, n_points, tolerance_sq, max_segs); Geom::Path fit; if ( n_segs > 0) { fit.start(b[0]); for (int c = 0; c < n_segs; c++) { fit.appendNew(b[4*c+1], b[4*c+2], b[4*c+3]); } } g_free(b); g_free(points_array); return fit; }; private: CubicBezierFit(const CubicBezierFit&); CubicBezierFit& operator=(const CubicBezierFit&); }; /// @todo invent name for this class class CubicBezierJohan : public Interpolator { public: CubicBezierJohan() {}; virtual ~CubicBezierJohan() {}; virtual Path interpolateToPath(std::vector points) { Path fit; fit.start(points.at(0)); for (unsigned int i = 1; i < points.size(); ++i) { Point p0 = points.at(i-1); Point p1 = points.at(i); Point dx = Point(p1[X] - p0[X], 0); fit.appendNew(p0+0.2*dx, p1-0.2*dx, p1); } return fit; }; private: CubicBezierJohan(const CubicBezierJohan&); CubicBezierJohan& operator=(const CubicBezierJohan&); }; #define SPIRO_SHOW_INFINITE_COORDINATE_CALLS class SpiroInterpolator : public Interpolator { public: SpiroInterpolator() {}; virtual ~SpiroInterpolator() {}; virtual Path interpolateToPath(std::vector points) { Path fit; Coord scale_y = 100.; guint len = points.size(); bezctx *bc = new_bezctx_ink(&fit); spiro_cp *controlpoints = g_new (spiro_cp, len); for (unsigned int i = 0; i < len; ++i) { controlpoints[i].x = points[i][X]; controlpoints[i].y = points[i][Y] / scale_y; controlpoints[i].ty = 'c'; } controlpoints[0].ty = '{'; controlpoints[1].ty = 'v'; controlpoints[len-2].ty = 'v'; controlpoints[len-1].ty = '}'; spiro_seg *s = run_spiro(controlpoints, len); spiro_to_bpath(s, len, bc); free(s); free(bc); fit *= Scale(1,scale_y); return fit; }; private: typedef struct { bezctx base; Path *path; int is_open; } bezctx_ink; static void bezctx_ink_moveto(bezctx *bc, double x, double y, int /*is_open*/) { bezctx_ink *bi = (bezctx_ink *) bc; if ( IS_FINITE(x) && IS_FINITE(y) ) { bi->path->start(Point(x, y)); } #ifdef SPIRO_SHOW_INFINITE_COORDINATE_CALLS else { g_message("spiro moveto not finite"); } #endif } static void bezctx_ink_lineto(bezctx *bc, double x, double y) { bezctx_ink *bi = (bezctx_ink *) bc; if ( IS_FINITE(x) && IS_FINITE(y) ) { bi->path->appendNew( Point(x, y) ); } #ifdef SPIRO_SHOW_INFINITE_COORDINATE_CALLS else { g_message("spiro lineto not finite"); } #endif } static void bezctx_ink_quadto(bezctx *bc, double xm, double ym, double x3, double y3) { bezctx_ink *bi = (bezctx_ink *) bc; if ( IS_FINITE(xm) && IS_FINITE(ym) && IS_FINITE(x3) && IS_FINITE(y3) ) { bi->path->appendNew(Point(xm, ym), Point(x3, y3)); } #ifdef SPIRO_SHOW_INFINITE_COORDINATE_CALLS else { g_message("spiro quadto not finite"); } #endif } static void bezctx_ink_curveto(bezctx *bc, double x1, double y1, double x2, double y2, double x3, double y3) { bezctx_ink *bi = (bezctx_ink *) bc; if ( IS_FINITE(x1) && IS_FINITE(y1) && IS_FINITE(x2) && IS_FINITE(y2) ) { bi->path->appendNew(Point(x1, y1), Point(x2, y2), Point(x3, y3)); } #ifdef SPIRO_SHOW_INFINITE_COORDINATE_CALLS else { g_message("spiro curveto not finite"); } #endif } bezctx * new_bezctx_ink(Geom::Path *path) { bezctx_ink *result = g_new(bezctx_ink, 1); result->base.moveto = bezctx_ink_moveto; result->base.lineto = bezctx_ink_lineto; result->base.quadto = bezctx_ink_quadto; result->base.curveto = bezctx_ink_curveto; result->base.mark_knot = NULL; result->path = path; return &result->base; } SpiroInterpolator(const SpiroInterpolator&); SpiroInterpolator& operator=(const SpiroInterpolator&); }; Interpolator* Interpolator::create(InterpolatorType type) { switch (type) { case INTERP_LINEAR: return new Geom::Interpolate::Linear(); case INTERP_CUBICBEZIER: return new Geom::Interpolate::CubicBezierFit(); case INTERP_CUBICBEZIER_JOHAN: return new Geom::Interpolate::CubicBezierJohan(); case INTERP_SPIRO: return new Geom::Interpolate::SpiroInterpolator(); default: return new Geom::Interpolate::Linear(); } } } //namespace Interpolate } //namespace Geom namespace Inkscape { namespace LivePathEffect { static const Util::EnumData InterpolatorTypeData[] = { {Geom::Interpolate::INTERP_LINEAR , N_("Linear"), "Linear"}, {Geom::Interpolate::INTERP_CUBICBEZIER , N_("CubicBezierFit"), "CubicBezierFit"}, {Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN , N_("CubicBezierJohan"), "CubicBezierJohan"}, {Geom::Interpolate::INTERP_SPIRO , N_("SpiroInterpolator"), "SpiroInterpolator"} }; static const Util::EnumDataConverter InterpolatorTypeConverter(InterpolatorTypeData, sizeof(InterpolatorTypeData)/sizeof(*InterpolatorTypeData)); LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) : Effect(lpeobject), offset_points(_("Offset points"), _("Offset points"), "offset_points", &wr, this), sort_points(_("Sort points"), _("Sort offset points according to their time value along the curve."), "sort_points", &wr, this, true), interpolator_type(_("Interpolator type"), _("Determines which kind of interpolator will be used to interpolate between stroke width along the path."), "interpolator_type", InterpolatorTypeConverter, &wr, this, Geom::Interpolate::INTERP_CUBICBEZIER_JOHAN) { show_orig_path = true; /// @todo offset_points are initialized with empty path, is that bug-save? registerParameter( dynamic_cast(&offset_points) ); registerParameter( dynamic_cast(&sort_points) ); registerParameter( dynamic_cast(&interpolator_type) ); } LPEPowerStroke::~LPEPowerStroke() { } void LPEPowerStroke::doOnApply(SPLPEItem *lpeitem) { std::vector points; Geom::Path::size_type size = SP_SHAPE(lpeitem)->curve->get_pathvector().front().size_open(); points.push_back( Geom::Point(0,0) ); points.push_back( Geom::Point(0.5*size,0) ); points.push_back( Geom::Point(size,0) ); offset_points.param_set_and_write_new_value(points); } static bool compare_offsets (Geom::Point first, Geom::Point second) { return first[Geom::X] < second[Geom::X]; } Geom::Piecewise > LPEPowerStroke::doEffect_pwd2 (Geom::Piecewise > const & pwd2_in) { using namespace Geom; offset_points.set_pwd2(pwd2_in); Piecewise > der = unitVector(derivative(pwd2_in)); Piecewise > n = rot90(der); offset_points.set_pwd2_normal(n); // see if we should treat the path as being closed. bool closed_path = false; if ( are_near(pwd2_in.firstValue(), pwd2_in.lastValue()) ) { closed_path = true; } Piecewise > output; if (!closed_path) { // perhaps use std::list instead of std::vector? std::vector ts(offset_points.data().size() + 2); // first and last point coincide with input path (for now at least) ts.front() = Point(pwd2_in.domain().min(),0); ts.back() = Point(pwd2_in.domain().max(),0); for (unsigned int i = 0; i < offset_points.data().size(); ++i) { ts.at(i+1) = offset_points.data().at(i); } if (sort_points) { sort(ts.begin(), ts.end(), compare_offsets); } // create stroke path where points (x,y) := (t, offset) Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(static_cast(interpolator_type.get_value())); Geom::Path strokepath = interpolator->interpolateToPath(ts); Geom::Path mirroredpath = strokepath.reverse() * Geom::Scale(1,-1); delete interpolator; strokepath.append(mirroredpath, Geom::Path::STITCH_DISCONTINUOUS); strokepath.close(); D2 > patternd2 = make_cuts_independent(strokepath.toPwSb()); Piecewise x = Piecewise(patternd2[0]); Piecewise y = Piecewise(patternd2[1]); output = compose(pwd2_in,x) + y*compose(n,x); } else { // path is closed // perhaps use std::list instead of std::vector? std::vector 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) ); // create stroke path where points (x,y) := (t, offset) Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(static_cast(interpolator_type.get_value())); Geom::Path strokepath = interpolator->interpolateToPath(ts); delete interpolator; // output 2 separate paths D2 > patternd2 = make_cuts_independent(strokepath.toPwSb()); Piecewise x = Piecewise(patternd2[0]); Piecewise y = Piecewise(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)); } 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; } /* ######################## */ } //namespace LivePathEffect } /* namespace Inkscape */ /* 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:fileencoding=utf-8:textwidth=99 :