From cca66b9ec4e494c1d919bff0f71a820d8afab1fa Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:24:48 +0200 Subject: Adding upstream version 1.2.2. Signed-off-by: Daniel Baumann --- src/live_effects/lpe-transform_2pts.cpp | 482 ++++++++++++++++++++++++++++++++ 1 file changed, 482 insertions(+) create mode 100644 src/live_effects/lpe-transform_2pts.cpp (limited to 'src/live_effects/lpe-transform_2pts.cpp') diff --git a/src/live_effects/lpe-transform_2pts.cpp b/src/live_effects/lpe-transform_2pts.cpp new file mode 100644 index 0000000..b3685ac --- /dev/null +++ b/src/live_effects/lpe-transform_2pts.cpp @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * LPE "Transform through 2 points" implementation + */ + +/* + * Authors: + * Jabier Arraiza Cenoz + * + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "lpe-transform_2pts.h" + +#include + +#include "display/curve.h" +#include "helper/geom.h" +#include "object/sp-path.h" +#include "svg/svg.h" +#include "ui/icon-names.h" + +// TODO due to internal breakage in glibmm headers, this must be last: +#include + + +namespace Inkscape { +namespace LivePathEffect { + +LPETransform2Pts::LPETransform2Pts(LivePathEffectObject *lpeobject) : + Effect(lpeobject), + elastic(_("Elastic"), _("Elastic transform mode"), "elastic", &wr, this, false,"", INKSCAPE_ICON("on-outline"), INKSCAPE_ICON("off-outline")), + from_original_width(_("From original width"), _("From original width"), "from_original_width", &wr, this, false,"", INKSCAPE_ICON("on-outline"), INKSCAPE_ICON("off-outline")), + lock_length(_("Lock length"), _("Lock length to current distance"), "lock_length", &wr, this, false,"", INKSCAPE_ICON("on-outline"), INKSCAPE_ICON("off-outline")), + lock_angle(_("Lock angle"), _("Lock angle"), "lock_angle", &wr, this, false,"", INKSCAPE_ICON("on-outline"), INKSCAPE_ICON("off-outline")), + flip_horizontal(_("Flip horizontal"), _("Flip horizontal"), "flip_horizontal", &wr, this, false,"", INKSCAPE_ICON("on-outline"), INKSCAPE_ICON("off-outline")), + flip_vertical(_("Flip vertical"), _("Flip vertical"), "flip_vertical", &wr, this, false,"", INKSCAPE_ICON("on-outline"), INKSCAPE_ICON("off-outline")), + start(_("Start"), _("Start point"), "start", &wr, this, "Start point"), + end(_("End"), _("End point"), "end", &wr, this, "End point"), + stretch(_("Stretch"), _("Stretch the result"), "stretch", &wr, this, 1), + offset(_("Offset"), _("Offset from knots"), "offset", &wr, this, 0), + first_knot(_("First Knot"), _("First Knot"), "first_knot", &wr, this, 1), + last_knot(_("Last Knot"), _("Last Knot"), "last_knot", &wr, this, 1), + helper_size(_("Helper size:"), _("Rotation helper size"), "helper_size", &wr, this, 3), + from_original_width_toggler(false), + point_a(Geom::Point()), + point_b(Geom::Point()), + pathvector(), + append_path(false), + previous_angle(Geom::rad_from_deg(0)), + previous_start(Geom::Point()), + previous_length(-1) +{ + + registerParameter(&first_knot); + registerParameter(&last_knot); + registerParameter(&helper_size); + registerParameter(&stretch); + registerParameter(&offset); + registerParameter(&start); + registerParameter(&end); + registerParameter(&elastic); + registerParameter(&from_original_width); + registerParameter(&flip_vertical); + registerParameter(&flip_horizontal); + registerParameter(&lock_length); + registerParameter(&lock_angle); + + first_knot.param_make_integer(); + first_knot.param_set_undo(false); + last_knot.param_make_integer(); + last_knot.param_set_undo(false); + helper_size.param_set_range(0, 999); + helper_size.param_set_increments(1, 1); + helper_size.param_set_digits(0); + offset.param_set_range(std::numeric_limits::lowest(), std::numeric_limits::max()); + offset.param_set_increments(1, 1); + offset.param_set_digits(2); + stretch.param_set_range(0, 999.0); + stretch.param_set_increments(0.01, 0.01); + stretch.param_set_digits(4); + apply_to_clippath_and_mask = true; +} + +LPETransform2Pts::~LPETransform2Pts() += default; + +void +LPETransform2Pts::doOnApply(SPLPEItem const* lpeitem) +{ + using namespace Geom; + original_bbox(lpeitem, false, true); + point_a = Point(boundingbox_X.min(), boundingbox_Y.middle()); + point_b = Point(boundingbox_X.max(), boundingbox_Y.middle()); + SPLPEItem * splpeitem = const_cast(lpeitem); + SPPath *sp_path = dynamic_cast(splpeitem); + if (sp_path) { + pathvector = sp_path->curveForEdit()->get_pathvector(); + } + if(!pathvector.empty()) { + point_a = pathvector.initialPoint(); + point_b = pathvector.finalPoint(); + if(are_near(point_a,point_b)) { + point_b = pathvector.back().finalCurve().initialPoint(); + } + size_t nnodes = nodeCount(pathvector); + last_knot.param_set_value(nnodes); + } + + previous_length = Geom::distance(point_a,point_b); + Geom::Ray transformed(point_a,point_b); + previous_angle = transformed.angle(); + start.param_update_default(point_a); + start.param_set_default(); + end.param_update_default(point_b); + end.param_set_default(); +} + +void LPETransform2Pts::transform_multiply(Geom::Affine const &postmul, bool /*set*/) +{ + if (sp_lpe_item && sp_lpe_item->pathEffectsEnabled() && sp_lpe_item->optimizeTransforms()) { + start.param_transform_multiply(postmul, false); + end.param_transform_multiply(postmul, false); + } +} + +void +LPETransform2Pts::doBeforeEffect (SPLPEItem const* lpeitem) +{ + using namespace Geom; + original_bbox(lpeitem, false, true); + point_a = Point(boundingbox_X.min(), boundingbox_Y.middle()); + point_b = Point(boundingbox_X.max(), boundingbox_Y.middle()); + + SPLPEItem * splpeitem = const_cast(lpeitem); + SPPath *sp_path = dynamic_cast(splpeitem); + if (sp_path) { + pathvector = sp_path->curveForEdit()->get_pathvector(); + } + if(from_original_width_toggler != from_original_width) { + from_original_width_toggler = from_original_width; + reset(); + } + if(!pathvector.empty() && !from_original_width) { + append_path = false; + point_a = pointAtNodeIndex(pathvector,(size_t)first_knot-1); + point_b = pointAtNodeIndex(pathvector,(size_t)last_knot-1); + size_t nnodes = nodeCount(pathvector); + first_knot.param_set_range(1, last_knot-1); + last_knot.param_set_range(first_knot+1, nnodes); + if (from_original_width){ + from_original_width.param_setValue(false); + } + } else { + if (first_knot != 1){ + first_knot.param_set_value(1); + } + if (last_knot != 2){ + last_knot.param_set_value(2); + } + first_knot.param_set_range(1,1); + last_knot.param_set_range(2,2); + append_path = false; + if (!from_original_width){ + from_original_width.param_setValue(true); + } + } + if(lock_length && !lock_angle && previous_length != -1) { + Geom::Ray transformed((Geom::Point)start,(Geom::Point)end); + if(previous_start == start || previous_angle == Geom::rad_from_deg(0)) { + previous_angle = transformed.angle(); + } + } else if(lock_angle && !lock_length && previous_angle != Geom::rad_from_deg(0)) { + if(previous_start == start){ + previous_length = Geom::distance((Geom::Point)start, (Geom::Point)end); + } + } + if(lock_length || lock_angle ) { + Geom::Point end_point = Geom::Point::polar(previous_angle, previous_length) + (Geom::Point)start; + end.param_setValue(end_point); + } + Geom::Ray transformed((Geom::Point)start,(Geom::Point)end); + previous_angle = transformed.angle(); + previous_length = Geom::distance((Geom::Point)start, (Geom::Point)end); + previous_start = start; +} + +void +LPETransform2Pts::updateIndex() +{ + SPLPEItem * splpeitem = const_cast(sp_lpe_item); + SPPath *sp_path = dynamic_cast(splpeitem); + if (sp_path) { + pathvector = sp_path->curveForEdit()->get_pathvector(); + } + if(pathvector.empty()) { + return; + } + if(!from_original_width) { + point_a = pointAtNodeIndex(pathvector,(size_t)first_knot-1); + point_b = pointAtNodeIndex(pathvector,(size_t)last_knot-1); + start.param_update_default(point_a); + start.param_set_default(); + end.param_update_default(point_b); + end.param_set_default(); + start.param_update_default(point_a); + end.param_update_default(point_b); + start.param_set_default(); + end.param_set_default(); + } + DocumentUndo::done(getSPDoc(), _("Change index of knot"), INKSCAPE_ICON("dialog-path-effects")); +} +//todo migrate to PathVector class? +size_t +LPETransform2Pts::nodeCount(Geom::PathVector pathvector) const +{ + size_t n = 0; + for (auto & it : pathvector) { + n += count_path_nodes(it); + } + return n; +} +//todo migrate to PathVector class? +Geom::Point +LPETransform2Pts::pointAtNodeIndex(Geom::PathVector pathvector, size_t index) const +{ + size_t n = 0; + for (auto & pv_it : pathvector) { + for (Geom::Path::iterator curve_it = pv_it.begin(); curve_it != pv_it.end_closed(); ++curve_it) { + if(index == n) { + return curve_it->initialPoint(); + } + n++; + } + } + return Geom::Point(); +} +//todo migrate to PathVector class? Not used +Geom::Path +LPETransform2Pts::pathAtNodeIndex(Geom::PathVector pathvector, size_t index) const +{ + size_t n = 0; + for (auto & pv_it : pathvector) { + for (Geom::Path::iterator curve_it = pv_it.begin(); curve_it != pv_it.end_closed(); ++curve_it) { + if(index == n) { + return pv_it; + } + n++; + } + } + return Geom::Path(); +} + + +void +LPETransform2Pts::reset() +{ + point_a = Geom::Point(boundingbox_X.min(), boundingbox_Y.middle()); + point_b = Geom::Point(boundingbox_X.max(), boundingbox_Y.middle()); + if(!pathvector.empty() && !from_original_width) { + size_t nnodes = nodeCount(pathvector); + first_knot.param_set_range(1, last_knot-1); + last_knot.param_set_range(first_knot+1, nnodes); + first_knot.param_set_value(1); + last_knot.param_set_value(nnodes); + point_a = pathvector.initialPoint(); + point_b = pathvector.finalPoint(); + } else { + first_knot.param_set_value(1); + last_knot.param_set_value(2); + } + offset.param_set_value(0.0); + stretch.param_set_value(1.0); + Geom::Ray transformed(point_a, point_b); + previous_angle = transformed.angle(); + previous_length = Geom::distance(point_a, point_b); + start.param_update_default(point_a); + end.param_update_default(point_b); + start.param_set_default(); + end.param_set_default(); +} + +Gtk::Widget *LPETransform2Pts::newWidget() +{ + // use manage here, because after deletion of Effect object, others might + // still be pointing to this widget. + Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + vbox->set_border_width(5); + vbox->set_homogeneous(false); + vbox->set_spacing(6); + + std::vector::iterator it = param_vector.begin(); + Gtk::Box * button1 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::Box * button2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::Box * button3 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + Gtk::Box * button4 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL,0)); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = dynamic_cast(param->param_newWidget()); + Glib::ustring *tip = param->param_getTooltip(); + if (param->param_key == "first_knot" || param->param_key == "last_knot") { + Inkscape::UI::Widget::Scalar *registered_widget = Gtk::manage(dynamic_cast(widg)); + registered_widget->signal_value_changed().connect(sigc::mem_fun(*this, &LPETransform2Pts::updateIndex)); + widg = registered_widget; + if (widg) { + Gtk::Box *hbox_scalar = dynamic_cast(widg); + std::vector child_list = hbox_scalar->get_children(); + Gtk::Entry *entry_widget = dynamic_cast(child_list[1]); + entry_widget->set_width_chars(3); + vbox->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } else if (param->param_key == "from_original_width" || param->param_key == "elastic") { + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + button1->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } else if (param->param_key == "flip_horizontal" || param->param_key == "flip_vertical") { + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + button2->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } else if (param->param_key == "lock_angle" || param->param_key == "lock_length") { + Glib::ustring * tip = param->param_getTooltip(); + if (widg) { + button3->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } else if (widg) { + vbox->pack_start(*widg, true, true, 2); + if (tip) { + widg->set_tooltip_text(*tip); + } else { + widg->set_tooltip_text(""); + widg->set_has_tooltip(false); + } + } + } + + ++it; + } + Gtk::Button *reset = Gtk::manage(new Gtk::Button(Glib::ustring(_("Reset")))); + reset->signal_clicked().connect(sigc::mem_fun(*this, &LPETransform2Pts::reset)); + button4->pack_start(*reset, true, true, 2); + vbox->pack_start(*button1, true, true, 2); + vbox->pack_start(*button2, true, true, 2); + vbox->pack_start(*button3, true, true, 2); + vbox->pack_start(*button4, true, true, 2); + if(Gtk::Widget* widg = defaultParamSet()) { + vbox->pack_start(*widg, true, true, 2); + } + return dynamic_cast(vbox); +} + +Geom::Piecewise > +LPETransform2Pts::doEffect_pwd2 (Geom::Piecewise > const & pwd2_in) +{ + Geom::Piecewise > output; + double sca = Geom::distance((Geom::Point)start,(Geom::Point)end)/Geom::distance(point_a,point_b); + Geom::Ray original(point_a,point_b); + Geom::Ray transformed((Geom::Point)start,(Geom::Point)end); + double rot = transformed.angle() - original.angle(); + Geom::Path helper; + helper.start(point_a); + helper.appendNew(point_b); + Geom::Affine m; + Geom::Angle original_angle = original.angle(); + if(flip_horizontal && flip_vertical){ + m *= Geom::Rotate(-original_angle); + m *= Geom::Scale(-1,-1); + m *= Geom::Rotate(original_angle); + } else if(flip_vertical){ + m *= Geom::Rotate(-original_angle); + m *= Geom::Scale(1,-1); + m *= Geom::Rotate(original_angle); + } else if(flip_horizontal){ + m *= Geom::Rotate(-original_angle); + m *= Geom::Scale(-1,1); + m *= Geom::Rotate(original_angle); + } + if(stretch != 1){ + m *= Geom::Rotate(-original_angle); + m *= Geom::Scale(1,stretch); + m *= Geom::Rotate(original_angle); + } + if(elastic) { + m *= Geom::Rotate(-original_angle); + if(sca > 1){ + m *= Geom::Scale(sca, 1.0); + } else { + m *= Geom::Scale(sca, 1.0-((1.0-sca)/2.0)); + } + m *= Geom::Rotate(transformed.angle()); + } else { + m *= Geom::Scale(sca); + m *= Geom::Rotate(rot); + } + helper *= m; + Geom::Point trans = (Geom::Point)start - helper.initialPoint(); + if(flip_horizontal){ + trans = (Geom::Point)end - helper.initialPoint(); + } + if(offset != 0){ + trans = Geom::Point::polar(transformed.angle() + Geom::rad_from_deg(-90),offset) + trans; + } + m *= Geom::Translate(trans); + + output.concat(pwd2_in * m); + + return output; +} + +void +LPETransform2Pts::addCanvasIndicators(SPLPEItem const */*lpeitem*/, std::vector &hp_vec) +{ + using namespace Geom; + hp_vec.clear(); + Geom::Path hp; + hp.start((Geom::Point)start); + hp.appendNew((Geom::Point)end); + Geom::PathVector pathv; + pathv.push_back(hp); + double r = helper_size*.1; + if(lock_length || lock_angle ) { + char const * svgd; + svgd = "M -5.39,8.78 -9.13,5.29 -10.38,10.28 Z M -7.22,7.07 -3.43,3.37 m -1.95,-12.16 -3.74,3.5 -1.26,-5 z m -1.83,1.71 3.78,3.7 M 5.24,8.78 8.98,5.29 10.24,10.28 Z M 7.07,7.07 3.29,3.37 M 5.24,-8.78 l 3.74,3.5 1.26,-5 z M 7.07,-7.07 3.29,-3.37"; + PathVector pathv_move = sp_svg_read_pathv(svgd); + pathv_move *= Affine(r,0,0,r,0,0) * Translate(Geom::Point(start)); + hp_vec.push_back(pathv_move); + } + if(!lock_angle && lock_length) { + char const * svgd; + svgd = "M 0,9.94 C -2.56,9.91 -5.17,8.98 -7.07,7.07 c -3.91,-3.9 -3.91,-10.24 0,-14.14 1.97,-1.97 4.51,-3.02 7.07,-3.04 2.56,0.02 5.1,1.07 7.07,3.04 3.91,3.9 3.91,10.24 0,14.14 C 5.17,8.98 2.56,9.91 0,9.94 Z"; + PathVector pathv_turn = sp_svg_read_pathv(svgd); + pathv_turn *= Geom::Rotate(previous_angle); + pathv_turn *= Affine(r,0,0,r,0,0) * Translate(Geom::Point(end)); + hp_vec.push_back(pathv_turn); + } + hp_vec.push_back(pathv); +} + + +/* ######################## */ + +} //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 : -- cgit v1.2.3