// SPDX-License-Identifier: GPL-2.0-or-later /** * A class to represent a single Bezier control curve, either a line or a cubic Bezier. */ /* * Author: * Tavmjong Bah * * Copyright (C) 2020 Tavmjong Bah * * Rewrite of SPCtrlLine and SPCtrlCurve * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include <2geom/bezier-curve.h> #include "canvas-item-curve.h" #include "color.h" // SP_RGBA_x_F #include "helper/geom.h" #include "ui/widget/canvas.h" namespace Inkscape { /** * Create an null control curve. */ CanvasItemCurve::CanvasItemCurve(CanvasItemGroup *group) : CanvasItem(group) { _name = "CanvasItemCurve:Null"; } /** * Create a linear control curve. Points are in document coordinates. */ CanvasItemCurve::CanvasItemCurve(CanvasItemGroup *group, Geom::Point const &p0, Geom::Point const &p1) : CanvasItem(group) , _curve(std::make_unique(p0, p1)) { _name = "CanvasItemCurve:Line"; } /** * Create a cubic Bezier control curve. Points are in document coordinates. */ CanvasItemCurve::CanvasItemCurve(CanvasItemGroup *group, Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3) : CanvasItem(group) , _curve(std::make_unique(p0, p1, p2, p3)) { _name = "CanvasItemCurve:CubicBezier"; } /** * Set a linear control curve. Points are in document coordinates. */ void CanvasItemCurve::set_coords(Geom::Point const &p0, Geom::Point const &p1) { defer([=] { _name = "CanvasItemCurve:Line"; _curve = std::make_unique(p0, p1); request_update(); }); } /** * Set a cubic Bezier control curve. Points are in document coordinates. */ void CanvasItemCurve::set_coords(Geom::Point const &p0, Geom::Point const &p1, Geom::Point const &p2, Geom::Point const &p3) { defer([=] { _name = "CanvasItemCurve:CubicBezier"; _curve = std::make_unique(p0, p1, p2, p3); request_update(); }); } /** * Set stroke width. */ void CanvasItemCurve::set_width(int width) { defer([=] { if (_width == width) return; _width = width; request_update(); }); } /** * Set background stroke alpha. */ void CanvasItemCurve::set_bg_alpha(float alpha) { defer([=] { if (bg_alpha == alpha) return; bg_alpha = alpha; request_update(); }); } /** * Returns distance between point in canvas units and nearest point on curve. */ double CanvasItemCurve::closest_distance_to(Geom::Point const &p) const { double d = Geom::infinity(); if (_curve) { Geom::BezierCurve curve = *_curve; curve *= affine(); // Document to canvas. Geom::Point n = curve.pointAt(curve.nearestTime(p)); d = Geom::distance(p, n); } return d; } /** * Returns true if point p (in canvas units) is within tolerance (canvas units) distance of curve. */ bool CanvasItemCurve::contains(Geom::Point const &p, double tolerance) { return closest_distance_to(p) <= tolerance; } /** * Update and redraw control curve. */ void CanvasItemCurve::_update(bool) { // Queue redraw of old area (erase previous content). request_redraw(); // This is actually never useful as curves are always deleted // and recreated when a node is moved! But keep it in case we change that. if (!_curve || _curve->isDegenerate()) { _bounds = {}; return; // No curve! Can happen - see node.h. } // Tradeoff between updating a larger area (typically twice for Beziers?) vs computation time for bounds. _bounds = expandedBy(_curve->boundsExact() * affine(), 2); // Room for stroke. // Queue redraw of new area request_redraw(); } /** * Render curve to screen via Cairo. */ void CanvasItemCurve::_render(Inkscape::CanvasItemBuffer &buf) const { assert(_curve); // Not called if _curve is null, since _bounds would be null. // Todo: Transform, rather than copy. Geom::BezierCurve curve = *_curve; curve *= affine(); // Document to canvas. curve *= Geom::Translate(-buf.rect.min()); // Canvas to screen. buf.cr->save(); buf.cr->begin_new_path(); if (curve.size() == 2) { // Line buf.cr->move_to(curve[0].x(), curve[0].y()); buf.cr->line_to(curve[1].x(), curve[1].y()); } else { // Curve buf.cr->move_to(curve[0].x(), curve[0].y()); buf.cr->curve_to(curve[1].x(), curve[1].y(), curve[2].x(), curve[2].y(), curve[3].x(), curve[3].y()); } buf.cr->set_source_rgba(1.0, 1.0, 1.0, bg_alpha); buf.cr->set_line_width(background_width); buf.cr->stroke_preserve(); buf.cr->set_source_rgba(SP_RGBA32_R_F(_stroke), SP_RGBA32_G_F(_stroke), SP_RGBA32_B_F(_stroke), SP_RGBA32_A_F(_stroke)); buf.cr->set_line_width(_width); buf.cr->stroke(); // Uncomment to show bounds // Geom::Rect bounds = _bounds; // bounds.expandBy(-1); // bounds -= buf.rect.min(); // buf.cr->set_source_rgba(1.0, 0.0, 0.0, 1.0); // buf.cr->rectangle(bounds.min().x(), bounds.min().y(), bounds.width(), bounds.height()); // buf.cr->stroke(); buf.cr->restore(); } } // 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 :