add (bezier) curves to canvas, use for automation lines; fix issue with rectangles...
authorPaul Davis <paul@linuxaudiosystems.com>
Fri, 12 Apr 2013 02:54:12 +0000 (22:54 -0400)
committerPaul Davis <paul@linuxaudiosystems.com>
Fri, 12 Apr 2013 02:54:12 +0000 (22:54 -0400)
13 files changed:
gtk2_ardour/audio_region_view.cc
gtk2_ardour/automation_line.cc
gtk2_ardour/automation_line.h
gtk2_ardour/control_point.cc
gtk2_ardour/region_view.cc
gtk2_ardour/region_view.h
libs/canvas/canvas/curve.h [new file with mode: 0644]
libs/canvas/canvas/fwd.h
libs/canvas/canvas/poly_item.h
libs/canvas/curve.cc [new file with mode: 0644]
libs/canvas/poly_item.cc
libs/canvas/rectangle.cc
libs/canvas/wscript

index 1e1e2f988313b4ec366c6a56f92355bda15494bb..cf348bf532b3e2984e1fca07f7d2f13923fef561 100644 (file)
@@ -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"
index 02ce72149f09776f0a42e371ddcea4ee42dc0086..460802309a2aaee1ffce52e42a3c5dc245798015 100644 (file)
@@ -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);
 
index 2b41647fe0d0088c78eb45faf97a68175a6ec160..816105b1e56b70c8333c5c6b2c0a775043200db4 100644 (file)
@@ -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 */
 
index 8e05ad0a0b1b57367489db43177a66012c6b86a9..a3c2c1874b8aff817a7b18162d65840d016066bc 100644 (file)
@@ -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);
index 8451497289a0525c21bb3242d20ffa50b80505da..824026ff6e1be3e9498c239e5d06631bcf5f2816 100644 (file)
@@ -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"
index 5b1c57044e41091528c56f92396ba10bf09afcf6..f94fc8ed8de32c66b69632ef44b641cc0d54a2dd 100644 (file)
@@ -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 (file)
index 0000000..733808d
--- /dev/null
@@ -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
index 14847fab5409fe409aca8f355f55a56f7c4347b9..1e812f9144c5ba3c8078eef9528705af9db6fb15 100644 (file)
@@ -30,6 +30,7 @@ namespace ArdourCanvas {
        class GtkCanvas;
        class GtkCanvasViewport;
        class Text;
+       class Curve;
 }
 
 #endif /* __canvas_canvas_fwd_h__ */
index de5e5cf1885d0a9ba23b336d71a1e11df0928288..51f907699df01ef1231bcb3aaff70280b262e02e 100644 (file)
@@ -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 (file)
index 0000000..172d1e8
--- /dev/null
@@ -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;
+}
index d6e67ede0c47b5094fbc929753f0dbec5d94b53e..45faaf2d54a3c3388321c64cdf6e0bf74cd92286 100644 (file)
@@ -60,6 +60,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)
 {
index a7683143641ee108978ea88380b95ff7c73d0359..ea141e095455009c8588dc2f9da30b96564b4884 100644 (file)
@@ -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);
index 8d1181713a1655017bcf57c49014111f52489388..5b36edcb038892e102c9c342c232cc855f29efc0 100644 (file)
@@ -30,6 +30,7 @@ path_prefix = 'libs/canvas/'
 canvas_sources = [
        'arrow.cc',
        'canvas.cc',
+        'curve.cc',
        'debug.cc',
        'item.cc',
        'fill.cc',