From: Paul Davis Date: Fri, 12 Apr 2013 02:54:12 +0000 (-0400) Subject: add (bezier) curves to canvas, use for automation lines; fix issue with rectangles... X-Git-Tag: 1.0.0~1457 X-Git-Url: https://main.carlh.net/gitweb/?a=commitdiff_plain;h=7caf517b27aa93df14519840224104fb8a1f12c1;p=ardour.git add (bezier) curves to canvas, use for automation lines; fix issue with rectangles missing their upper line segment; more cairo canvas fixes --- diff --git a/gtk2_ardour/audio_region_view.cc b/gtk2_ardour/audio_region_view.cc index 1e1e2f9883..cf348bf532 100644 --- a/gtk2_ardour/audio_region_view.cc +++ b/gtk2_ardour/audio_region_view.cc @@ -40,6 +40,7 @@ #include "canvas/rectangle.h" #include "canvas/polygon.h" #include "canvas/poly_line.h" +#include "canvas/line.h" #include "canvas/pixbuf.h" #include "streamview.h" diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index 02ce72149f..460802309a 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -91,7 +91,7 @@ AutomationLine::AutomationLine (const string& name, TimeAxisView& tv, ArdourCanv group = new ArdourCanvas::Group (&parent); - line = new ArdourCanvas::PolyLine (group); + line = new ArdourCanvas::Curve (group); line->set_outline_width (1); line->set_data ("line", this); diff --git a/gtk2_ardour/automation_line.h b/gtk2_ardour/automation_line.h index 2b41647fe0..816105b1e5 100644 --- a/gtk2_ardour/automation_line.h +++ b/gtk2_ardour/automation_line.h @@ -38,7 +38,7 @@ #include "canvas/types.h" #include "canvas/group.h" -#include "canvas/line.h" +#include "canvas/curve.h" class AutomationLine; class ControlPoint; @@ -48,9 +48,6 @@ class AutomationTimeAxisView; class Selectable; class Selection; -namespace ArdourCanvas { - class Rectangle; -} /** A GUI representation of an ARDOUR::AutomationList */ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible @@ -177,7 +174,7 @@ class AutomationLine : public sigc::trackable, public PBD::StatefulDestructible ArdourCanvas::Group& _parent_group; ArdourCanvas::Group* group; - ArdourCanvas::PolyLine* line; /* line */ + ArdourCanvas::Curve* line; /* line */ ArdourCanvas::Points line_points; /* coordinates for canvas line */ std::vector control_points; /* visible control points */ diff --git a/gtk2_ardour/control_point.cc b/gtk2_ardour/control_point.cc index 8e05ad0a0b..a3c2c1874b 100644 --- a/gtk2_ardour/control_point.cc +++ b/gtk2_ardour/control_point.cc @@ -122,14 +122,6 @@ ControlPoint::visible () const void ControlPoint::reset (double x, double y, AutomationList::iterator mi, uint32_t vi, ShapeType shape) { - /* If this is too big, libart will confuse itself and segfault after it casts the bounding box - of this automation line to ints. Sigh. - */ - - if (x > INT32_MAX) { - x = INT32_MAX; - } - _model = mi; _view_index = vi; move_to (x, y, shape); diff --git a/gtk2_ardour/region_view.cc b/gtk2_ardour/region_view.cc index 8451497289..824026ff6e 100644 --- a/gtk2_ardour/region_view.cc +++ b/gtk2_ardour/region_view.cc @@ -32,6 +32,7 @@ #include "canvas/debug.h" #include "canvas/pixbuf.h" #include "canvas/text.h" +#include "canvas/line.h" #include "ardour_ui.h" #include "global_signals.h" diff --git a/gtk2_ardour/region_view.h b/gtk2_ardour/region_view.h index 5b1c57044e..f94fc8ed8d 100644 --- a/gtk2_ardour/region_view.h +++ b/gtk2_ardour/region_view.h @@ -25,6 +25,8 @@ #include "ardour/region.h" #include "ardour/beats_frames_converter.h" +#include "canvas/fwd.h" + #include "time_axis_view_item.h" #include "automation_line.h" #include "enums.h" diff --git a/libs/canvas/canvas/curve.h b/libs/canvas/canvas/curve.h new file mode 100644 index 0000000000..733808dc14 --- /dev/null +++ b/libs/canvas/canvas/curve.h @@ -0,0 +1,37 @@ +#ifndef __CANVAS_CURVE_H__ +#define __CANVAS_CURVE_H__ + +#include "canvas/poly_item.h" + +namespace ArdourCanvas { + +class Curve : public PolyItem +{ +public: + Curve (Group *); + + void compute_bounding_box () const; + + void render (Rect const & area, Cairo::RefPtr) const; + XMLNode* get_state () const; + void set_state (XMLNode const *); + + void set (Points const &); + + protected: + void render_path (Rect const &, Cairo::RefPtr) const; + void render_curve (Rect const &, Cairo::RefPtr) const; + + private: + Points first_control_points; + Points second_control_points; + + + static void compute_control_points (Points const &, + Points&, Points&); + static double* solve (std::vector const&); +}; + +} + +#endif diff --git a/libs/canvas/canvas/fwd.h b/libs/canvas/canvas/fwd.h index 14847fab54..1e812f9144 100644 --- a/libs/canvas/canvas/fwd.h +++ b/libs/canvas/canvas/fwd.h @@ -30,6 +30,7 @@ namespace ArdourCanvas { class GtkCanvas; class GtkCanvasViewport; class Text; + class Curve; } #endif /* __canvas_canvas_fwd_h__ */ diff --git a/libs/canvas/canvas/poly_item.h b/libs/canvas/canvas/poly_item.h index de5e5cf188..51f907699d 100644 --- a/libs/canvas/canvas/poly_item.h +++ b/libs/canvas/canvas/poly_item.h @@ -16,13 +16,14 @@ public: void add_poly_item_state (XMLNode *) const; void set_poly_item_state (XMLNode const *); - void set (Points const &); + virtual void set (Points const &); Points const & get () const; void dump (std::ostream&) const; protected: void render_path (Rect const &, Cairo::RefPtr) const; + void render_curve (Rect const &, Cairo::RefPtr, Points const &, Points const &) const; Points _points; }; diff --git a/libs/canvas/curve.cc b/libs/canvas/curve.cc new file mode 100644 index 0000000000..172d1e8b9d --- /dev/null +++ b/libs/canvas/curve.cc @@ -0,0 +1,229 @@ +/* + Copyright (C) 2013 Paul Davis + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include +#include + +#include "pbd/xml++.h" + +#include "canvas/curve.h" + +using namespace ArdourCanvas; +using std::min; +using std::max; + +Curve::Curve (Group* parent) + : Item (parent) + , PolyItem (parent) +{ + +} + +void +Curve::compute_bounding_box () const +{ + PolyItem::compute_bounding_box (); + + if (_bounding_box) { + + bool have1 = false; + bool have2 = false; + + Rect bbox1; + Rect bbox2; + + for (Points::const_iterator i = first_control_points.begin(); i != first_control_points.end(); ++i) { + if (have1) { + bbox1.x0 = min (bbox1.x0, i->x); + bbox1.y0 = min (bbox1.y0, i->y); + bbox1.x1 = max (bbox1.x1, i->x); + bbox1.y1 = max (bbox1.y1, i->y); + } else { + bbox1.x0 = bbox1.x1 = i->x; + bbox1.y0 = bbox1.y1 = i->y; + have1 = true; + } + } + + for (Points::const_iterator i = second_control_points.begin(); i != second_control_points.end(); ++i) { + if (have2) { + bbox2.x0 = min (bbox2.x0, i->x); + bbox2.y0 = min (bbox2.y0, i->y); + bbox2.x1 = max (bbox2.x1, i->x); + bbox2.y1 = max (bbox2.y1, i->y); + } else { + bbox2.x0 = bbox2.x1 = i->x; + bbox2.y0 = bbox2.y1 = i->y; + have2 = true; + } + } + + Rect u = bbox1.extend (bbox2); + _bounding_box = u.extend (_bounding_box.get()); + } + + _bounding_box_dirty = false; +} + +void +Curve::set (Points const& p) +{ + PolyItem::set (p); + + first_control_points.clear (); + second_control_points.clear (); + + compute_control_points (_points, first_control_points, second_control_points); +} + +void +Curve::render (Rect const & area, Cairo::RefPtr context) const +{ + if (_outline) { + setup_outline_context (context); + render_path (area, context); + context->stroke (); + } +} + +XMLNode * +Curve::get_state () const +{ + XMLNode* node = new XMLNode ("PolyLine"); + add_poly_item_state (node); + add_outline_state (node); + return node; +} + +void +Curve::set_state (XMLNode const * node) +{ + set_poly_item_state (node); + set_outline_state (node); +} + +void +Curve::render_path (Rect const & area, Cairo::RefPtr context) const +{ + PolyItem::render_curve (area, context, first_control_points, second_control_points); +} + +void +Curve::compute_control_points (Points const& knots, + Points& firstControlPoints, + Points& secondControlPoints) +{ + Points::size_type n = knots.size() - 1; + + if (n < 1) { + return; + } + + if (n == 1) { + /* Special case: Bezier curve should be a straight line. */ + + Duple d; + + d.x = (2.0 * knots[0].x + knots[1].x) / 3; + d.y = (2.0 * knots[0].y + knots[1].y) / 3; + firstControlPoints.push_back (d); + + d.x = 2.0 * firstControlPoints[0].x - knots[0].x; + d.y = 2.0 * firstControlPoints[0].y - knots[0].y; + secondControlPoints.push_back (d); + + return; + } + + // Calculate first Bezier control points + // Right hand side vector + + std::vector rhs; + + rhs.assign (n, 0); + + // Set right hand side X values + + for (Points::size_type i = 1; i < n - 1; ++i) { + rhs[i] = 4 * knots[i].x + 2 * knots[i + 1].x; + } + rhs[0] = knots[0].x + 2 * knots[1].x; + rhs[n - 1] = (8 * knots[n - 1].x + knots[n].x) / 2.0; + + // Get first control points X-values + double* x = solve (rhs); + + // Set right hand side Y values + for (Points::size_type i = 1; i < n - 1; ++i) { + rhs[i] = 4 * knots[i].y + 2 * knots[i + 1].y; + } + rhs[0] = knots[0].y + 2 * knots[1].y; + rhs[n - 1] = (8 * knots[n - 1].y + knots[n].y) / 2.0; + + // Get first control points Y-values + double* y = solve (rhs); + + for (Points::size_type i = 0; i < n; ++i) { + + firstControlPoints.push_back (Duple (x[i], y[i])); + + if (i < n - 1) { + secondControlPoints.push_back (Duple (2 * knots [i + 1].x - x[i + 1], + 2 * knots[i + 1].y - y[i + 1])); + } else { + secondControlPoints.push_back (Duple ((knots [n].x + x[n - 1]) / 2, + (knots[n].y + y[n - 1]) / 2)); + } + } + + delete [] x; + delete [] y; +} + +/** Solves a tridiagonal system for one of coordinates (x or y) + * of first Bezier control points. + */ + +double* +Curve::solve (std::vector const & rhs) +{ + std::vector::size_type n = rhs.size(); + double* x = new double[n]; // Solution vector. + double* tmp = new double[n]; // Temp workspace. + + double b = 2.0; + + x[0] = rhs[0] / b; + + for (std::vector::size_type i = 1; i < n; i++) { + // Decomposition and forward substitution. + tmp[i] = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; + x[i] = (rhs[i] - x[i - 1]) / b; + } + + for (std::vector::size_type i = 1; i < n; i++) { + // Backsubstitution + x[n - i - 1] -= tmp[n - i] * x[n - i]; + } + + delete [] tmp; + + return x; +} diff --git a/libs/canvas/poly_item.cc b/libs/canvas/poly_item.cc index d6e67ede0c..45faaf2d54 100644 --- a/libs/canvas/poly_item.cc +++ b/libs/canvas/poly_item.cc @@ -60,6 +60,38 @@ PolyItem::render_path (Rect const & /*area*/, Cairo::RefPtr cont } } +void +PolyItem::render_curve (Rect const & area, Cairo::RefPtr context, Points const & first_control_points, Points const & second_control_points) const +{ + bool done_first = false; + + if (_points.size() <= 2) { + render_path (area, context); + return; + } + + Points::const_iterator cp1 = first_control_points.begin(); + Points::const_iterator cp2 = second_control_points.begin(); + + for (Points::const_iterator i = _points.begin(); i != _points.end(); ++i) { + + if (done_first) { + + context->curve_to (cp1->x, cp1->y, + cp2->x, cp2->y, + i->x, i->y); + + cp1++; + cp2++; + + } else { + + context->move_to (i->x, i->y); + done_first = true; + } + } +} + void PolyItem::set (Points const & points) { diff --git a/libs/canvas/rectangle.cc b/libs/canvas/rectangle.cc index a768314364..ea141e0954 100644 --- a/libs/canvas/rectangle.cc +++ b/libs/canvas/rectangle.cc @@ -61,7 +61,7 @@ Rectangle::render (Rect const & /*area*/, Cairo::RefPtr context) if (_outline_what & TOP) { context->move_to (plot.x0, plot.y0); - context->line_to (plot.x0, plot.y1); + context->line_to (plot.x1, plot.y0); } setup_outline_context (context); diff --git a/libs/canvas/wscript b/libs/canvas/wscript index 8d1181713a..5b36edcb03 100644 --- a/libs/canvas/wscript +++ b/libs/canvas/wscript @@ -30,6 +30,7 @@ path_prefix = 'libs/canvas/' canvas_sources = [ 'arrow.cc', 'canvas.cc', + 'curve.cc', 'debug.cc', 'item.cc', 'fill.cc',