diff options
Diffstat (limited to 'src/live_effects/lpe-ellipse_5pts.cpp')
-rw-r--r-- | src/live_effects/lpe-ellipse_5pts.cpp | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/src/live_effects/lpe-ellipse_5pts.cpp b/src/live_effects/lpe-ellipse_5pts.cpp new file mode 100644 index 0000000..e4c9e6f --- /dev/null +++ b/src/live_effects/lpe-ellipse_5pts.cpp @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/** \file + * LPE "Ellipse through 5 points" implementation + */ +/* + * Authors: + * Theodore Janeczko + * + * Copyright (C) Theodore Janeczko 2012 <flutterguy317@gmail.com> + * + * Released under GNU GPL v2+, read the file 'COPYING' for more information. + */ + +#include <2geom/ellipse.h> +#include <2geom/path-sink.h> +#include <glibmm/i18n.h> + +#include "desktop.h" +#include "inkscape.h" +#include "message-stack.h" + +#include "live_effects/lpe-ellipse_5pts.h" + +namespace Inkscape::LivePathEffect { + +LPEEllipse5Pts::LPEEllipse5Pts(LivePathEffectObject *lpeobject) + : Effect(lpeobject) + , _unit_circle{ // Run an IIFE to build the unit circle. + []() { + Geom::PathBuilder builder; + builder.moveTo({1, 0}); + builder.arcTo(1, 1, 0, true, true, {-1, 0}); + builder.arcTo(1, 1, 0, true, true, { 1, 0}); + builder.closePath(); + return builder.peek(); + }()} +{} + +/** Flash a warning message on the status bar. */ +void LPEEllipse5Pts::_flashWarning(char const *message) +{ + auto &app = Inkscape::Application::instance(); + if (auto desktop = app.active_desktop()) { + _clearWarning(); + _error = desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, message); + } +} + +/** Clear our warning from the status bar. */ +void LPEEllipse5Pts::_clearWarning() +{ + if (_error == INVALID) { + return; + } + auto &app = Inkscape::Application::instance(); + if (auto desktop = app.active_desktop()) { + desktop->messageStack()->cancel(_error); + _error = INVALID; + } +} + +/** Fit an ellipse to the first 5 nodes in the given PathVector. */ +Geom::PathVector LPEEllipse5Pts::doEffect_path(Geom::PathVector const &path_in) +{ + auto const &source = path_in[0]; + if (source.size() < 4 /* For 5 nodes, we need at least 4 segments. */) { + _flashWarning(_("Five points required for constructing an ellipse")); + return path_in; + } + + std::vector<Geom::Point> source_points; + source_points.reserve(5); + for (int i = 0; i < 5; i++) { + source_points.push_back(source.pointAt((Geom::Coord)i)); + } + + Geom::Ellipse ellipse; + bool fitting_fail = false; + try { + ellipse.fit(source_points); + } catch (Geom::RangeError &e) { + fitting_fail = true; + } + if (fitting_fail || ellipse.ray(Geom::X) == 0 || ellipse.ray(Geom::Y) == 0) { + _flashWarning(_("No unique ellipse passing through these points")); + return path_in; + } + _clearWarning(); + + // Transform the unit circle contour to the fitted ellipse. + return _unit_circle * ellipse.unitCircleTransform(); +} + +} //namespace Inkscape::LivePathEffect + +/* + 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 : |