From 3d059a10fd17411f73e88ccde2cd853642ee7dc3 Mon Sep 17 00:00:00 2001 From: Jabier Arraiza Date: Mon, 18 Dec 2017 01:11:36 +0100 Subject: Working on powerstroke fixes to pentool --- src/live_effects/lpe-powerstroke.cpp | 185 +++---------------- src/live_effects/lpe-powerstroke.h | 2 - src/ui/dialog/inkscape-preferences.cpp | 10 -- src/ui/dialog/inkscape-preferences.h | 1 - src/ui/tools/freehand-base.cpp | 4 - src/ui/tools/pencil-tool.cpp | 313 +++++++++++++-------------------- src/ui/tools/pencil-tool.h | 11 +- 7 files changed, 147 insertions(+), 379 deletions(-) (limited to 'src') diff --git a/src/live_effects/lpe-powerstroke.cpp b/src/live_effects/lpe-powerstroke.cpp index f4e4a06ca..e7ceaa397 100644 --- a/src/live_effects/lpe-powerstroke.cpp +++ b/src/live_effects/lpe-powerstroke.cpp @@ -97,10 +97,13 @@ static Circle touching_circle( D2 const &curve, double t, double tol=0.0 if ( are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) ) { // try second time dM=derivative(dM); } - if ( are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) ) { // admit defeat + if ( dM.isZero(tol) || (are_near(L2sq(dM(t)),0.) && (dM[0].size() > 1) && (dM[1].size() > 1) )) { // admit defeat return Geom::Circle(Geom::Point(0., 0.), 0.); } Piecewise > unitv = unitVector(dM,tol); + if (unitv.empty()) { // admit defeat + return Geom::Circle(Geom::Point(0., 0.), 0.); + } Piecewise dMlength = dot(Piecewise >(dM),unitv); Piecewise k = cross(derivative(unitv),unitv); k = divide(k,dMlength,tol,3); @@ -165,7 +168,6 @@ 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), - interpolate_original(_("Interpolate original"), _("Interpolate original path"), "interpolate_original", &wr, this, false), 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), interpolator_beta(_("Smoothness:"), _("Sets the smoothness for the CubicBezierJohan interpolator; 0 = linear interpolation, 1 = smooth"), "interpolator_beta", &wr, this, 0.2), scale_width(_("Width scale:"), _("Width scale all points"), "scale_width", &wr, this, 1.0), @@ -183,7 +185,6 @@ LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) : registerParameter(&offset_points); registerParameter(&sort_points); - registerParameter(&interpolate_original); registerParameter(&interpolator_type); registerParameter(&interpolator_beta); registerParameter(&start_linecap_type); @@ -194,7 +195,6 @@ LPEPowerStroke::LPEPowerStroke(LivePathEffectObject *lpeobject) : scale_width.param_set_range(0.0, Geom::infinity()); scale_width.param_set_increments(0.1, 0.1); scale_width.param_set_digits(4); - interpolate_original_prev = !interpolate_original; } LPEPowerStroke::~LPEPowerStroke() @@ -566,57 +566,16 @@ LPEPowerStroke::doEffect_path (Geom::PathVector const & path_in) Geom::PathVector path_out; if (path_in.empty()) { - return path_in; + return path_out; } Geom::PathVector pathv = pathv_to_linear_and_cubic_beziers(path_in); - // create stroke path where points (x,y) := (t, offset) - - if (interpolate_original) { - Geom::PathVector path_out; - for(Geom::PathVector::const_iterator path_it = path_in.begin(); path_it != path_in.end(); ++path_it) { - if (path_it->empty()) - continue; - - if (path_it->closed()) { - g_warning("Interpolate points LPE currently ignores whether path is closed or not."); - } - std::vector pts; - pts.push_back(path_it->initialPoint()); - - for (Geom::Path::const_iterator it = path_it->begin(), e = path_it->end_default(); it != e; ++it) { - pts.push_back((*it).finalPoint()); - } - //We use this fixed interpolator to simplfy the UI and for better results - Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM); - Geom::Path path = interpolator->interpolateToPath(pts); - - path_out.push_back(path); - } - pathv = path_out; - } - Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(static_cast(interpolator_type.get_value())); - if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast(interpolator)) { - johan->setBeta(interpolator_beta); - } - if (Geom::Interpolate::CubicBezierSmooth *smooth = dynamic_cast(interpolator)) { - smooth->setBeta(interpolator_beta); - } - if (interpolate_original_prev != interpolate_original) { - adjustForNewPath(pathv); - interpolate_original_prev = interpolate_original; - } Geom::Piecewise > pwd2_in = pathv[0].toPwSb(); - if (!pwd2_in.size()) { - return path_in; - } Piecewise > der = derivative(pwd2_in); - if (!der.size()) { - return path_in; - } Piecewise > n = unitVector(der,0.0001); - if (!n.size()) { + if (!n.size() || !pwd2_in.size() || !n.size()) { return path_in; } + n = rot90(n); offset_points.set_pwd2(pwd2_in, n); @@ -654,116 +613,18 @@ LPEPowerStroke::doEffect_path (Geom::PathVector const & path_in) // instead of the heavily compressed coordinate system of (segment_no offset, Y) in which the knots are stored double pwd2_in_arclength = length(pwd2_in); double xcoord_scaling = pwd2_in_arclength / ts.back()[Geom::X]; - if (interpolate_original) { - size_t i = 0; - std::vector ts_aprox; - size_t steps = 0; - double distance = 0; - Geom::PathVector splits; - Geom::Coord start = 0; - for(std::vector::iterator point = ts.begin(); point != ts.end();) { - point++; - Geom::Coord end = (*point)[Geom::X]; - if (Geom::are_near(start, end,0.0001)) { - continue; - } - splits.push_back(path_in[0].portion(start, end)); - start = end; - if (end == pathv[0].size()) { - break; - } - } - size_t counter = 0; - for(Geom::PathVector::const_iterator path_it = splits.begin(); path_it != splits.end(); ++path_it) { - if (path_it->empty()) { - continue; - } - Geom::Piecewise > path_pwd = (*path_it).toPwSb(); - size_t size = (*path_it).size(); - double path_it_arclength = length(path_pwd); - Geom::Point start = ts[counter]; - counter++; - Geom::Point end = ts[counter]; - if (Geom::are_near(start[Geom::Y],end[Geom::Y],0.0001)) { - continue; - } - double gap = (start[Geom::Y] - end[Geom::Y])/size; - double width = 0; - width = start[Geom::Y]; - for (size_t j = 1; j < size; j++){ - Geom::Path current_curve = (*path_it).portion(j-1, j); - double path_it_arclength_sub = length(current_curve.toPwSb()); - double factor = path_it_arclength_sub * size/path_it_arclength; - width -= gap * factor; - ts.push_back(Geom::Point(std::floor(ts[counter-1][Geom::X]) + j, width)); - } - } - sort(ts.begin(), ts.end(), compare_offsets); + for (std::size_t i = 0, e = ts.size(); i < e; ++i) { + ts[i][Geom::X] *= xcoord_scaling; } // create stroke path where points (x,y) := (t, offset) - - Geom::Path fixed_path; - Geom::Path fixed_mirrorpath; - Geom::Path strokepath; - if (interpolate_original && 2==1) { - std::vector ts_normal; - std::vector ts_mirror; - - bool previous_isnode = false; - size_t counter = 0; - for(auto point:ts) { - Geom::Point normal_pos = pwd2_in.valueAt(point[Geom::X]) + (point[Geom::Y] * scale_width) * n.valueAt(point[Geom::X]); - Geom::Point mirror_pos = pwd2_in.valueAt(point[Geom::X]) + (point[Geom::Y] * -1 * scale_width) * n.valueAt(point[Geom::X]); - Geom::Point normal = Geom::Point(normal_pos[Geom::X] * xcoord_scaling, normal_pos[Geom::Y]); - Geom::Point mirror = Geom::Point(mirror_pos [Geom::X] * xcoord_scaling, mirror_pos [Geom::Y]); - //a bit smoothig tweak - if (counter > 2) { - Geom::Point granparent_normal = ts_normal[counter-2]; - Geom::Point parent_normal = ts_normal[counter-1]; - Geom::Point granparent_mirror = ts_mirror [counter-2]; - Geom::Point parent_mirror = ts_mirror [counter-1]; - bool isnode = ts[counter][Geom::X] == std::floor(ts[counter ][Geom::X]); - bool previous_isnode = ts[counter-1][Geom::X] == std::floor(ts[counter-1][Geom::X]); - bool ccw_toggle_normal = cross(parent_normal - granparent_normal, normal - granparent_normal) < 0; - bool ccw_toggle_mirror = cross(parent_mirror - granparent_mirror , mirror - granparent_mirror ) < 0; - Geom::Ray ray_normal_a(parent_normal, granparent_normal); - Geom::Ray ray_normal_b(parent_normal , normal); - Geom::Ray ray_mirror_a (parent_mirror, granparent_mirror); - Geom::Ray ray_mirror_b (parent_mirror , mirror); - double angle_normal = angle_between(ray_normal_a, ray_normal_b, ccw_toggle_normal); - double angle_mirror = angle_between(ray_mirror_a , ray_mirror_b , ccw_toggle_mirror); - if (point[Geom::X] > 2 && - previous_isnode && - !isnode && - !ccw_toggle_normal && - angle_normal < Geom::rad_from_deg(90)) - { - ts_normal.pop_back(); - } - if (point[Geom::X] > 2 && - previous_isnode && - !isnode && - ccw_toggle_mirror && - angle_mirror < Geom::rad_from_deg(90)) - { - ts_mirror.pop_back(); - } - } - ts_normal.push_back(normal); - ts_mirror.push_back(mirror); - counter++; - } - fixed_path = interpolator->interpolateToPath(ts_normal); - fixed_path *= Scale(1/xcoord_scaling, 1); - fixed_mirrorpath = interpolator->interpolateToPath(ts_mirror); - fixed_mirrorpath *= Scale(1/xcoord_scaling, 1); - fixed_mirrorpath = fixed_mirrorpath.reversed(); - } else { - for (std::size_t i = 0, e = ts.size(); i < e; ++i) { - ts[i][Geom::X] *= xcoord_scaling; - } - strokepath = interpolator->interpolateToPath(ts); + Geom::Interpolate::Interpolator *interpolator = Geom::Interpolate::Interpolator::create(static_cast(interpolator_type.get_value())); + if (Geom::Interpolate::CubicBezierJohan *johan = dynamic_cast(interpolator)) { + johan->setBeta(interpolator_beta); } + if (Geom::Interpolate::CubicBezierSmooth *smooth = dynamic_cast(interpolator)) { + smooth->setBeta(interpolator_beta); + } + Geom::Path strokepath = interpolator->interpolateToPath(ts); delete interpolator; // apply the inverse knot-xcoord scaling that was applied before the interpolation @@ -781,13 +642,13 @@ LPEPowerStroke::doEffect_path (Geom::PathVector const & path_in) y = portion(y, rtsmin.at(0), rtsmax.at(0)); } - if (!interpolate_original || 2>1) { - LineJoinType jointype = static_cast(linejoin_type.get_value()); - Piecewise > pwd2_out = compose(pwd2_in,x) + y*compose(n,x); - Piecewise > mirrorpath = reverse( compose(pwd2_in,x) - y*compose(n,x)); - fixed_path = path_from_piecewise_fix_cusps( pwd2_out, y, jointype, miter_limit, LPE_CONVERSION_TOLERANCE); - fixed_mirrorpath = path_from_piecewise_fix_cusps( mirrorpath, reverse(y), jointype, miter_limit, LPE_CONVERSION_TOLERANCE); - } + LineJoinType jointype = static_cast(linejoin_type.get_value()); + + Piecewise > pwd2_out = compose(pwd2_in,x) + y*compose(n,x); + Piecewise > mirrorpath = reverse( compose(pwd2_in,x) - y*compose(n,x)); + + Geom::Path fixed_path = path_from_piecewise_fix_cusps( pwd2_out, y, jointype, miter_limit, LPE_CONVERSION_TOLERANCE); + Geom::Path fixed_mirrorpath = path_from_piecewise_fix_cusps( mirrorpath, reverse(y), jointype, miter_limit, LPE_CONVERSION_TOLERANCE); if (pathv[0].closed()) { fixed_path.close(true); path_out.push_back(fixed_path); diff --git a/src/live_effects/lpe-powerstroke.h b/src/live_effects/lpe-powerstroke.h index 53aab1b05..19696ac57 100644 --- a/src/live_effects/lpe-powerstroke.h +++ b/src/live_effects/lpe-powerstroke.h @@ -38,7 +38,6 @@ public: private: BoolParam sort_points; - BoolParam interpolate_original; EnumParam interpolator_type; ScalarParam interpolator_beta; ScalarParam scale_width; @@ -46,7 +45,6 @@ private: EnumParam linejoin_type; ScalarParam miter_limit; EnumParam end_linecap_type; - bool interpolate_original_prev; LPEPowerStroke(const LPEPowerStroke&); LPEPowerStroke& operator=(const LPEPowerStroke&); }; diff --git a/src/ui/dialog/inkscape-preferences.cpp b/src/ui/dialog/inkscape-preferences.cpp index d75b22ddb..eb4a6567e 100644 --- a/src/ui/dialog/inkscape-preferences.cpp +++ b/src/ui/dialog/inkscape-preferences.cpp @@ -213,15 +213,6 @@ void InkscapePreferences::AddPencilPowerStrokePressureStep(DialogPage &p, Glib:: false ); } -void InkscapePreferences::AddPencilPowerStrokeMinSimplify(DialogPage &p, Glib::ustring const &prefs_path, gint def_value) -{ - PrefSpinButton* sb = Gtk::manage( new PrefSpinButton); - sb->init ( prefs_path + "/powerstrokemintolerance", 1, 100, 1, 10, def_value, true, false); - p.add_line( false, _("Power Stroke base min simplify:"), *sb, _("%"), - _("Base simplify for pencil with pressure"), - false ); -} - static void StyleFromSelectionToTool(Glib::ustring const &prefs_path, StyleSwatch *swatch) { SPDesktop *desktop = SP_ACTIVE_DESKTOP; @@ -442,7 +433,6 @@ void InkscapePreferences::initPageTools() this->AddBaseSimplifySpinbutton(_page_pencil, "/tools/freehand/pencil", 25.0); _page_pencil.add_group_header( _("Pressure sensitivity settings")); this->AddPencilPowerStrokePressureStep(_page_pencil, "/tools/freehand/pencil", 10); - this->AddPencilPowerStrokeMinSimplify(_page_pencil, "/tools/freehand/pencil", 40); _page_pencil.add_group_header( _("Sketch mode")); _page_pencil.add_line( true, "", _pencil_average_all_sketches, "", diff --git a/src/ui/dialog/inkscape-preferences.h b/src/ui/dialog/inkscape-preferences.h index f7c5cc971..e0bd7f257 100644 --- a/src/ui/dialog/inkscape-preferences.h +++ b/src/ui/dialog/inkscape-preferences.h @@ -502,7 +502,6 @@ protected: static void AddDotSizeSpinbutton(UI::Widget::DialogPage& p, Glib::ustring const &prefs_path, double def_value); static void AddBaseSimplifySpinbutton(UI::Widget::DialogPage& p, Glib::ustring const &prefs_path, double def_value); static void AddPencilPowerStrokePressureStep(UI::Widget::DialogPage& p, Glib::ustring const &prefs_path, gint def_value); - static void AddPencilPowerStrokeMinSimplify(UI::Widget::DialogPage& p, Glib::ustring const &prefs_path, gint def_value); static void AddNewObjectsStyle(UI::Widget::DialogPage& p, Glib::ustring const &prefs_path, const gchar* banner = NULL); void on_pagelist_selection_changed(); diff --git a/src/ui/tools/freehand-base.cpp b/src/ui/tools/freehand-base.cpp index b72eb84db..e294c37a2 100644 --- a/src/ui/tools/freehand-base.cpp +++ b/src/ui/tools/freehand-base.cpp @@ -259,10 +259,6 @@ static void spdc_apply_powerstroke_shape(std::vector points, Freeha Effect* lpe = SP_LPE_ITEM(item)->getCurrentLPE(); lpe->getRepr()->setAttribute("sort_points", "true"); lpe->getRepr()->setAttribute("interpolator_type", "CentripetalCatmullRom"); - lpe->getRepr()->setAttribute("interpolate_original", "true"); - lpe->getRepr()->setAttribute("interpolator_beta", "0.2"); - lpe->getRepr()->setAttribute("miter_limit", "100"); - lpe->getRepr()->setAttribute("linejoin_type", "miter"); static_cast(lpe)->offset_points.param_set_and_write_new_value(pt->points); pt->points.clear(); return; diff --git a/src/ui/tools/pencil-tool.cpp b/src/ui/tools/pencil-tool.cpp index 6dd3126e3..7779f6687 100644 --- a/src/ui/tools/pencil-tool.cpp +++ b/src/ui/tools/pencil-tool.cpp @@ -45,6 +45,7 @@ #include "display/sp-canvas.h" #include "display/curve.h" #include "live_effects/lpe-powerstroke.h" +#include "live_effects/lpe-powerstroke-interpolators.h" #include "ui/tool/event-utils.h" namespace Inkscape { @@ -70,11 +71,8 @@ PencilTool::PencilTool() , state(SP_PENCIL_CONTEXT_IDLE) , req_tangent(0, 0) , is_drawing(false) - , points_parsed(0) , sketch_n(0) - , _powerpreviewtail(NULL) , _powerpreview(NULL) - , _preview_ok(true) { } @@ -98,6 +96,7 @@ PencilTool::~PencilTool() { void PencilTool::_extinput(GdkEvent *event) { if (gdk_event_get_axis (event, GDK_AXIS_PRESSURE, &this->pressure)) { this->pressure = CLAMP (this->pressure, DDC_MIN_PRESSURE, DDC_MAX_PRESSURE); + input_has_pressure = true; } else { this->pressure = DDC_DEFAULT_PRESSURE; //If no pressure device ignore pressure button @@ -200,7 +199,7 @@ bool PencilTool::_handleButtonPress(GdkEventButton const &bevent) { /* Set first point of sequence */ SnapManager &m = desktop->namedview->snap_manager; if (bevent.state & GDK_CONTROL_MASK) { - m.setup(desktop, true); + m.setup(desktop, true, _powerpreview); if (!(bevent.state & GDK_SHIFT_MASK)) { m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE); } @@ -219,7 +218,7 @@ bool PencilTool::_handleButtonPress(GdkEventButton const &bevent) { } desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path")); } else { - m.setup(desktop, true); + m.setup(desktop, true, _powerpreview); if (!(bevent.state & GDK_SHIFT_MASK)) { // This is the first click of a new curve; deselect item so that // this curve is not combined with it (unless it is drawn from its @@ -380,7 +379,7 @@ bool PencilTool::_handleMotionNotify(GdkEventMotion const &mevent) { // b) release the mousebutton to finish a freehand drawing if (!this->sp_event_context_knot_mouseover()) { SnapManager &m = desktop->namedview->snap_manager; - m.setup(desktop, true); + m.setup(desktop, true, _powerpreview); m.preSnap(Inkscape::SnapCandidatePoint(p, Inkscape::SNAPSOURCE_NODE_HANDLE)); m.unSetup(); } @@ -453,7 +452,7 @@ bool PencilTool::_handleButtonRelease(GdkEventButton const &revent) { /* Write curves to object */ desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand")); - this->_interpolate(true); + this->_interpolate(); //Remove here because points are recalculated once finish. This is because we want live preview. this->points.clear(); spdc_concat_colors_and_flush(this, FALSE); @@ -664,7 +663,7 @@ void PencilTool::_finishEndpoint() { } void -PencilTool::_powerStrokePreview(Geom::Path const path, std::vector points, bool write) +PencilTool::_powerStrokePreview(Geom::Path const path, std::vector points) { using namespace Inkscape::LivePathEffect; SPDocument * document = SP_ACTIVE_DOCUMENT; @@ -672,15 +671,14 @@ PencilTool::_powerStrokePreview(Geom::Path const path, std::vector return; } Inkscape::XML::Document *xml_doc = document->getReprDoc(); - SPCurve * curve = new SPCurve(); Geom::PathVector const pathv(path); - if (!_powerpreview) { + SPLPEItem * lpeitem = dynamic_cast(_powerpreview); + if (!lpeitem) { Inkscape::XML::Node *body = NULL; body = xml_doc->createElement("svg:path"); body->setAttribute("sodipodi:insensitive", "true"); sp_desktop_apply_style_tool(desktop, body, Glib::ustring("/tools/freehand/pencil").data(), false); _powerpreview = SP_SHAPE(SP_ITEM(SP_ACTIVE_DESKTOP->currentLayer())->appendChildRepr(body)); - _powerpreview->setCurve(curve, true); Inkscape::GC::release(body); SPCSSAttr *css = sp_css_attr_from_object(_powerpreview, SP_STYLE_FLAG_ALWAYS); const gchar * stroke = sp_repr_css_property(css, "stroke", "none"); @@ -705,57 +703,21 @@ PencilTool::_powerStrokePreview(Geom::Path const path, std::vector } Effect::createAndApply(POWERSTROKE, SP_ACTIVE_DESKTOP->doc(), SP_ITEM(_powerpreview)); Effect* lpe = lpeitem->getCurrentLPE(); + static_cast(lpe)->offset_points.param_set_and_write_new_value(points); lpe->getRepr()->setAttribute("sort_points", "true"); lpe->getRepr()->setAttribute("interpolator_type", "CentripetalCatmullRom"); - lpe->getRepr()->setAttribute("interpolate_original", "true"); - lpe->getRepr()->setAttribute("interpolator_beta", "0.2"); - lpe->getRepr()->setAttribute("miter_limit", "100"); - lpe->getRepr()->setAttribute("linejoin_type", "miter"); - end_linecap_type = lpe->getRepr()->attribute("end_linecap_type"); - } - SPLPEItem * lpeitem = dynamic_cast(_powerpreview); - if (!lpeitem) { - return; - } - Effect* lpe = lpeitem->getCurrentLPE(); - lpe->getRepr()->setAttribute("end_linecap_type", end_linecap_type); - static_cast(lpe)->offset_points.param_set_and_write_new_value(points); - gchar * pvector_str = sp_svg_write_path(pathv); - _powerpreview->setAttribute("inkscape:original-d" , pvector_str); - g_free(pvector_str); - if (write) { - lpe->getRepr()->setAttribute("end_linecap_type", "round"); - curve = _powerpreview->getCurve(); - _powerpreview->setAttribute("inkscape:original-d", ""); - _powerpreview->setAttribute("d", ""); - if (!_powerpreviewtail) { - Inkscape::XML::Node *tail = NULL; - tail = xml_doc->createElement("svg:path"); - tail->setAttribute("sodipodi:insensitive", "true"); - _powerpreviewtail = SP_SHAPE(SP_ITEM(SP_ACTIVE_DESKTOP->currentLayer())->appendChildRepr(tail)); - SPCSSAttr *css = sp_css_attr_from_object(_powerpreview, SP_STYLE_FLAG_ALWAYS); - Glib::ustring css_str; - sp_repr_css_write_string(css,css_str); - tail->setAttribute("style", css_str.c_str()); - Inkscape::GC::release(tail); - } - if (!curve->is_empty()) { - _powerpreviewtail->setCurve(curve, true); - _preview_ok = true; - } else {//if (!_second_chance_preview) { - _preview_ok = false; - } + } else { + Effect* lpe = lpeitem->getCurrentLPE(); + static_cast(lpe)->offset_points.param_set_and_write_new_value(points); + gchar * pvector_str = sp_svg_write_path(pathv); + _powerpreview->setAttribute("inkscape:original-d" , pvector_str); + g_free(pvector_str); } - curve->unref(); } void PencilTool::removePowerStrokePreview() { - if (_powerpreviewtail) { - _powerpreviewtail->deleteObject(); - _powerpreviewtail = NULL; - } if(_powerpreview) { using namespace Inkscape::LivePathEffect; Effect* lpe = SP_LPE_ITEM(_powerpreview)->getCurrentLPE(); @@ -774,12 +736,15 @@ PencilTool::addPowerStrokePencil(SPCurve * c) { using namespace Inkscape::LivePathEffect; this->points.clear(); + this->points_pos.clear(); + this->key_nodes.clear(); Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0); - double pstol = prefs->getDoubleLimited("/tools/freehand/pencil/powerstrokemintolerance", 45.0, 1.0, 100.0); - double gap_pressure = prefs->getIntLimited("/tools/freehand/pencil/ps-step-pressure",10, 1, 100)/100.0; - double min = prefs->getIntLimited("/tools/freehand/pencil/minpressure", 0, 1, 100) / 100.0; - double max = prefs->getIntLimited("/tools/freehand/pencil/maxpressure", 100, 1, 100) / 100.0; + double step = prefs->getIntLimited("/tools/freehand/pencil/ps-step-pressure",10, 1, 100)/100.0; + double min = prefs->getIntLimited("/tools/freehand/pencil/minpressure", 0, 1, 100) / 100.0; + double max = prefs->getIntLimited("/tools/freehand/pencil/maxpressure", 100, 1, 100) / 100.0; + //This is a calculated number of nodes from 2 to 7 to get a simplify simil + int tol = 2 + (prefs->getIntLimited("/tools/freehand/pencil/tolerance",10, 1, 100)/20); + if (min > max){ min = max; } @@ -798,18 +763,6 @@ PencilTool::addPowerStrokePencil(SPCurve * c) } if(!c) { live = true; - //Simplify a bit the base curve to avoid artifacts - SPCurve * previous_red = red_curve->copy(); - SPCurve * previous_green = green_curve->copy(); - prefs->setDouble("/tools/freehand/pencil/tolerance", pstol); - std::vector stroreps = this->ps; - std::vector strorewps = this->wps; - _interpolate(true); - this->ps = stroreps; - this->wps = strorewps; - stroreps.clear(); - strorewps.clear(); - prefs->setDouble("/tools/freehand/pencil/tolerance", tol); if (sa && sa->curve) { curve = sa_overwrited->copy(); if (!green_curve->is_unset()) { @@ -828,13 +781,9 @@ PencilTool::addPowerStrokePencil(SPCurve * c) curve = NULL; } } - if (curve->is_empty()) { + if (!curve || curve->is_unset()) { curve = NULL; } - red_curve = previous_red->copy(); - green_curve = previous_green->copy(); - previous_red->unref(); - previous_green->unref(); } else { curve = c->copy(); removePowerStrokePreview(); @@ -848,8 +797,10 @@ PencilTool::addPowerStrokePencil(SPCurve * c) } Geom::Affine transformCoordinate = SP_ITEM(SP_ACTIVE_DESKTOP->currentLayer())->i2dt_affine(); Geom::PathVector pathvector = curve->get_pathvector(); + if (!pathvector.size()) { + return; + } Geom::Path const path = pathvector[0]; - curve->unref(); double previous_pressure = 0.0; double pressure_computed = 0.0; double dezoomify_factor = 0.05 * 1000/SP_EVENT_CONTEXT(this)->desktop->current_zoom();//\/100 we want 100% = 1; @@ -861,47 +812,56 @@ PencilTool::addPowerStrokePencil(SPCurve * c) position *= transformCoordinate.inverse(); } double pos = Geom::nearest_time(position, path); + size_t last = 0; for (auto point = this->ps.begin(); point != this->ps.end(); ++point, ++pressure) { + position = *point; counter++; + double pressure_shrunk = (*pressure * (max - min)) + min; //We need half width for power stroke pressure_computed = pressure_shrunk * dezoomify_factor/2.0; //remove start pressure gap if (start) { start = false; - this->points.push_back(Geom::Point(pos + 0.01, pressure_computed)); + this->points.push_back(Geom::Point(pos, pressure_computed)); + this->key_nodes.push_back(position); + this->points_pos.push_back(position); previous_pressure = pressure_shrunk; continue; } - position = *point; if (!live) { position *= transformCoordinate.inverse(); } pos = Geom::nearest_time(position, path); - if (pos < 1e6 && std::abs(previous_pressure - pressure_shrunk) > gap_pressure && pos < path.size() - 1) { + if (counter%tol == 0) { + this->key_nodes.push_back(position); + } + if (pos < 1e6 && std::abs(previous_pressure - pressure_shrunk) > step && pos < path.size() - 1) { previous_pressure = pressure_shrunk; this->points.push_back(Geom::Point(pos, pressure_computed)); + this->points_pos.push_back(position); + if (counter%tol != 0) { + this->key_nodes.push_back(position); + } } - } - if (live && this->points.size() > 0) { - bool write = false; - if (points_parsed != this->points.size() || !_preview_ok) { - points_parsed = this->points.size(); - write = true; + if (Geom::are_near(this->key_nodes[this->key_nodes.size()-1],this->key_nodes[this->key_nodes.size()-2])) { + this->key_nodes.pop_back(); } - if (write) { - _powerStrokePreview(path * transformCoordinate.inverse(), this->points, write); + } + bool last_added = counter%tol == 0; + if (!last_added) { + this->key_nodes.pop_back(); + this->key_nodes.push_back(position); + } + if (this->points.size() > 0) { + _interpolate(curve); + if (live && !curve->is_empty()) { + _powerStrokePreview(curve->get_pathvector()[0] * transformCoordinate.inverse(), this->points); } else { - Geom::Point start = this->points[this->points.size()-1]; - if (start[Geom::X] < path.size()) { - std::vector pointsend; - Geom::Path const pathtail = path.portion(start[Geom::X], path.size()); - pointsend.push_back(Geom::Point(0.01, start[Geom::Y])); - _powerStrokePreview(pathtail * transformCoordinate.inverse(), pointsend, write); - pointsend.clear(); - } + c = curve->copy(); } } + curve->unref(); } void PencilTool::_addFreehandPoint(Geom::Point const &p, guint /*state*/) { @@ -914,8 +874,13 @@ void PencilTool::_addFreehandPoint(Geom::Point const &p, guint /*state*/) { this->p[this->npoints++] = p; this->_fitAndSplit(); this->ps.push_back(p); - this->wps.push_back(this->pressure); if (input_has_pressure) { + this->wps.push_back(this->pressure); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->red_bpath), NULL); + for (auto i:this->green_bpaths) { + sp_canvas_item_destroy(i); + } + this->green_bpaths.clear(); this->addPowerStrokePencil(NULL); } } @@ -927,7 +892,8 @@ square(double const x) return x * x; } -void PencilTool::_interpolate(bool realize) { +void +PencilTool::_interpolate(SPCurve * curve) { if ( this->ps.size() <= 1 ) { return; } @@ -943,66 +909,11 @@ void PencilTool::_interpolate(bool realize) { double tol2 = prefs->getDoubleLimited("/tools/freehand/pencil/base-simplify", 25.0, 1.0, 100.0) * 0.4; tol = std::min(tol,tol2); } - //To avoid artifacts on pressure power - if (input_has_pressure) { - double pstol = prefs->getDoubleLimited("/tools/freehand/pencil/powerstrokemintolerance", 40); - if (tol < pstol * 0.4) { - tol = pstol * 0.4; - } - //we dont need a exact calulation set up a high precission - //we remove pointa at start and end nearest to 1/20 of total length - double distance = 0; - Geom::Point prev = this->ps[0]; - for (auto i:this->ps) { - if (i == prev) { - continue; - } - distance += Geom::distance(i, prev); - prev = i; - } - double smoothlenght = (distance/this->ps.size()); - std::cout << smoothlenght << "smoothlenght" << std::endl; - -// //Double check to limit on large strokes -// double limitlenght = desktop->get_display_area().diameter()/20.0; -// std::cout << limitlenght << "limitlenght" << std::endl; -// //smoothlenght = std::min(smoothlenght, limitlenght); -// //Smooth start segments - - - - - if (realize && this->ps.size() > 3) { - Geom::Point start_point = *this->ps.begin(); - while ( this->ps.size() > 6 && Geom::distance(*(this->ps.begin()+1), start_point) < smoothlenght) { - this->ps.erase(this->ps.begin() + 1); - this->wps.erase(this->wps.begin() + 1); - } - } - //Smooth last segments - if (realize && this->ps.size() > 3) { - Geom::Point last_point = *this->ps.end(); - bool erased = false; - while ( this->ps.size() > 6 && Geom::distance(*this->ps.end(), last_point) < smoothlenght) { - this->ps.pop_back(); - this->wps.pop_back(); - erased = true; - } - if (erased) { - this->wps.push_back(this->wps[this->wps.size()-1]); - this->ps.push_back(last_point); - } - } - } double tolerance_sq = 0.02 * square(this->desktop->w2d().descrim() * tol) * exp(0.2 * tol - 2); g_assert(is_zero(this->req_tangent) || is_unit_vector(this->req_tangent)); - this->green_curve->reset(); - this->red_curve->reset(); - this->red_curve_is_valid = false; - int n_points = this->ps.size(); // worst case gives us a segment per point @@ -1011,55 +922,69 @@ void PencilTool::_interpolate(bool realize) { std::vector b(max_segs); int const n_segs = Geom::bezier_fit_cubic_r(b.data(), this->ps.data(), n_points, tolerance_sq, max_segs); - if (n_segs > 0) { - /* Fit and draw and reset state */ - - this->green_curve->moveto(b[0]); - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); - guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0); - for (int c = 0; c < n_segs; c++) { - // if we are in BSpline we modify the trace to create adhoc nodes - if (mode == 2) { - Geom::Point point_at1 = b[4 * c + 0] + (1./3) * (b[4 * c + 3] - b[4 * c + 0]); - point_at1 = Geom::Point(point_at1[X] + HANDLE_CUBIC_GAP, point_at1[Y] + HANDLE_CUBIC_GAP); - Geom::Point point_at2 = b[4 * c + 3] + (1./3) * (b[4 * c + 0] - b[4 * c + 3]); - point_at2 = Geom::Point(point_at2[X] + HANDLE_CUBIC_GAP, point_at2[Y] + HANDLE_CUBIC_GAP); - this->green_curve->curveto(point_at1,point_at2,b[4*c+3]); - } else { - //force retracted handle at end if power stroke - if (c == n_segs - 1 && input_has_pressure) { - this->green_curve->curveto(b[4 * c + 1], b[4 * c + 3], b[4 * c + 3]); - } else if (c == 0 && input_has_pressure) { - this->green_curve->curveto(b[4 * c], b[4 * c + 2], b[4 * c + 3]); + if (input_has_pressure) { + std::unique_ptr interpolator( Geom::Interpolate::Interpolator::create(Geom::Interpolate::INTERP_CENTRIPETAL_CATMULLROM) ); + Geom::Path path = interpolator->interpolateToPath(this->key_nodes); + size_t counter = 0; + for (auto point:this->points_pos) { + double pos = Geom::nearest_time(point, path); + this->points[counter][Geom::X] = pos; + counter++; + } + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->red_bpath), NULL); + curve->set_pathvector(path); + } else { + /* Fit and draw and reset state */ + curve->moveto(b[0]); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0); + for (int c = 0; c < n_segs; c++) { + // if we are in BSpline we modify the trace to create adhoc nodes + if (mode == 2) { + Geom::Point point_at1 = b[4 * c + 0] + (1./3) * (b[4 * c + 3] - b[4 * c + 0]); + point_at1 = Geom::Point(point_at1[X] + HANDLE_CUBIC_GAP, point_at1[Y] + HANDLE_CUBIC_GAP); + Geom::Point point_at2 = b[4 * c + 3] + (1./3) * (b[4 * c + 0] - b[4 * c + 3]); + point_at2 = Geom::Point(point_at2[X] + HANDLE_CUBIC_GAP, point_at2[Y] + HANDLE_CUBIC_GAP); + curve->curveto(point_at1,point_at2,b[4*c+3]); } else { - this->green_curve->curveto(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]); + //force retracted handle at end if power stroke + if (c == n_segs - 1 && input_has_pressure) { + curve->curveto(b[4 * c + 1], b[4 * c + 3], b[4 * c + 3]); + } else if (c == 0 && input_has_pressure) { + curve->curveto(b[4 * c], b[4 * c + 2], b[4 * c + 3]); + } else { + curve->curveto(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]); + } } } - } - if (!input_has_pressure) { - sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->red_bpath), this->green_curve); - } - - /* Fit and draw and copy last point */ - g_assert(!this->green_curve->is_empty()); - - /* Set up direction of next curve. */ - { - Geom::Curve const * last_seg = this->green_curve->last_segment(); - g_assert( last_seg ); // Relevance: validity of (*last_seg) - this->p[0] = last_seg->finalPoint(); - this->npoints = 1; - Geom::Curve *last_seg_reverse = last_seg->reverse(); - Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); - delete last_seg_reverse; - this->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) - ? Geom::Point(0, 0) - : Geom::unit_vector(req_vec) ); + sp_canvas_bpath_set_bpath(SP_CANVAS_BPATH(this->red_bpath), curve); + /* Fit and draw and copy last point */ + g_assert(!curve->is_empty()); + /* Set up direction of next curve. */ + { + Geom::Curve const * last_seg = curve->last_segment(); + g_assert( last_seg ); // Relevance: validity of (*last_seg) + this->p[0] = last_seg->finalPoint(); + this->npoints = 1; + Geom::Curve *last_seg_reverse = last_seg->reverse(); + Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) ); + delete last_seg_reverse; + this->req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) ) + ? Geom::Point(0, 0) + : Geom::unit_vector(req_vec) ); + } } } } +void PencilTool::_interpolate() { + this->green_curve->reset(); + this->red_curve->reset(); + this->red_curve_is_valid = false; + _interpolate(this->green_curve); +} + /* interpolates the sketched curve and tweaks the current sketch interpolation*/ void PencilTool::_sketchInterpolate() { diff --git a/src/ui/tools/pencil-tool.h b/src/ui/tools/pencil-tool.h index e15289ad1..354acfbba 100644 --- a/src/ui/tools/pencil-tool.h +++ b/src/ui/tools/pencil-tool.h @@ -44,9 +44,10 @@ public: Geom::Point req_tangent; bool is_drawing; - size_t points_parsed; std::vector ps; + std::vector key_nodes; std::vector points; + std::vector points_pos; std::vector wps; void addPowerStrokePencil(SPCurve * c); @@ -70,16 +71,14 @@ private: bool _handleKeyPress(GdkEventKey const &event); bool _handleKeyRelease(GdkEventKey const &event); void _setStartpoint(Geom::Point const &p); - void _powerStrokePreview(Geom::Path const path, std::vector points, bool write); + void _powerStrokePreview(Geom::Path const path, std::vector points); SPShape *_powerpreview; - SPShape *_powerpreviewtail; - bool _preview_ok; - gchar const * end_linecap_type; void _setEndpoint(Geom::Point const &p); void _finishEndpoint(); void _addFreehandPoint(Geom::Point const &p, guint state); void _fitAndSplit(); - void _interpolate(bool realize = false); + void _interpolate(); + void _interpolate(SPCurve * curve); void _sketchInterpolate(); void _extinput(GdkEvent *event); void _cancel(); -- cgit v1.2.3