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-roughen.cpp | 568 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 568 insertions(+) create mode 100644 src/live_effects/lpe-roughen.cpp (limited to 'src/live_effects/lpe-roughen.cpp') diff --git a/src/live_effects/lpe-roughen.cpp b/src/live_effects/lpe-roughen.cpp new file mode 100644 index 0000000..c568207 --- /dev/null +++ b/src/live_effects/lpe-roughen.cpp @@ -0,0 +1,568 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** + * @file + * Roughen LPE implementation. Creates roughen paths. + */ +/* Authors: + * Jabier Arraiza Cenoz + * + * Thanks to all people involved specially to Josh Andler for the idea and to the + * original extensions authors. + * + * Copyright (C) 2014 Authors + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include "live_effects/lpe-roughen.h" +#include "display/curve.h" +#include "helper/geom.h" +#include +#include + +// TODO due to internal breakage in glibmm headers, this must be last: +#include + +namespace Inkscape { +namespace LivePathEffect { + +static const Util::EnumData DivisionMethodData[] = { + { DM_SEGMENTS, N_("By number of segments"), "segments" }, + { DM_SIZE, N_("By max. segment size"), "size" } +}; +static const Util::EnumDataConverter DMConverter(DivisionMethodData, DM_END); + +static const Util::EnumData HandlesMethodData[] = { + { HM_ALONG_NODES, N_("Along nodes"), "along" }, + { HM_RAND, N_("Rand"), "rand" }, + { HM_RETRACT, N_("Retract"), "retract" }, + { HM_SMOOTH, N_("Smooth"), "smooth" } +}; +static const Util::EnumDataConverter HMConverter(HandlesMethodData, HM_END); + +LPERoughen::LPERoughen(LivePathEffectObject *lpeobject) + : Effect(lpeobject) + , method(_("Method"), _("Division method"), "method", DMConverter, &wr, this, DM_SIZE) + , max_segment_size(_("Max. segment size"), _("Max. segment size"), "max_segment_size", &wr, this, 10) + , segments(_("Number of segments"), _("Number of segments"), "segments", &wr, this, 2) + , displace_x(_("Max. displacement in X"), _("Max. displacement in X"), "displace_x", &wr, this, 10.) + , displace_y(_("Max. displacement in Y"), _("Max. displacement in Y"), "displace_y", &wr, this, 10.) + , global_randomize(_("Global randomize"), _("Global randomize"), "global_randomize", &wr, this, 1.) + , handles(_("Handles"), _("Handles options"), "handles", HMConverter, &wr, this, HM_ALONG_NODES) + , shift_nodes(_("Shift nodes"), _("Shift nodes"), "shift_nodes", &wr, this, true) + , fixed_displacement(_("Fixed displacement"), _("Fixed displacement, 1/3 of segment length"), "fixed_displacement", + &wr, this, false) + , spray_tool_friendly(_("Spray Tool friendly"), _("For use with spray tool in copy mode"), "spray_tool_friendly", + &wr, this, false) +{ + registerParameter(&method); + registerParameter(&max_segment_size); + registerParameter(&segments); + registerParameter(&displace_x); + registerParameter(&displace_y); + registerParameter(&global_randomize); + registerParameter(&handles); + registerParameter(&shift_nodes); + registerParameter(&fixed_displacement); + registerParameter(&spray_tool_friendly); + displace_x.param_set_range(0., std::numeric_limits::max()); + displace_y.param_set_range(0., std::numeric_limits::max()); + global_randomize.param_set_range(0., std::numeric_limits::max()); + max_segment_size.param_set_range(0., std::numeric_limits::max()); + max_segment_size.param_set_increments(1, 1); + max_segment_size.param_set_digits(3); + segments.param_make_integer(); + segments.param_set_range(1, 9999); + segments.param_set_increments(1, 1); + seed = 0; + apply_to_clippath_and_mask = true; +} + +LPERoughen::~LPERoughen() = default; + +void LPERoughen::doOnApply(SPLPEItem const *lpeitem) +{ + Geom::OptRect bbox = lpeitem->bounds(SPItem::GEOMETRIC_BBOX); + if (bbox) { + std::vector::iterator it = param_vector.begin(); + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + while (it != param_vector.end()) { + Parameter *param = *it; + const gchar *key = param->param_key.c_str(); + Glib::ustring pref_path = (Glib::ustring) "/live_effects/" + + (Glib::ustring)LPETypeConverter.get_key(effectType()).c_str() + + (Glib::ustring) "/" + (Glib::ustring)key; + + + bool valid = prefs->getEntry(pref_path).isValid(); + Glib::ustring displace_x_str = Glib::ustring::format((*bbox).width() / 150.0); + Glib::ustring displace_y_str = Glib::ustring::format((*bbox).height() / 150.0); + Glib::ustring max_segment_size_str = Glib::ustring::format(std::min((*bbox).height(), (*bbox).width()) / 50.0); + if (!valid) { + if (strcmp(key, "max_segment_size") == 0) { + param->param_readSVGValue(max_segment_size_str.c_str()); + } else if (strcmp(key, "displace_x") == 0) { + param->param_readSVGValue(displace_x_str.c_str()); + } else if (strcmp(key, "displace_y") == 0) { + param->param_readSVGValue(displace_y_str.c_str()); + } + } + ++it; + } + } + lpeversion.param_setValue("1.2", true); +} + +void LPERoughen::doBeforeEffect(SPLPEItem const *lpeitem) +{ + if (spray_tool_friendly && seed == 0 && lpeitem->getId()) { + std::string id_item(lpeitem->getId()); + long seed = static_cast(boost::hash_value(id_item)); + global_randomize.param_set_value(global_randomize.get_value(), seed); + } + displace_x.resetRandomizer(); + displace_y.resetRandomizer(); + global_randomize.resetRandomizer(); + if (lpeversion.param_getSVGValue() < "1.1") { + srand(1); + } else { + displace_x.param_set_randomsign(true); + displace_y.param_set_randomsign(true); + } +} + +Gtk::Widget *LPERoughen::newWidget() +{ + Gtk::Box *vbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + vbox->set_border_width(5); + vbox->set_homogeneous(false); + vbox->set_spacing(2); + std::vector::iterator it = param_vector.begin(); + while (it != param_vector.end()) { + if ((*it)->widget_is_visible) { + Parameter *param = *it; + Gtk::Widget *widg = dynamic_cast(param->param_newWidget()); + if (param->param_key == "method") { + Gtk::Label *method_label = Gtk::manage( + new Gtk::Label(Glib::ustring(_("Add nodes Subdivide each segment")), Gtk::ALIGN_START)); + method_label->set_use_markup(true); + vbox->pack_start(*method_label, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)), + Gtk::PACK_EXPAND_WIDGET); + } + if (param->param_key == "displace_x") { + Gtk::Label *displace_x_label = Gtk::manage( + new Gtk::Label(Glib::ustring(_("Jitter nodes Move nodes/handles")), Gtk::ALIGN_START)); + displace_x_label->set_use_markup(true); + vbox->pack_start(*displace_x_label, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)), + Gtk::PACK_EXPAND_WIDGET); + } + if (param->param_key == "global_randomize") { + Gtk::Label *global_rand = Gtk::manage(new Gtk::Label( + Glib::ustring(_("Extra roughen Add an extra layer of rough")), Gtk::ALIGN_START)); + global_rand->set_use_markup(true); + vbox->pack_start(*global_rand, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)), + Gtk::PACK_EXPAND_WIDGET); + } + if (param->param_key == "handles") { + Gtk::Label *options = Gtk::manage( + new Gtk::Label(Glib::ustring(_("Options Modify options to rough")), Gtk::ALIGN_START)); + options->set_use_markup(true); + vbox->pack_start(*options, false, false, 2); + vbox->pack_start(*Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL)), + Gtk::PACK_EXPAND_WIDGET); + } + Glib::ustring *tip = param->param_getTooltip(); + 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; + } + if (Gtk::Widget *widg = defaultParamSet()) { + vbox->pack_start(*widg, true, true, 2); + } + return dynamic_cast(vbox); +} + +double LPERoughen::sign(double random_number) +{ + if (lpeversion.param_getSVGValue() < "1.1") { + if (rand() % 100 < 49) { + random_number *= -1.; + } + } + return random_number; +} + +Geom::Point LPERoughen::randomize(double max_length, bool is_node) +{ + double factor = 1.0 / 3.0; + if (is_node) { + factor = 1.0; + } + double displace_x_parsed = displace_x * global_randomize * factor; + double displace_y_parsed = displace_y * global_randomize * factor; + Geom::Point output = Geom::Point(sign(displace_x_parsed), sign(displace_y_parsed)); + if (fixed_displacement) { + Geom::Ray ray(Geom::Point(0, 0), output); + output = Geom::Point::polar(ray.angle(), max_length); + } + return output; +} + +void LPERoughen::doEffect(SPCurve *curve) +{ + Geom::PathVector const original_pathv = pathv_to_linear_and_cubic_beziers(curve->get_pathvector()); + curve->reset(); + for (const auto &path_it : original_pathv) { + if (path_it.empty()) + continue; + + Geom::Path::const_iterator curve_it1 = path_it.begin(); + Geom::Path::const_iterator curve_it2 = ++(path_it.begin()); + Geom::Path::const_iterator curve_endit = path_it.end_default(); + auto nCurve = std::make_unique(); + Geom::Point prev(0, 0); + Geom::Point last_move(0, 0); + nCurve->moveto(curve_it1->initialPoint()); + if (path_it.closed()) { + const Geom::Curve &closingline = path_it.back_closed(); + // the closing line segment is always of type + // Geom::LineSegment. + if (are_near(closingline.initialPoint(), closingline.finalPoint())) { + // closingline.isDegenerate() did not work, because it only checks for + // *exact* zero length, which goes wrong for relative coordinates and + // rounding errors... + // the closing line segment has zero-length. So stop before that one! + curve_endit = path_it.end_open(); + } + } + while (curve_it1 != curve_endit) { + Geom::CubicBezier const *cubic = nullptr; + cubic = dynamic_cast(&*curve_it1); + if (cubic) { + nCurve->curveto((*cubic)[1] + last_move, (*cubic)[2], curve_it1->finalPoint()); + } else { + nCurve->lineto(curve_it1->finalPoint()); + } + last_move = Geom::Point(0, 0); + double length = curve_it1->length(0.01); + std::size_t splits = 0; + if (method == DM_SEGMENTS) { + splits = segments; + } else { + splits = ceil(length / max_segment_size); + } + auto const original = std::unique_ptr(nCurve->last_segment()->duplicate()); + for (unsigned int t = 1; t <= splits; t++) { + if (t == splits && splits != 1) { + continue; + } + std::unique_ptr tmp; + if (splits == 1) { + tmp = jitter(nCurve->last_segment(), prev, last_move); + } else { + bool last = false; + if (t == splits - 1) { + last = true; + } + double time = + Geom::nearest_time(original->pointAt((1. / (double)splits) * t), *nCurve->last_segment()); + tmp = addNodesAndJitter(nCurve->last_segment(), prev, last_move, time, last); + } + assert(tmp); + if (nCurve->get_segment_count() > 1) { + nCurve->backspace(); + nCurve->append_continuous(*tmp, 0.001); + } else { + nCurve = std::move(tmp); + } + } + ++curve_it1; + ++curve_it2; + } + if (path_it.closed()) { + if (handles == HM_SMOOTH && curve_it1 == curve_endit) { + SPCurve *out = new SPCurve(); + nCurve = nCurve->create_reverse(); + Geom::CubicBezier const *cubic_start = dynamic_cast(nCurve->first_segment()); + Geom::CubicBezier const *cubic = dynamic_cast(nCurve->last_segment()); + Geom::Point oposite = nCurve->first_segment()->pointAt(1.0 / 3.0); + if (cubic_start) { + Geom::Ray ray((*cubic_start)[1], (*cubic_start)[0]); + double dist = Geom::distance((*cubic_start)[1], (*cubic_start)[0]); + oposite = Geom::Point::polar(ray.angle(), dist) + (*cubic_start)[0]; + } + if (cubic) { + out->moveto((*cubic)[0]); + out->curveto((*cubic)[1], oposite, (*cubic)[3]); + } else { + out->moveto(nCurve->last_segment()->initialPoint()); + out->curveto(nCurve->last_segment()->initialPoint(), oposite, nCurve->last_segment()->finalPoint()); + } + nCurve->backspace(); + nCurve->append_continuous(*out, 0.001); + nCurve = nCurve->create_reverse(); + } + if (handles == HM_ALONG_NODES && curve_it1 == curve_endit) { + SPCurve *out = new SPCurve(); + nCurve = nCurve->create_reverse(); + Geom::CubicBezier const *cubic = dynamic_cast(nCurve->last_segment()); + if (cubic) { + out->moveto((*cubic)[0]); + out->curveto((*cubic)[1], (*cubic)[2] - ((*cubic)[3] - nCurve->first_segment()->initialPoint()), + (*cubic)[3]); + nCurve->backspace(); + nCurve->append_continuous(*out, 0.001); + } + nCurve = nCurve->create_reverse(); + } + nCurve->move_endpoints(nCurve->last_segment()->finalPoint(), nCurve->last_segment()->finalPoint()); + nCurve->closepath_current(); + } + curve->append(*nCurve); + } +} + +std::unique_ptr LPERoughen::addNodesAndJitter(Geom::Curve const *A, Geom::Point &prev, Geom::Point &last_move, double t, + bool last) +{ + auto out = std::make_unique(); + Geom::CubicBezier const *cubic = dynamic_cast(&*A); + double max_length = Geom::distance(A->initialPoint(), A->pointAt(t)) / 3.0; + Geom::Point point_a1(0, 0); + Geom::Point point_a2(0, 0); + Geom::Point point_a3(0, 0); + Geom::Point point_b1(0, 0); + Geom::Point point_b2(0, 0); + Geom::Point point_b3(0, 0); + if (shift_nodes) { + point_a3 = randomize(max_length, true); + if (last) { + point_b3 = randomize(max_length, true); + } + } + if (handles == HM_RAND || handles == HM_SMOOTH) { + point_a1 = randomize(max_length); + point_a2 = randomize(max_length); + point_b1 = randomize(max_length); + if (last) { + point_b2 = randomize(max_length); + } + } else { + point_a2 = point_a3; + point_b1 = point_a3; + if (last) { + point_b2 = point_b3; + } + } + if (handles == HM_SMOOTH) { + if (cubic) { + std::pair div = cubic->subdivide(t); + std::vector seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints(); + Geom::Ray ray(seg1[3] + point_a3, seg2[1] + point_a3); + double length = max_length; + if (!fixed_displacement) { + length = Geom::distance(seg1[3] + point_a3, seg2[1] + point_a3); + } + point_b1 = seg1[3] + point_a3 + Geom::Point::polar(ray.angle(), length); + point_b2 = seg2[2]; + point_b3 = seg2[3] + point_b3; + point_a3 = seg1[3] + point_a3; + ray.setPoints(prev, A->initialPoint()); + point_a1 = A->initialPoint() + Geom::Point::polar(ray.angle(), max_length); + if (last) { + Geom::Path b2(point_b3); + b2.appendNew(point_a3); + length = max_length; + ray.setPoints(point_b3, point_b2); + if (!fixed_displacement) { + length = Geom::distance(b2.pointAt(1.0 / 3.0), point_b3); + } + point_b2 = point_b3 + Geom::Point::polar(ray.angle(), length); + } + ray.setPoints(point_b1, point_a3); + point_a2 = point_a3 + Geom::Point::polar(ray.angle(), max_length); + if (last) { + prev = point_b2; + } else { + prev = point_a2; + } + out->moveto(seg1[0]); + out->curveto(point_a1, point_a2, point_a3); + out->curveto(point_b1, point_b2, point_b3); + } else { + Geom::Ray ray(A->pointAt(t) + point_a3, A->pointAt(t + (t / 3))); + double length = max_length; + if (!fixed_displacement) { + length = Geom::distance(A->pointAt(t) + point_a3, A->pointAt(t + (t / 3))); + } + point_b1 = A->pointAt(t) + point_a3 + Geom::Point::polar(ray.angle(), length); + point_b2 = A->pointAt(t + ((t / 3) * 2)); + point_b3 = A->finalPoint() + point_b3; + point_a3 = A->pointAt(t) + point_a3; + ray.setPoints(prev, A->initialPoint()); + point_a1 = A->initialPoint() + Geom::Point::polar(ray.angle(), max_length); + if (prev == Geom::Point(0, 0)) { + point_a1 = randomize(max_length); + } + if (last) { + Geom::Path b2(point_b3); + b2.appendNew(point_a3); + length = max_length; + ray.setPoints(point_b3, point_b2); + if (!fixed_displacement) { + length = Geom::distance(b2.pointAt(1.0 / 3.0), point_b3); + } + point_b2 = point_b3 + Geom::Point::polar(ray.angle(), length); + } + ray.setPoints(point_b1, point_a3); + point_a2 = point_a3 + Geom::Point::polar(ray.angle(), max_length); + if (last) { + prev = point_b2; + } else { + prev = point_a2; + } + out->moveto(A->initialPoint()); + out->curveto(point_a1, point_a2, point_a3); + out->curveto(point_b1, point_b2, point_b3); + } + } else if (handles == HM_RETRACT) { + out->moveto(A->initialPoint()); + out->lineto(A->pointAt(t) + point_a3); + if (cubic && !last) { + std::pair div = cubic->subdivide(t); + std::vector seg2 = div.second.controlPoints(); + out->curveto(seg2[1], seg2[2], seg2[3]); + } else { + out->lineto(A->finalPoint() + point_b3); + } + } else if (handles == HM_ALONG_NODES) { + if (cubic) { + std::pair div = cubic->subdivide(t); + std::vector seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints(); + out->moveto(seg1[0]); + out->curveto(seg1[1] + last_move, seg1[2] + point_a3, seg1[3] + point_a3); + last_move = point_a3; + if (last) { + last_move = point_b3; + } + out->curveto(seg2[1] + point_a3, seg2[2] + point_b3, seg2[3] + point_b3); + } else { + out->moveto(A->initialPoint()); + out->lineto(A->pointAt(t) + point_a3); + out->lineto(A->finalPoint() + point_b3); + } + } else if (handles == HM_RAND) { + if (cubic) { + std::pair div = cubic->subdivide(t); + std::vector seg1 = div.first.controlPoints(), seg2 = div.second.controlPoints(); + out->moveto(seg1[0]); + out->curveto(seg1[1] + point_a1, seg1[2] + point_a2 + point_a3, seg1[3] + point_a3); + out->curveto(seg2[1] + point_a3 + point_b1, seg2[2] + point_b2 + point_b3, seg2[3] + point_b3); + } else { + out->moveto(A->initialPoint()); + out->lineto(A->pointAt(t) + point_a3); + out->lineto(A->finalPoint() + point_b3); + } + } + return out; +} + +std::unique_ptr LPERoughen::jitter(Geom::Curve const *A, Geom::Point &prev, Geom::Point &last_move) +{ + auto out = std::make_unique(); + Geom::CubicBezier const *cubic = dynamic_cast(&*A); + double max_length = Geom::distance(A->initialPoint(), A->finalPoint()) / 3.0; + Geom::Point point_a1(0, 0); + Geom::Point point_a2(0, 0); + Geom::Point point_a3(0, 0); + if (shift_nodes) { + point_a3 = randomize(max_length, true); + } + if (handles == HM_RAND || handles == HM_SMOOTH) { + point_a1 = randomize(max_length); + point_a2 = randomize(max_length); + } + if (handles == HM_SMOOTH) { + if (cubic) { + Geom::Ray ray(prev, A->initialPoint()); + point_a1 = Geom::Point::polar(ray.angle(), max_length); + if (prev == Geom::Point(0, 0)) { + point_a1 = A->pointAt(1.0 / 3.0) + randomize(max_length); + } + ray.setPoints((*cubic)[3] + point_a3, (*cubic)[2] + point_a3); + if (lpeversion.param_getSVGValue() < "1.1") { + point_a2 = randomize(max_length, ray.angle()); + } else { + point_a2 = randomize(max_length, false); + } + prev = (*cubic)[2] + point_a2; + out->moveto((*cubic)[0]); + out->curveto((*cubic)[0] + point_a1, (*cubic)[2] + point_a2 + point_a3, (*cubic)[3] + point_a3); + } else { + Geom::Ray ray(prev, A->initialPoint()); + point_a1 = Geom::Point::polar(ray.angle(), max_length); + if (prev == Geom::Point(0, 0)) { + point_a1 = A->pointAt(1.0 / 3.0) + randomize(max_length); + } + ray.setPoints(A->finalPoint() + point_a3, A->pointAt((1.0 / 3.0) * 2) + point_a3); + if (lpeversion.param_getSVGValue() < "1.1") { + point_a2 = randomize(max_length, ray.angle()); + } else { + point_a2 = randomize(max_length, false); + } + prev = A->pointAt((1.0 / 3.0) * 2) + point_a2 + point_a3; + out->moveto(A->initialPoint()); + out->curveto(A->initialPoint() + point_a1, A->pointAt((1.0 / 3.0) * 2) + point_a2 + point_a3, + A->finalPoint() + point_a3); + } + } else if (handles == HM_RETRACT) { + out->moveto(A->initialPoint()); + out->lineto(A->finalPoint() + point_a3); + } else if (handles == HM_ALONG_NODES) { + if (cubic) { + out->moveto((*cubic)[0]); + out->curveto((*cubic)[1] + last_move, (*cubic)[2] + point_a3, (*cubic)[3] + point_a3); + last_move = point_a3; + } else { + out->moveto(A->initialPoint()); + out->lineto(A->finalPoint() + point_a3); + } + } else if (handles == HM_RAND) { + out->moveto(A->initialPoint()); + out->curveto(A->pointAt(0.3333) + point_a1, A->pointAt(0.6666) + point_a2 + point_a3, + A->finalPoint() + point_a3); + } + return out; +} + +Geom::Point LPERoughen::tPoint(Geom::Point A, Geom::Point B, double t) +{ + using Geom::X; + using Geom::Y; + return Geom::Point(A[X] + t * (B[X] - A[X]), A[Y] + t * (B[Y] - A[Y])); +} + +}; // 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