Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
pencil-tool.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/*
7 * Authors:
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * bulia byak <buliabyak@users.sf.net>
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 2000 Lauris Kaplinski
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 * Copyright (C) 2002 Lauris Kaplinski
15 * Copyright (C) 2004 Monash University
16 *
17 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
18 */
19
20#include "pencil-tool.h"
21
22#include <cmath> // std::lerp
23#include <numeric> // std::accumulate
24
25#include <gdk/gdkkeysyms.h>
26#include <glibmm/i18n.h>
27
28#include <2geom/bezier-utils.h>
29#include <2geom/circle.h>
32
33
34#include "context-fns.h"
35#include "desktop.h"
36#include "desktop-style.h"
37#include "layer-manager.h"
38#include "message-context.h"
39#include "message-stack.h"
40#include "selection-chemistry.h"
41#include "selection.h"
42#include "snap.h"
43
44#include "display/curve.h"
47
48#include "livarot/Path.h" // Simplify paths
49
54
55#include "object/sp-lpe-item.h"
56#include "object/sp-path.h"
57#include "path/path-boolop.h"
58#include "style.h"
59
60#include "svg/svg.h"
61
62#include "ui/draw-anchor.h"
64
65#include "xml/node.h"
66#include "xml/sp-css-attr.h"
67
68#define DDC_MIN_PRESSURE 0.0
69#define DDC_MAX_PRESSURE 1.0
70#define DDC_DEFAULT_PRESSURE 1.0
71
72namespace Inkscape::UI::Tools {
73
75static bool pencil_within_tolerance = false;
76
77static bool in_svg_plane(Geom::Point const &p) { return Geom::LInfty(p) < 1e18; }
78
80 : FreehandBase(desktop, "/tools/freehand/pencil", "pencil.svg")
81{
83 if (prefs->getBool("/tools/freehand/pencil/selcue")) {
84 this->enableSelectionCue();
85 }
86 this->_is_drawing = false;
87 this->anchor_statusbar = false;
88}
89
90PencilTool::~PencilTool() = default;
91
93{
94 if (ext.pressure) {
96 is_tablet = true;
97 } else {
99 is_tablet = false;
100 }
101}
102
104void PencilTool::_endpointSnap(Geom::Point &p, guint const state) {
105 if ((state & GDK_CONTROL_MASK)) { //CTRL enables constrained snapping
106 if (this->_npoints > 0) {
107 spdc_endpoint_snap_rotation(this, p, p_array[0], state);
108 }
109 } else {
110 if (!(state & GDK_SHIFT_MASK)) { //SHIFT disables all snapping, except the angular snapping above
111 //After all, the user explicitly asked for angular snapping by
112 //pressing CTRL
113 std::optional<Geom::Point> origin = this->_npoints > 0 ? p_array[0] : std::optional<Geom::Point>();
115 } else {
117 }
118 }
119}
120
125{
126 bool ret = false;
127
128 inspect_event(event,
129 [&] (ButtonPressEvent const &event) {
130 _extinput(event.extinput);
131 ret = _handleButtonPress(event);
132 },
133 [&] (MotionEvent const &event) {
134 _extinput(event.extinput);
135 ret = _handleMotionNotify(event);
136 },
137 [&] (ButtonReleaseEvent const &event) {
138 ret = _handleButtonRelease(event);
139 },
140 [&] (KeyPressEvent const &event) {
141 ret = _handleKeyPress(event);
142 },
143 [&] (KeyReleaseEvent const &event) {
144 ret = _handleKeyRelease(event);
145 },
146 [&] (CanvasEvent const &event) {}
147 );
148
149 return ret || FreehandBase::root_handler(event);
150}
151
153{
154 bool ret = false;
155 if (event.num_press == 1 && event.button == 1) {
157
159 return true;
160 }
161
162 /* Grab mouse, so release will not pass unnoticed */
164
165 /* Find desktop coordinates */
166 Geom::Point p = _desktop->w2d(event.pos);
167
168 /* Test whether we hit any anchor. */
169 SPDrawAnchor *anchor = spdc_test_inside(this, event.pos);
170 if (tablet_enabled) {
171 anchor = nullptr;
172 }
173 pencil_drag_origin_w = event.pos;
176 tablet_enabled = prefs->getBool("/tools/freehand/pencil/pressure", false);
177
178 switch (this->_state) {
180 /* Current segment will be finished with release */
181 ret = true;
182 break;
183 default:
184 /* Set first point of sequence */
185 auto &m = _desktop->getNamedView()->snap_manager;
186 if (event.modifiers & GDK_CONTROL_MASK) {
187 m.setup(_desktop, true);
188 if (!(event.modifiers & GDK_SHIFT_MASK)) {
189 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
190 }
191 spdc_create_single_dot(this, p, "/tools/freehand/pencil", event.modifiers);
192 m.unSetup();
193 ret = true;
194 break;
195 }
196 if (anchor) {
197 p = anchor->dp;
198 //Put the start overwrite curve always on the same direction
199 if (anchor->start) {
200 sa_overwrited = std::make_shared<SPCurve>(anchor->curve->reversed());
201 } else {
202 sa_overwrited = std::make_shared<SPCurve>(*anchor->curve);
203 }
204 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Continuing selected path"));
205 } else {
206 m.setup(_desktop, true);
207 if (tablet_enabled) {
208 // This is the first click of a new curve; deselect item so that
209 // this curve is not combined with it (unless it is drawn from its
210 // anchor, which is handled by the sibling branch above)
211 selection->clear();
212 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
213 } else if (!(event.modifiers & GDK_SHIFT_MASK)) {
214 // This is the first click of a new curve; deselect item so that
215 // this curve is not combined with it (unless it is drawn from its
216 // anchor, which is handled by the sibling branch above)
217 selection->clear();
218 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Creating new path"));
219 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
220 } else if (selection->singleItem() && is<SPPath>(selection->singleItem())) {
221 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Appending to selected path"));
222 m.freeSnapReturnByRef(p, Inkscape::SNAPSOURCE_NODE_HANDLE);
223 }
224 m.unSetup();
225 }
226 if (!tablet_enabled) {
227 sa = anchor;
228 }
230 ret = true;
231 break;
232 }
233
235 _is_drawing = true;
236 }
237 return ret;
238}
239
241 if ((event.modifiers & GDK_CONTROL_MASK) && (event.modifiers & GDK_BUTTON1_MASK)) {
242 // mouse was accidentally moved during Ctrl+click;
243 // ignore the motion and create a single point
244 _is_drawing = false;
245 return true;
246 }
247
248 if ((event.modifiers & GDK_BUTTON2_MASK)) {
249 // allow scrolling
250 return false;
251 }
252
253 /* Test whether we hit any anchor. */
255 if (this->pressure == 0.0 && tablet_enabled && !anchor) {
256 // tablet event was accidentally fired without press;
257 return false;
258 }
259
260 if ( ( event.modifiers & GDK_BUTTON1_MASK ) && this->_is_drawing) {
261 /* Grab mouse, so release will not pass unnoticed */
263 }
264
265 /* Find desktop coordinates */
266 Geom::Point p = _desktop->w2d(event.pos);
267
270 gint const tolerance = prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
272 return false; // Do not drag if we're within tolerance from origin.
273 }
274 }
275
276 // Once the user has moved farther than tolerance from the original location
277 // (indicating they intend to move the object, not click), then always process the
278 // motion notify coordinates as given (no snapping back to origin)
280
281 anchor = spdc_test_inside(this, event.pos);
282
283 bool ret = false;
284
285 switch (this->_state) {
287 if (is_tablet) {
289 return false;
290 }
291 /* Set red endpoint */
292 if (anchor) {
293 p = anchor->dp;
294 } else {
295 Geom::Point ptnr(p);
296 _endpointSnap(ptnr, event.modifiers);
297 p = ptnr;
298 }
299 _setEndpoint(p);
300 ret = true;
301 break;
302 default:
303 /* We may be idle or already freehand */
304 if ( (event.modifiers & GDK_BUTTON1_MASK) && _is_drawing ) {
307 }
309
310 if ( !sa && !green_anchor ) {
311 /* Create green anchor */
312 green_anchor = std::make_unique<SPDrawAnchor>(this, green_curve, true, p_array[0]);
313 }
314 if (anchor) {
315 p = anchor->dp;
316 }
317 if ( _npoints != 0) { // buttonpress may have happened before we entered draw context!
318 if (ps.empty()) {
319 // Only in freehand mode we have to add the first point also to ps (apparently)
320 // - We cannot add this point in spdc_set_startpoint, because we only need it for freehand
321 // - We cannot do this in the button press handler because at that point we don't know yet
322 // whether we're going into freehand mode or not
323 ps.push_back(p_array[0]);
324 if (tablet_enabled) {
325 _wps.emplace_back(0, 0);
326 }
327 }
328 _addFreehandPoint(p, event.modifiers, false);
329 ret = true;
330 }
331 if (anchor && !anchor_statusbar) {
332 message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Release</b> here to close and finish the path."));
333 anchor_statusbar = true;
334 ea = anchor;
335 } else if (!anchor && anchor_statusbar) {
336 message_context->clear();
337 anchor_statusbar = false;
338 ea = nullptr;
339 } else if (!anchor) {
340 message_context->set(Inkscape::NORMAL_MESSAGE, _("Drawing a freehand path"));
341 ea = nullptr;
342 }
343
344 } else {
345 if (anchor && !anchor_statusbar) {
346 message_context->set(Inkscape::NORMAL_MESSAGE, _("<b>Drag</b> to continue the path from this point."));
347 anchor_statusbar = true;
348 } else if (!anchor && anchor_statusbar) {
349 message_context->clear();
350 anchor_statusbar = false;
351 }
352 }
353
354 // Show the pre-snap indicator to communicate to the user where we would snap to if he/she were to
355 // a) press the mousebutton to start a freehand drawing, or
356 // b) release the mousebutton to finish a freehand drawing
359 m.setup(_desktop, true);
361 m.unSetup();
362 }
363 break;
364 }
365 return ret;
366}
367
369
370 bool ret = false;
371
373
374 if (event.button == 1 && _is_drawing) {
375 _is_drawing = false;
376
377 /* Find desktop coordinates */
378 Geom::Point p = _desktop->w2d(event.pos);
379
380 /* Test whether we hit any anchor. */
381 SPDrawAnchor *anchor = spdc_test_inside(this, event.pos);
382
383 switch (_state) {
385 /* Releasing button in idle mode means single click */
386 /* We have already set up start point/anchor in button_press */
387 if (!(event.modifiers & GDK_CONTROL_MASK) && !is_tablet) {
388 // Ctrl+click creates a single point so only set context in ADDLINE mode when Ctrl isn't pressed
390 }
391 /*Or select the down item if we are in tablet mode*/
392 if (is_tablet) {
393 using namespace Inkscape::LivePathEffect;
394 SPItem *item = sp_event_context_find_item(_desktop, event.pos, false, false);
395 if (item && (!white_item || item != white_item)) {
396 if (is<SPLPEItem>(item)) {
397 Effect* lpe = cast<SPLPEItem>(item)->getCurrentLPE();
398 if (lpe) {
399 LPEPowerStroke* ps = static_cast<LPEPowerStroke*>(lpe);
400 if (ps) {
403 }
404 }
405 }
406 }
407 }
408 break;
410 /* Finish segment now */
411 if (anchor) {
412 p = anchor->dp;
413 } else {
414 _endpointSnap(p, event.modifiers);
415 }
416 ea = anchor;
417 _setEndpoint(p);
421 break;
423 if (event.modifiers & GDK_ALT_MASK && !tablet_enabled) {
424 /* sketch mode: interpolate the sketched path and improve the current output path with the new interpolation. don't finish sketch */
426
427 green_anchor.reset();
428
430 } else {
431 /* Finish segment now */
433 if (anchor) {
434 p = anchor->dp;
435 } else {
436 Geom::Point p_end = p;
437 if (tablet_enabled) {
438 _addFreehandPoint(p_end, event.modifiers, true);
440 } else {
441 _endpointSnap(p_end, event.modifiers);
442 if (p_end != p) {
443 // then we must have snapped!
444 _addFreehandPoint(p_end, event.modifiers, true);
445 }
446 }
447 }
448
449 ea = anchor;
450 /* Write curves to object */
451 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand"));
452 _interpolate();
454 if (tablet_enabled) {
455 gint shapetype = prefs->getInt("/tools/freehand/pencil/shape", 0);
456 gint simplify = prefs->getInt("/tools/freehand/pencil/simplify", 0);
457 gint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
458 prefs->setInt("/tools/freehand/pencil/shape", 0);
459 prefs->setInt("/tools/freehand/pencil/simplify", 0);
460 prefs->setInt("/tools/freehand/pencil/freehand-mode", 0);
461 spdc_concat_colors_and_flush(this, false);
462 prefs->setInt("/tools/freehand/pencil/freehand-mode", mode);
463 prefs->setInt("/tools/freehand/pencil/simplify", simplify);
464 prefs->setInt("/tools/freehand/pencil/shape", shapetype);
465 } else {
466 spdc_concat_colors_and_flush(this, false);
467 }
468 points.clear();
469 sa = nullptr;
470 ea = nullptr;
471 ps.clear();
472 _wps.clear();
473 green_anchor.reset();
475 // reset sketch mode too
476 sketch_n = 0;
477 }
478 break;
480 default:
481 break;
482 }
483
485
486 ret = true;
487 }
488 return ret;
489}
490
493
494 _is_drawing = false;
497
499 red_bpath->set_bpath(&red_curve);
500
501 green_bpaths.clear();
502 green_curve->reset();
503 green_anchor.reset();
504
505 message_context->clear();
506 message_context->flash(Inkscape::NORMAL_MESSAGE, _("Drawing cancelled"));
507}
508
510 bool ret = false;
511
512 switch (get_latin_keyval(event)) {
513 case GDK_KEY_Up:
514 case GDK_KEY_Down:
515 case GDK_KEY_KP_Up:
516 case GDK_KEY_KP_Down:
517 // Prevent the zoom field from activation.
518 if (!state_held_only_ctrl(event.modifiers)) {
519 ret = true;
520 }
521 break;
522 case GDK_KEY_Escape:
523 if (_npoints != 0) {
524 // if drawing, cancel, otherwise pass it up for deselecting
526 _cancel();
527 ret = true;
528 }
529 }
530 break;
531 case GDK_KEY_z:
532 case GDK_KEY_Z:
533 if (state_held_only_ctrl(event.modifiers) && _npoints != 0) {
534 // if drawing, cancel, otherwise pass it up for undo
536 _cancel();
537 ret = true;
538 }
539 }
540 break;
541 case GDK_KEY_g:
542 case GDK_KEY_G:
543 if (state_held_only_shift(event.modifiers)) {
545 ret = true;
546 }
547 break;
548 case GDK_KEY_Alt_L:
549 case GDK_KEY_Alt_R:
550 case GDK_KEY_Meta_L:
551 case GDK_KEY_Meta_R:
553 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("<b>Sketch mode</b>: holding <b>Alt</b> interpolates between sketched paths. Release <b>Alt</b> to finalize."));
554 }
555 break;
556 default:
557 break;
558 }
559 return ret;
560}
561
563 bool ret = false;
564
565 switch (get_latin_keyval(event)) {
566 case GDK_KEY_Alt_L:
567 case GDK_KEY_Alt_R:
568 case GDK_KEY_Meta_L:
569 case GDK_KEY_Meta_R:
571 spdc_concat_colors_and_flush(this, false);
572 sketch_n = 0;
573 sa = nullptr;
574 ea = nullptr;
575 green_anchor.reset();
578 _desktop->messageStack()->flash(Inkscape::NORMAL_MESSAGE, _("Finishing freehand sketch"));
579 ret = true;
580 }
581 break;
582 default:
583 break;
584 }
585 return ret;
586}
587
592 _npoints = 0;
593 red_curve_is_valid = false;
594 if (in_svg_plane(p)) {
595 p_array[_npoints++] = p;
596 }
597}
598
610 if (_npoints == 0) {
611 return;
612 /* May occur if first point wasn't in SVG plane (e.g. weird w2d transform, perhaps from bad
613 * zoom setting).
614 */
615 }
616 g_return_if_fail( this->_npoints > 0 );
617
619 if ( ( p == p_array[0] )
620 || !in_svg_plane(p) )
621 {
622 _npoints = 1;
623 } else {
624 p_array[1] = p;
625 _npoints = 2;
626
629 red_curve_is_valid = true;
630 if (!tablet_enabled) {
631 red_bpath->set_bpath(&red_curve);
632 }
633 }
634}
635
644 if (this->red_curve.is_unset() ||
645 this->red_curve.first_point() == this->red_curve.second_point())
646 {
647 this->red_curve.reset();
648 if (!tablet_enabled) {
649 red_bpath->set_bpath(nullptr);
650 }
651 } else {
652 /* Write curves to object. */
653 spdc_concat_colors_and_flush(this, false);
654 this->sa = nullptr;
655 this->ea = nullptr;
656 }
657}
658
659static inline double square(double const x) { return x * x; }
660
661
662
664{
665 {
666 SPDocument *document = _desktop->doc();
667 if (!document) {
668 return;
669 }
670 using namespace Inkscape::LivePathEffect;
672 double tol = prefs->getDoubleLimited("/tools/freehand/pencil/base-simplify", 25.0, 0.0, 100.0) * 0.4;
673 double tolerance_sq = 0.02 * square(_desktop->w2d().descrim() * tol) * exp(0.2 * tol - 2);
674 int n_points = this->ps.size();
675 // worst case gives us a segment per point
676 int max_segs = 4 * n_points;
677 std::vector<Geom::Point> b(max_segs);
678 SPCurve curvepressure;
679 int const n_segs = Geom::bezier_fit_cubic_r(b.data(), this->ps.data(), n_points, tolerance_sq, max_segs);
680 if (n_segs > 0) {
681 /* Fit and draw and reset state */
682 curvepressure.moveto(b[0]);
683 for (int c = 0; c < n_segs; c++) {
684 curvepressure.curveto(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
685 }
686 }
687 curvepressure.transform(currentLayer()->i2dt_affine().inverse());
688 Geom::Path path = curvepressure.get_pathvector()[0];
689
690 if (!path.empty()) {
691 Inkscape::XML::Document *xml_doc = document->getReprDoc();
692 Inkscape::XML::Node *pp = nullptr;
693 pp = xml_doc->createElement("svg:path");
694 pp->setAttribute("d", sp_svg_write_path(path));
695 pp->setAttribute("id", "power_stroke_preview");
697
698 auto powerpreview = cast<SPShape>(currentLayer()->appendChildRepr(pp));
699 auto lpeitem = powerpreview;
700 if (!lpeitem) {
701 return;
702 }
704 tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 0.0, 100.0) + 30;
705 if (tol > 30) {
706 tol = tol / (130.0 * (132.0 - tol));
708 threshold << tol;
709 Effect::createAndApply(SIMPLIFY, document, lpeitem);
710 Effect *lpe = lpeitem->getCurrentLPE();
712 static_cast<Inkscape::LivePathEffect::LPESimplify *>(lpe);
713 if (simplify) {
714 sp_lpe_item_enable_path_effects(lpeitem, false);
715 Glib::ustring pref_path = "/live_effects/simplify/smooth_angles";
716 bool valid = prefs->getEntry(pref_path).isValidDouble();
717 if (!valid) {
718 lpe->getRepr()->setAttribute("smooth_angles", "0");
719 }
720 pref_path = "/live_effects/simplify/helper_size";
721 valid = prefs->getEntry(pref_path).isValidDouble();
722 if (!valid) {
723 lpe->getRepr()->setAttribute("helper_size", "0");
724 }
725 pref_path = "/live_effects/simplify/step";
726 valid = prefs->getEntry(pref_path).isValidDouble();
727 if (!valid) {
728 lpe->getRepr()->setAttribute("step", "1");
729 }
730 lpe->getRepr()->setAttribute("threshold", threshold.str());
731 lpe->getRepr()->setAttribute("simplify_individual_paths", "false");
732 lpe->getRepr()->setAttribute("simplify_just_coalesce", "false");
733 sp_lpe_item_enable_path_effects(lpeitem, true);
734 }
735 sp_lpe_item_update_patheffect(lpeitem, false, true);
736 SPCurve const *curvepressure = powerpreview->curve();
737 if (curvepressure->is_empty()) {
738 return;
739 }
740 path = curvepressure->get_pathvector()[0];
741 }
744 Glib::ustring pref_path_pp = "/live_effects/powerstroke/powerpencil";
745 prefs->setBool(pref_path_pp, true);
746 Effect::createAndApply(POWERSTROKE, document, lpeitem);
747 Effect *lpe = lpeitem->getCurrentLPE();
748 Inkscape::LivePathEffect::LPEPowerStroke *pspreview = static_cast<LPEPowerStroke *>(lpe);
749 if (pspreview) {
750 sp_lpe_item_enable_path_effects(lpeitem, false);
751 Glib::ustring pref_path = "/live_effects/powerstroke/interpolator_type";
752 bool valid = prefs->getEntry(pref_path).isValidString();
753 if (!valid) {
754 pspreview->getRepr()->setAttribute("interpolator_type", "CentripetalCatmullRom");
755 }
756 pref_path = "/live_effects/powerstroke/linejoin_type";
757 valid = prefs->getEntry(pref_path).isValidString();
758 if (!valid) {
759 pspreview->getRepr()->setAttribute("linejoin_type", "spiro");
760 }
761 pref_path = "/live_effects/powerstroke/interpolator_beta";
762 valid = prefs->getEntry(pref_path).isValidDouble();
763 if (!valid) {
764 pspreview->getRepr()->setAttribute("interpolator_beta", "0.75");
765 }
766 gint cap = prefs->getInt("/live_effects/powerstroke/powerpencilcap", 2);
767 pspreview->getRepr()->setAttribute("start_linecap_type", LineCapTypeConverter.get_key(cap));
768 pspreview->getRepr()->setAttribute("end_linecap_type", LineCapTypeConverter.get_key(cap));
769 pspreview->getRepr()->setAttribute("sort_points", "true");
770 pspreview->getRepr()->setAttribute("not_jump", "true");
772 sp_lpe_item_enable_path_effects(lpeitem, true);
773 sp_lpe_item_update_patheffect(lpeitem, false, true);
774 pp->setAttribute("style", "fill:#888888;opacity:1;fill-rule:nonzero;stroke:none;");
775 }
776 prefs->setBool(pref_path_pp, false);
777 }
778 }
779}
780
788void PencilTool::_addFreehandPoint(Geom::Point const &p, guint /*state*/, bool last)
789{
790 g_assert(_npoints > 0 );
791 g_return_if_fail(unsigned(_npoints) < G_N_ELEMENTS(p_array));
792
793 double distance = 0;
794 if ( ( p != p_array[_npoints - 1 ] )
795 && in_svg_plane(p) )
796 {
797 p_array[_npoints++] = p;
798 this->_fitAndSplit();
799 if (tablet_enabled) {
800 distance = Geom::distance(p, this->ps.back()) + this->_wps.back()[Geom::X];
801 }
802 this->ps.push_back(p);
803 }
804 if (tablet_enabled && in_svg_plane(p)) {
806 double min = prefs->getIntLimited("/tools/freehand/pencil/minpressure", 0, 0, 100) / 100.0;
807 double max = prefs->getIntLimited("/tools/freehand/pencil/maxpressure", 30, 0, 100) / 100.0;
808 if (min > max) {
809 min = max;
810 }
811 double dezoomify_factor = 0.05 * 1000 / _desktop->current_zoom();
812 double const pressure_shrunk = std::lerp(min, max, pressure);
813 double pressure_computed = std::abs(pressure_shrunk * dezoomify_factor);
814 double pressure_computed_scaled = std::abs(pressure_computed * _desktop->getDocument()->getDocumentScale().inverse()[Geom::X]);
815 if (p != p_array[_npoints - 1]) {
816 _wps.emplace_back(distance, pressure_computed_scaled);
817 }
818 if (pressure_computed) {
819 Geom::Circle pressure_dot(p, pressure_computed);
820 Geom::Piecewise<Geom::D2<Geom::SBasis>> pressure_piecewise;
821 pressure_piecewise.push_cut(0);
822 pressure_piecewise.push(pressure_dot.toSBasis(), 1);
823 Geom::PathVector pressure_path = Geom::path_from_piecewise(pressure_piecewise, 0.1);
825 if (!pressure_path.empty() && !previous_presure.empty()) {
826 pressure_path = sp_pathvector_boolop(pressure_path, previous_presure, bool_op_union, fill_nonZero, fill_nonZero);
827 }
828 _pressure_curve = SPCurve(std::move(pressure_path));
829 red_bpath->set_bpath(&_pressure_curve);
830 }
831 if (last) {
832 this->addPowerStrokePencil();
833 }
834 }
835}
836
838{
839 size_t ps_size = this->ps.size();
840 if ( ps_size <= 1 ) {
841 return;
842 }
843
844 using Geom::X;
845 using Geom::Y;
846 gint path_size = path.size();
847 std::vector<Geom::Point> tmp_points;
848 Geom::Point previous = Geom::Point(Geom::infinity(), 0);
849 bool increase = false;
850 size_t i = 0;
851 double dezoomify_factor = 0.05 * 1000 / _desktop->current_zoom();
852 double limit = 6 * dezoomify_factor;
853 double max =
854 std::max(this->_wps.back()[Geom::X] - (this->_wps.back()[Geom::X] / 10), this->_wps.back()[Geom::X] - limit);
855 double min = std::min(this->_wps.back()[Geom::X] / 10, limit);
856 double original_lenght = this->_wps.back()[Geom::X];
857 double max10 = 0;
858 double min10 = 0;
859 for (auto wps : this->_wps) {
860 i++;
862 max10 = max10 > pressure ? max10 : pressure;
863 min10 = min10 <= pressure ? min10 : pressure;
864 if (!original_lenght || wps[Geom::X] > max) {
865 break;
866 }
867 if (wps[Geom::Y] == 0 || wps[Geom::X] < min) {
868 continue;
869 }
870 if (previous[Geom::Y] < (max10 + min10) / 2.0) {
871 if (increase && tmp_points.size() > 1) {
872 tmp_points.pop_back();
873 }
874 wps[Geom::Y] = max10;
875 tmp_points.push_back(wps);
876 increase = true;
877 } else {
878 if (!increase && tmp_points.size() > 1) {
879 tmp_points.pop_back();
880 }
881 wps[Geom::Y] = min10;
882 tmp_points.push_back(wps);
883 increase = false;
884 }
885 previous = wps;
886 max10 = 0;
887 min10 = 999999999;
888 }
889 this->points.clear();
890 double prev_pressure = 0;
891 for (auto point : tmp_points) {
892 point[Geom::X] /= (double)original_lenght;
893 point[Geom::X] *= path_size;
894 if (std::abs(point[Geom::Y] - prev_pressure) > point[Geom::Y] / 10.0) {
895 this->points.push_back(point);
896 prev_pressure = point[Geom::Y];
897 }
898 }
899 if (points.empty() && !_wps.empty()) {
900 // Synthesize a pressure data point based on the average pressure
901 double average_pressure = std::accumulate(_wps.begin(), _wps.end(), 0.0,
902 [](double const &sum_so_far, Geom::Point const &point) -> double {
903 return sum_so_far + point[Geom::Y];
904 }) / (double)_wps.size();
905 points.emplace_back(0.5 * path.size(), /* place halfway along the path */
906 2.0 * average_pressure /* 2.0 - for correct average thickness of a kite */);
907 }
908}
909
911 size_t ps_size = this->ps.size();
912 if ( ps_size <= 1 ) {
913 return;
914 }
915 using Geom::X;
916 using Geom::Y;
918 double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 0.0, 100.0) * 0.4;
919 bool simplify = prefs->getInt("/tools/freehand/pencil/simplify", 0);
920 if(simplify){
921 double tol2 = prefs->getDoubleLimited("/tools/freehand/pencil/base-simplify", 25.0, 0.0, 100.0) * 0.4;
922 tol = std::min(tol,tol2);
923 }
924 this->green_curve->reset();
925 this->red_curve.reset();
926 this->red_curve_is_valid = false;
927
928 double tolerance_sq = 0.02 * square(_desktop->w2d().descrim() * tol) * exp(0.2 * tol - 2);
929
930 g_assert(is_zero(this->_req_tangent) || is_unit_vector(this->_req_tangent));
931
932 int n_points = this->ps.size();
933
934 // worst case gives us a segment per point
935 int max_segs = 4 * n_points;
936
937 std::vector<Geom::Point> b(max_segs);
938 int const n_segs = Geom::bezier_fit_cubic_r(b.data(), this->ps.data(), n_points, tolerance_sq, max_segs);
939 if (n_segs > 0) {
940 /* Fit and draw and reset state */
941 this->green_curve->moveto(b[0]);
943 guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
944 for (int c = 0; c < n_segs; c++) {
945 // if we are in BSpline we modify the trace to create adhoc nodes
946 if (mode == 2) {
947 Geom::Point point_at1 = b[4 * c + 0] + (1./3) * (b[4 * c + 3] - b[4 * c + 0]);
948 Geom::Point point_at2 = b[4 * c + 3] + (1./3) * (b[4 * c + 0] - b[4 * c + 3]);
949 this->green_curve->curveto(point_at1,point_at2,b[4*c+3]);
950 } else {
951 if (!tablet_enabled || c != n_segs - 1) {
952 this->green_curve->curveto(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
953 } else {
954 std::optional<Geom::Point> finalp = this->green_curve->last_point();
955 if (this->green_curve->nodes_in_path() > 4 && Geom::are_near(*finalp, b[4 * c + 3], 10.0)) {
956 this->green_curve->backspace();
957 this->green_curve->curveto(*finalp, b[4 * c + 3], b[4 * c + 3]);
958 } else {
959 this->green_curve->curveto(b[4 * c + 1], b[4 * c + 3], b[4 * c + 3]);
960 }
961 }
962 }
963 }
964 if (!tablet_enabled) {
965 red_bpath->set_bpath(green_curve.get());
966 }
967
968 /* Fit and draw and copy last point */
969 g_assert(!this->green_curve->is_empty());
970
971 /* Set up direction of next curve. */
972 {
973 Geom::Curve const * last_seg = this->green_curve->last_segment();
974 g_assert( last_seg ); // Relevance: validity of (*last_seg)
975 p_array[0] = last_seg->finalPoint();
976 _npoints = 1;
977 Geom::Curve *last_seg_reverse = last_seg->reverse();
978 Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) );
979 delete last_seg_reverse;
980 _req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
981 ? Geom::Point(0, 0)
982 : Geom::unit_vector(req_vec) );
983 }
984 }
985}
986
987
988/* interpolates the sketched curve and tweaks the current sketch interpolation*/
990 if ( this->ps.size() <= 1 ) {
991 return;
992 }
993
995 double tol = prefs->getDoubleLimited("/tools/freehand/pencil/tolerance", 10.0, 1.0, 100.0) * 0.4;
996 bool simplify = prefs->getInt("/tools/freehand/pencil/simplify", 0);
997 if(simplify){
998 double tol2 = prefs->getDoubleLimited("/tools/freehand/pencil/base-simplify", 25.0, 1.0, 100.0) * 0.4;
999 tol = std::min(tol,tol2);
1000 }
1001 double tolerance_sq = 0.02 * square(_desktop->w2d().descrim() * tol) * exp(0.2 * tol - 2);
1002
1003 bool average_all_sketches = prefs->getBool("/tools/freehand/pencil/average_all_sketches", true);
1004
1005 g_assert(is_zero(this->_req_tangent) || is_unit_vector(this->_req_tangent));
1006
1007 this->red_curve.reset();
1008 this->red_curve_is_valid = false;
1009
1010 int n_points = this->ps.size();
1011
1012 // worst case gives us a segment per point
1013 int max_segs = 4 * n_points;
1014
1015 std::vector<Geom::Point> b(max_segs);
1016
1017 int const n_segs = Geom::bezier_fit_cubic_r(b.data(), this->ps.data(), n_points, tolerance_sq, max_segs);
1018
1019 if (n_segs > 0) {
1020 Geom::Path fit(b[0]);
1021
1022 for (int c = 0; c < n_segs; c++) {
1023 fit.appendNew<Geom::CubicBezier>(b[4 * c + 1], b[4 * c + 2], b[4 * c + 3]);
1024 }
1025
1027
1028 if (this->sketch_n > 0) {
1029 double t;
1030
1031 if (average_all_sketches) {
1032 // Average = (sum of all) / n
1033 // = (sum of all + new one) / n+1
1034 // = ((old average)*n + new one) / n+1
1035 t = this->sketch_n / (this->sketch_n + 1.);
1036 } else {
1037 t = 0.5;
1038 }
1039
1040 this->sketch_interpolation = Geom::lerp(t, fit_pwd2, this->sketch_interpolation);
1041
1042 // simplify path, to eliminate small segments
1043 Path path;
1045 path.Simplify(0.5);
1046
1047 Geom::PathVector pathv = path.MakePathVector();
1048 this->sketch_interpolation = pathv[0].toPwSb();
1049 } else {
1050 this->sketch_interpolation = fit_pwd2;
1051 }
1052
1053 this->sketch_n++;
1054
1055 this->green_curve->reset();
1056 this->green_curve->set_pathvector(Geom::path_from_piecewise(this->sketch_interpolation, 0.01));
1057 if (!tablet_enabled) {
1058 red_bpath->set_bpath(green_curve.get());
1059 }
1060 /* Fit and draw and copy last point */
1061 g_assert(!this->green_curve->is_empty());
1062
1063 /* Set up direction of next curve. */
1064 {
1065 Geom::Curve const * last_seg = this->green_curve->last_segment();
1066 g_assert( last_seg ); // Relevance: validity of (*last_seg)
1067 p_array[0] = last_seg->finalPoint();
1068 _npoints = 1;
1069 Geom::Curve *last_seg_reverse = last_seg->reverse();
1070 Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) );
1071 delete last_seg_reverse;
1072 _req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
1073 ? Geom::Point(0, 0)
1074 : Geom::unit_vector(req_vec) );
1075 }
1076 }
1077
1078 this->ps.clear();
1079 this->points.clear();
1080 this->_wps.clear();
1081}
1082
1084 g_assert(_npoints > 1 );
1085
1086 double const tolerance_sq = 0;
1087
1088 Geom::Point b[4];
1089 g_assert(is_zero(this->_req_tangent)
1090 || is_unit_vector(this->_req_tangent));
1091 Geom::Point const tHatEnd(0, 0);
1093 int const n_segs = Geom::bezier_fit_cubic_full(b, nullptr, p_array, _npoints,
1094 _req_tangent, tHatEnd,
1095 tolerance_sq, 1);
1096 if ( n_segs > 0
1097 && unsigned(_npoints) < G_N_ELEMENTS(p_array) )
1098 {
1099 /* Fit and draw and reset state */
1100
1101 this->red_curve.reset();
1102 this->red_curve.moveto(b[0]);
1103 using Geom::X;
1104 using Geom::Y;
1105 // if we are in BSpline we modify the trace to create adhoc nodes
1106 guint mode = prefs->getInt("/tools/freehand/pencil/freehand-mode", 0);
1107 if(mode == 2){
1108 Geom::Point point_at1 = b[0] + (1./3)*(b[3] - b[0]);
1109 Geom::Point point_at2 = b[3] + (1./3)*(b[0] - b[3]);
1110 this->red_curve.curveto(point_at1,point_at2,b[3]);
1111 }else{
1112 this->red_curve.curveto(b[1], b[2], b[3]);
1113 }
1114 if (!tablet_enabled) {
1115 red_bpath->set_bpath(&red_curve);
1116 }
1117 this->red_curve_is_valid = true;
1118 } else {
1119 /* Fit and draw and copy last point */
1120
1121 g_assert(!this->red_curve.is_empty());
1122
1123 /* Set up direction of next curve. */
1124 {
1125 Geom::Curve const * last_seg = this->red_curve.last_segment();
1126 g_assert( last_seg ); // Relevance: validity of (*last_seg)
1127 p_array[0] = last_seg->finalPoint();
1128 _npoints = 1;
1129 Geom::Curve *last_seg_reverse = last_seg->reverse();
1130 Geom::Point const req_vec( -last_seg_reverse->unitTangentAt(0) );
1131 delete last_seg_reverse;
1132 _req_tangent = ( ( Geom::is_zero(req_vec) || !in_svg_plane(req_vec) )
1133 ? Geom::Point(0, 0)
1134 : Geom::unit_vector(req_vec) );
1135 }
1136
1137 green_curve->append_continuous(red_curve);
1138
1140
1141 auto layer = _desktop->layerManager().currentLayer();
1142 auto highlight = layer->highlight_color();
1143 auto other = prefs->getColor("/tools/nodes/highlight_color", "#ff0000ff");
1144
1145 if(other == highlight) {
1146 green_color = 0x00ff007f;
1147 } else {
1148 green_color = highlight.toRGBA();
1149 }
1150 highlight_color = highlight.toRGBA();
1151
1153 cshape->set_stroke(green_color);
1154 cshape->set_fill(0x0, SP_WIND_RULE_NONZERO);
1155
1156 this->green_bpaths.emplace_back(cshape);
1157
1158 this->red_curve_is_valid = false;
1159 }
1160}
1161
1162} // namespace Inkscape::UI::Tools
1163
1164/*
1165 Local Variables:
1166 mode:c++
1167 c-file-style:"stroustrup"
1168 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1169 indent-tabs-mode:nil
1170 fill-column:99
1171 End:
1172*/
1173// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
@ fill_nonZero
Definition LivarotDefs.h:69
@ bool_op_union
Definition LivarotDefs.h:77
TODO: insert short description here.
double distance(Shape const *s, Geom::Point const &p)
Definition Shape.cpp:2136
Point origin
Definition aa.cpp:227
Bezier fitting algorithms.
static constexpr double DDC_MIN_PRESSURE
static constexpr double DDC_DEFAULT_PRESSURE
static constexpr double DDC_MAX_PRESSURE
Circle shape.
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
Bezier curve with compile-time specified order.
Set of all points at a fixed distance from the center.
Definition circle.h:55
D2< SBasis > toSBasis() const
Definition circle.cpp:268
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual Point finalPoint() const =0
Retrieve the end of the curve.
virtual Curve * reverse() const
Create a reversed version of this curve.
Definition curve.h:234
virtual Point unitTangentAt(Coord t, unsigned n=3) const
Compute a vector tangent to the curve.
Definition curve.cpp:201
Sequence of subpaths.
Definition pathvector.h:122
bool empty() const
Check whether the vector contains any paths.
Definition pathvector.h:145
Sequence of contiguous curves, aka spline.
Definition path.h:353
bool empty() const
Check whether path is empty.
Definition path.h:500
Piecewise< D2< SBasis > > toPwSb() const
Definition path.cpp:388
size_type size() const
Natural size of the path.
Definition path.h:490
void appendNew(Args &&... args)
Append a new curve to the path.
Definition path.h:804
Function defined as discrete pieces.
Definition piecewise.h:71
void push(const T &s, double to)
Convenience/implementation hiding function to add segment/cut pairs.
Definition piecewise.h:141
void push_cut(double c)
Definition piecewise.h:152
Two-dimensional point that doubles as a vector.
Definition point.h:66
Scale inverse() const
Definition transforms.h:172
uint32_t toRGBA(double opacity=1.0) const
Return an sRGB conversion of the color in RGBA int32 format.
Definition color.cpp:117
void remove_snaptarget(bool only_if_presnap=false)
RAII-style mechanism for creating a temporary undo-insensitive context.
SPGroup * currentLayer() const
Returns current top layer.
void param_set_and_write_new_value(std::vector< StorageType > const &new_vector)
Definition array.h:92
Inkscape::XML::Node * getRepr()
Definition effect.cpp:1934
PowerStrokePointArrayParam offset_points
MessageId flash(MessageType type, char const *message)
Temporarily pushes a message onto the stack.
void clear()
Unselects all selected objects.
SPItem * singleItem()
Returns a single selected item.
bool isValidDouble() const
Check if the preference value can be interpreted as a floating point value.
bool isValidString() const
Check if the preference value is a valid String.
Preference storage class.
Definition preferences.h:66
bool getBool(Glib::ustring const &pref_path, bool def=false)
Retrieve a Boolean value.
static Preferences * get()
Access the singleton Preferences object.
Colors::Color getColor(Glib::ustring const &pref_path, std::string const &def="black")
Entry const getEntry(Glib::ustring const &pref_path)
Retrieve a preference entry without specifying its type.
int getInt(Glib::ustring const &pref_path, int def=0)
Retrieve an integer.
void setInt(Glib::ustring const &pref_path, int value)
Set an integer value.
int getIntLimited(Glib::ustring const &pref_path, int def=0, int min=INT_MIN, int max=INT_MAX)
Retrieve a limited integer.
void setBool(Glib::ustring const &pref_path, bool value)
Set a Boolean value.
double getDoubleLimited(Glib::ustring const &pref_path, double def=0.0, double min=DBL_MIN, double max=DBL_MAX, Glib::ustring const &unit="")
Retrieve a limited floating point value.
std::string str() const
The set of selected SPObjects for a given document and layer model.
Definition selection.h:80
void add(XML::Node *repr)
Add an XML node's SPObject to the set of selected objects.
Definition selection.h:107
Class to store data for points which are snap candidates, either as a source or as a target.
std::shared_ptr< SPCurve > sa_overwrited
CanvasItemPtr< CanvasItemBpath > red_bpath
bool root_handler(CanvasEvent const &event) override
std::unique_ptr< SPDrawAnchor > green_anchor
std::shared_ptr< SPCurve > green_curve
std::vector< CanvasItemPtr< CanvasItemBpath > > green_bpaths
void _extinput(ExtendedInput const &ext)
bool _handleButtonRelease(ButtonReleaseEvent const &event)
void _addFreehandPoint(Geom::Point const &p, guint state, bool last)
Add a virtual point to the future pencil path.
Geom::Piecewise< Geom::D2< Geom::SBasis > > sketch_interpolation
Definition pencil-tool.h:59
bool _handleButtonPress(ButtonPressEvent const &event)
void _setEndpoint(Geom::Point const &p)
Change moving endpoint position.
PencilTool(SPDesktop *desktop)
bool _handleKeyRelease(KeyReleaseEvent const &event)
bool _handleKeyPress(KeyPressEvent const &event)
void powerStrokeInterpolate(Geom::Path const path)
void _setStartpoint(Geom::Point const &p)
Reset points and set new starting point.
std::vector< Geom::Point > points
Definition pencil-tool.h:56
void _finishEndpoint()
Finalize addline.
std::vector< Geom::Point > ps
Definition pencil-tool.h:55
bool root_handler(CanvasEvent const &event) override
Callback for handling all pencil context events.
void _endpointSnap(Geom::Point &p, guint const state)
Snaps new node relative to the previous node.
std::vector< Geom::Point > _wps
Definition pencil-tool.h:81
bool _handleMotionNotify(MotionEvent const &event)
bool sp_event_context_knot_mouseover() const
Returns true if we're hovering above a knot (needed because we don't want to pre-snap in that case).
void ungrabCanvasEvents()
Ungrab events from the Canvas Catchall.
void grabCanvasEvents(EventMask mask=EventType::KEY_PRESS|EventType::BUTTON_RELEASE|EventType::MOTION|EventType::BUTTON_PRESS)
Grab events from the Canvas Catchall.
SPGroup * currentLayer() const
std::unique_ptr< MessageContext > message_context
Definition tool-base.h:193
void set_high_motion_precision(bool high_precision=true)
Enable (or disable) high precision for motion events.
void enableSelectionCue(bool enable=true)
Enables/disables the ToolBase's SelCue.
void discard_delayed_snap_event()
If a delayed snap event has been scheduled, this function will cancel it.
MessageContext * defaultMessageContext() const
Definition tool-base.h:123
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
Path and its polyline approximation.
Definition Path.h:93
void LoadPathVector(Geom::PathVector const &pv, Geom::Affine const &tr, bool doTransformation)
Load a lib2geom Geom::PathVector in this path object.
Geom::PathVector MakePathVector() const
Create a lib2geom Geom::PathVector from this Path object.
void Simplify(double treshhold)
Simplify the path.
Wrapper around a Geom::PathVector object.
Definition curve.h:26
Geom::Curve const * last_segment() const
Return last pathsegment (possibly the closing path segment) of the last path in PathVector or NULL.
Definition curve.cpp:289
void moveto(Geom::Point const &p)
Perform a moveto to a point, thus starting a new subpath.
Definition curve.cpp:138
bool is_unset() const
True if paths are in curve.
Definition curve.cpp:245
void transform(Geom::Affine const &m)
Transform all paths in curve by matrix.
Definition curve.cpp:101
void reset()
Set curve to empty curve.
Definition curve.cpp:118
Geom::PathVector const & get_pathvector() const
Definition curve.cpp:52
void lineto(Geom::Point const &p)
Adds a line to the current subpath.
Definition curve.cpp:149
bool is_empty() const
True if no paths are in curve.
Definition curve.cpp:237
void curveto(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2)
Adds a bezier segment to the current subpath.
Definition curve.cpp:190
To do: update description of desktop.
Definition desktop.h:149
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
Inkscape::CanvasItemGroup * getCanvasSketch() const
Definition desktop.h:201
Inkscape::MessageStack * messageStack() const
Definition desktop.h:160
Inkscape::Display::SnapIndicator * getSnapIndicator() const
Definition desktop.h:193
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
SPDocument * doc() const
Definition desktop.h:159
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
Typed SVG document implementation.
Definition document.h:103
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:213
Geom::Scale getDocumentScale(bool computed=true) const
Returns document scale as defined by width/height (in pixels) and viewBox (real world to user-units).
Definition document.cpp:773
The drawing anchor.
Definition draw-anchor.h:42
Geom::Point dp
Definition draw-anchor.h:49
std::shared_ptr< SPCurve > curve
Definition draw-anchor.h:46
Inkscape::Colors::Color highlight_color() const override
Generate a highlight colour if one isn't set and return it.
Base class for visual SVG elements.
Definition sp-item.h:109
SnapManager snap_manager
Class to coordinate snapping operations.
Definition snap.h:80
void setup(SPDesktop const *desktop, bool snapindicator=true, SPObject const *item_to_ignore=nullptr, std::vector< Inkscape::SnapCandidatePoint > *unselected_nodes=nullptr)
Convenience shortcut when there is only one item to ignore.
Definition snap.cpp:663
void preSnap(Inkscape::SnapCandidatePoint const &p, bool to_path_only=false)
Definition snap.cpp:161
void unSetup()
Definition snap.h:147
double c[8][4]
Editable view implementation.
The nodes at the ends of the path in the pen/pencil tools.
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Definition coord.h:97
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
double Coord
Floating point type used to store coordinates.
Definition coord.h:76
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
SPItem * item
Interpolators for lists of points.
PowerStroke LPE effect, see lpe-powerstroke.cpp.
Interface for locally managing a current status message.
Raw stack of active status messages.
int bezier_fit_cubic_full(Point bezier[], int split_points[], Point const data[], int len, Point const &tHat1, Point const &tHat2, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, without possible weedout of identical ...
Angle distance(Angle const &a, Angle const &b)
Definition angle.h:163
bool is_zero(Point const &p)
int bezier_fit_cubic_r(Point bezier[], Point const data[], int len, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, with possible weedout of identical poi...
PathVector path_from_piecewise(Piecewise< D2< SBasis > > const &B, double tol, bool only_cubicbeziers=false)
Make a path from a d2 sbasis.
Point unit_vector(Point const &a)
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
Coord LInfty(Point const &p)
static R & release(R &r)
Decrements the reference count of a anchored object.
Live Path Effects code.
double square(double const x)
static bool pencil_within_tolerance
SPItem * sp_event_context_find_item(SPDesktop *desktop, Geom::Point const &p, bool select_under, bool into_groups)
Returns item at point p in desktop.
void spdc_endpoint_snap_free(ToolBase *tool, Geom::Point &p, std::optional< Geom::Point > &start_of_line)
void spdc_endpoint_snap_rotation(ToolBase *tool, Geom::Point &p, Geom::Point const &o, unsigned state)
Snaps node or handle to PI/rotationsnapsperpi degree increments.
unsigned get_latin_keyval(GtkEventControllerKey const *const controller, unsigned const keyval, unsigned const keycode, GdkModifierType const state, unsigned *consumed_modifiers)
Return the keyval corresponding to the event controller key in Latin group.
SPDrawAnchor * spdc_test_inside(FreehandBase *dc, Geom::Point const &p)
Returns FIRST active anchor (the activated one).
void spdc_concat_colors_and_flush(FreehandBase *dc, bool forceclosed)
Concats red, blue and green.
static Geom::Point pencil_drag_origin_w(0, 0)
void spdc_create_single_dot(ToolBase *tool, Geom::Point const &pt, char const *path, unsigned event_state)
Create a single dot represented by a circle.
static bool in_svg_plane(Geom::Point const &p)
void inspect_event(E &&event, Fs... funcs)
Perform pattern-matching on a CanvasEvent.
@ SNAPSOURCE_NODE_HANDLE
Definition snap-enums.h:43
bool state_held_only_shift(unsigned state)
@ NORMAL_MESSAGE
Definition message.h:26
bool have_viable_layer(SPDesktop *desktop, MessageContext *message)
Check to see if the current layer is both unhidden and unlocked.
bool state_held_only_ctrl(unsigned state)
int mode
Geom::PathVector sp_pathvector_boolop(Geom::PathVector const &pathva, Geom::PathVector const &pathvb, BooleanOp bop, FillRule fra, FillRule frb)
Perform a boolean operation on two pathvectors.
Boolean operations.
PencilTool: a context for pencil tool events.
Conversion between SBasis and Bezier basis polynomials.
Provides a class that shows a temporary indicator on the canvas of where the snap was,...
SPCSSAttr - interface for CSS Attributes.
void sp_lpe_item_update_patheffect(SPLPEItem *lpeitem, bool wholetree, bool write, bool with_satellites)
Calls any registered handlers for the update_patheffect action.
void sp_lpe_item_enable_path_effects(SPLPEItem *lpeitem, bool enable)
Base class for live path effect items.
unsigned button
The button that was pressed/released. (Matches GDK_BUTTON_*.)
Geom::Point pos
Location of the cursor, in world coordinates.
A mouse button (left/right/middle) is pressed.
int num_press
Counter for repeated clicks (e.g. double clicks). Starts at 1 and increments by 1 each time.
A mouse button (left/right/middle) is released.
Abstract base class for events.
unsigned modifiers
The modifiers mask immediately before the event.
Extended input data associated to events generated by graphics tablets.
std::optional< double > pressure
A key has been pressed.
A key has been released.
Movement of the mouse pointer.
Geom::Point pos
Location of the cursor.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
@ SP_WIND_RULE_NONZERO
Definition style-enums.h:24
SPStyle - a style object for SPItem objects.
parse SVG path specifications
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
SPDesktop * desktop
Interface for XML nodes.