add (bezier) curves to canvas, use for automation lines; fix issue with rectangles missing their upper line segment; more cairo canvas fixes
This commit is contained in:
parent
ee1f0520a8
commit
7caf517b27
@ -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"
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 */
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
37
libs/canvas/canvas/curve.h
Normal file
37
libs/canvas/canvas/curve.h
Normal file
@ -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
|
@ -30,6 +30,7 @@ namespace ArdourCanvas {
|
||||
class GtkCanvas;
|
||||
class GtkCanvasViewport;
|
||||
class Text;
|
||||
class Curve;
|
||||
}
|
||||
|
||||
#endif /* __canvas_canvas_fwd_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;
|
||||
};
|
||||
|
229
libs/canvas/curve.cc
Normal file
229
libs/canvas/curve.cc
Normal file
@ -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;
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -30,6 +30,7 @@ path_prefix = 'libs/canvas/'
|
||||
canvas_sources = [
|
||||
'arrow.cc',
|
||||
'canvas.cc',
|
||||
'curve.cc',
|
||||
'debug.cc',
|
||||
'item.cc',
|
||||
'fill.cc',
|
||||
|
Loading…
Reference in New Issue
Block a user