summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2013-04-11 22:54:12 -0400
committerPaul Davis <paul@linuxaudiosystems.com>2013-04-11 22:54:12 -0400
commit7caf517b27aa93df14519840224104fb8a1f12c1 (patch)
tree8310fd036083803c1da3472400be8522f4cfa512
parentee1f0520a820fb829ef3c1dbfcbb57e37242c2c8 (diff)
add (bezier) curves to canvas, use for automation lines; fix issue with rectangles missing their upper line segment; more cairo canvas fixes
-rw-r--r--gtk2_ardour/audio_region_view.cc1
-rw-r--r--gtk2_ardour/automation_line.cc2
-rw-r--r--gtk2_ardour/automation_line.h7
-rw-r--r--gtk2_ardour/control_point.cc8
-rw-r--r--gtk2_ardour/region_view.cc1
-rw-r--r--gtk2_ardour/region_view.h2
-rw-r--r--libs/canvas/canvas/curve.h37
-rw-r--r--libs/canvas/canvas/fwd.h1
-rw-r--r--libs/canvas/canvas/poly_item.h3
-rw-r--r--libs/canvas/curve.cc229
-rw-r--r--libs/canvas/poly_item.cc32
-rw-r--r--libs/canvas/rectangle.cc2
-rw-r--r--libs/canvas/wscript1
13 files changed, 310 insertions, 16 deletions
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<ControlPoint*> 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<Cairo::Context>) const;
+ XMLNode* get_state () const;
+ void set_state (XMLNode const *);
+
+ void set (Points const &);
+
+ protected:
+ void render_path (Rect const &, Cairo::RefPtr<Cairo::Context>) const;
+ void render_curve (Rect const &, Cairo::RefPtr<Cairo::Context>) const;
+
+ private:
+ Points first_control_points;
+ Points second_control_points;
+
+
+ static void compute_control_points (Points const &,
+ Points&, Points&);
+ static double* solve (std::vector<double> 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<Cairo::Context>) const;
+ void render_curve (Rect const &, Cairo::RefPtr<Cairo::Context>, 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 <exception>
+#include <algorithm>
+
+#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<Cairo::Context> 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<Cairo::Context> 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<double> 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<double> const & rhs)
+{
+ std::vector<double>::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<double>::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<double>::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
@@ -61,6 +61,38 @@ PolyItem::render_path (Rect const & /*area*/, Cairo::RefPtr<Cairo::Context> cont
}
void
+PolyItem::render_curve (Rect const & area, Cairo::RefPtr<Cairo::Context> 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)
{
begin_change ();
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<Cairo::Context> 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',