From 30f204b90ecc6fd00d1d91802565ed0502abb5b3 Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Thu, 29 May 2014 03:09:57 +0200 Subject: [PATCH] Canvas: new x-fade drawing, two curve widget --- libs/canvas/canvas/curve.h | 18 +- libs/canvas/canvas/interpolated_curve.h | 229 ++++++++++++++++++++ libs/canvas/canvas/xfade_curve.h | 84 ++++++++ libs/canvas/curve.cc | 197 +---------------- libs/canvas/wscript | 3 +- libs/canvas/xfade_curve.cc | 270 ++++++++++++++++++++++++ 6 files changed, 593 insertions(+), 208 deletions(-) create mode 100644 libs/canvas/canvas/interpolated_curve.h create mode 100644 libs/canvas/canvas/xfade_curve.h create mode 100644 libs/canvas/xfade_curve.cc diff --git a/libs/canvas/canvas/curve.h b/libs/canvas/canvas/curve.h index e6decf8455..234956763b 100644 --- a/libs/canvas/canvas/curve.h +++ b/libs/canvas/canvas/curve.h @@ -21,27 +21,25 @@ #include "canvas/visibility.h" +#include "canvas/interpolated_curve.h" #include "canvas/poly_item.h" #include "canvas/fill.h" namespace ArdourCanvas { -class LIBCANVAS_API Curve : public PolyItem, public Fill +class XFadeCurve; + +class LIBCANVAS_API Curve : public PolyItem, public Fill, public InterpolatedCurve { public: Curve (Group *); - enum SplineType { - CatmullRomUniform, - CatmullRomCentripetal, - }; - enum CurveFill { None, Inside, Outside, }; - + void compute_bounding_box () const; void render (Rect const & area, Cairo::RefPtr) const; void set (Points const &); @@ -55,14 +53,12 @@ public: Points samples; Points::size_type n_samples; uint32_t points_per_segment; - SplineType curve_type; + InterpolatedCurve::SplineType curve_type; CurveFill curve_fill; void interpolate (); - - static void interpolate (const Points& coordinates, uint32_t points_per_segment, SplineType, bool closed, Points& results); }; - + } #endif diff --git a/libs/canvas/canvas/interpolated_curve.h b/libs/canvas/canvas/interpolated_curve.h new file mode 100644 index 0000000000..6c5d6b0495 --- /dev/null +++ b/libs/canvas/canvas/interpolated_curve.h @@ -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. +*/ + +#ifndef __CANVAS_INTERPOLATED_CURVE_H__ +#define __CANVAS_INTERPOLATED_CURVE_H__ + +#include "canvas/visibility.h" +#include "canvas/types.h" + +namespace ArdourCanvas { + +class LIBCANVAS_API InterpolatedCurve +{ +public: + enum SplineType { + CatmullRomUniform, + CatmullRomCentripetal, + }; + +protected: + + /** + * This method will calculate the Catmull-Rom interpolation curve, returning + * it as a list of Coord coordinate objects. This method in particular + * adds the first and last control points which are not visible, but required + * for calculating the spline. + * + * @param coordinates The list of original straight line points to calculate + * an interpolation from. + * @param points_per_segment The integer number of equally spaced points to + * return along each curve. The actual distance between each + * point will depend on the spacing between the control points. + * @return The list of interpolated coordinates. + * @param curve_type Chordal (stiff), Uniform(floppy), or Centripetal(medium) + * @throws gov.ca.water.shapelite.analysis.CatmullRomException if + * points_per_segment is less than 2. + */ + static void + interpolate (const Points& coordinates, uint32_t points_per_segment, SplineType curve_type, bool closed, Points& results) + { + if (points_per_segment < 2) { + return; + } + + // Cannot interpolate curves given only two points. Two points + // is best represented as a simple line segment. + if (coordinates.size() < 3) { + results = coordinates; + return; + } + + // Copy the incoming coordinates. We need to modify it during interpolation + Points vertices = coordinates; + + // Test whether the shape is open or closed by checking to see if + // the first point intersects with the last point. M and Z are ignored. + if (closed) { + // Use the second and second from last points as control points. + // get the second point. + Duple p2 = vertices[1]; + // get the point before the last point + Duple pn1 = vertices[vertices.size() - 2]; + + // insert the second from the last point as the first point in the list + // because when the shape is closed it keeps wrapping around to + // the second point. + vertices.insert(vertices.begin(), pn1); + // add the second point to the end. + vertices.push_back(p2); + } else { + // The shape is open, so use control points that simply extend + // the first and last segments + + // Get the change in x and y between the first and second coordinates. + double dx = vertices[1].x - vertices[0].x; + double dy = vertices[1].y - vertices[0].y; + + // Then using the change, extrapolate backwards to find a control point. + double x1 = vertices[0].x - dx; + double y1 = vertices[0].y - dy; + + // Actaully create the start point from the extrapolated values. + Duple start (x1, y1); + + // Repeat for the end control point. + int n = vertices.size() - 1; + dx = vertices[n].x - vertices[n - 1].x; + dy = vertices[n].y - vertices[n - 1].y; + double xn = vertices[n].x + dx; + double yn = vertices[n].y + dy; + Duple end (xn, yn); + + // insert the start control point at the start of the vertices list. + vertices.insert (vertices.begin(), start); + + // append the end control ponit to the end of the vertices list. + vertices.push_back (end); + } + + // When looping, remember that each cycle requires 4 points, starting + // with i and ending with i+3. So we don't loop through all the points. + + for (Points::size_type i = 0; i < vertices.size() - 3; i++) { + + // Actually calculate the Catmull-Rom curve for one segment. + Points r; + + _interpolate (vertices, i, points_per_segment, curve_type, r); + + // Since the middle points are added twice, once for each bordering + // segment, we only add the 0 index result point for the first + // segment. Otherwise we will have duplicate points. + + if (results.size() > 0) { + r.erase (r.begin()); + } + + // Add the coordinates for the segment to the result list. + + results.insert (results.end(), r.begin(), r.end()); + } + } + +private: + /** + * Calculate the same values but introduces the ability to "parameterize" the t + * values used in the calculation. This is based on Figure 3 from + * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf + * + * @param p An array of double values of length 4, where interpolation + * occurs from p1 to p2. + * @param time An array of time measures of length 4, corresponding to each + * p value. + * @param t the actual interpolation ratio from 0 to 1 representing the + * position between p1 and p2 to interpolate the value. + */ + static double + __interpolate (double p[4], double time[4], double t) + { + const double L01 = p[0] * (time[1] - t) / (time[1] - time[0]) + p[1] * (t - time[0]) / (time[1] - time[0]); + const double L12 = p[1] * (time[2] - t) / (time[2] - time[1]) + p[2] * (t - time[1]) / (time[2] - time[1]); + const double L23 = p[2] * (time[3] - t) / (time[3] - time[2]) + p[3] * (t - time[2]) / (time[3] - time[2]); + const double L012 = L01 * (time[2] - t) / (time[2] - time[0]) + L12 * (t - time[0]) / (time[2] - time[0]); + const double L123 = L12 * (time[3] - t) / (time[3] - time[1]) + L23 * (t - time[1]) / (time[3] - time[1]); + const double C12 = L012 * (time[2] - t) / (time[2] - time[1]) + L123 * (t - time[1]) / (time[2] - time[1]); + return C12; + } + + /** + * Given a list of control points, this will create a list of points_per_segment + * points spaced uniformly along the resulting Catmull-Rom curve. + * + * @param points The list of control points, leading and ending with a + * coordinate that is only used for controling the spline and is not visualized. + * @param index The index of control point p0, where p0, p1, p2, and p3 are + * used in order to create a curve between p1 and p2. + * @param points_per_segment The total number of uniformly spaced interpolated + * points to calculate for each segment. The larger this number, the + * smoother the resulting curve. + * @param curve_type Clarifies whether the curve should use uniform, chordal + * or centripetal curve types. Uniform can produce loops, chordal can + * produce large distortions from the original lines, and centripetal is an + * optimal balance without spaces. + * @return the list of coordinates that define the CatmullRom curve + * between the points defined by index+1 and index+2. + */ + static void + _interpolate (const Points& points, Points::size_type index, int points_per_segment, SplineType curve_type, Points& results) + { + double x[4]; + double y[4]; + double time[4]; + + for (int i = 0; i < 4; i++) { + x[i] = points[index + i].x; + y[i] = points[index + i].y; + time[i] = i; + } + + double tstart = 1; + double tend = 2; + + if (curve_type != CatmullRomUniform) { + double total = 0; + for (int i = 1; i < 4; i++) { + double dx = x[i] - x[i - 1]; + double dy = y[i] - y[i - 1]; + if (curve_type == CatmullRomCentripetal) { + total += pow (dx * dx + dy * dy, .25); + } else { + total += pow (dx * dx + dy * dy, .5); + } + time[i] = total; + } + tstart = time[1]; + tend = time[2]; + } + + int segments = points_per_segment - 1; + results.push_back (points[index + 1]); + + for (int i = 1; i < segments; i++) { + double xi = __interpolate (x, time, tstart + (i * (tend - tstart)) / segments); + double yi = __interpolate (y, time, tstart + (i * (tend - tstart)) / segments); + results.push_back (Duple (xi, yi)); + } + + results.push_back (points[index + 2]); + } +}; + +} + +#endif diff --git a/libs/canvas/canvas/xfade_curve.h b/libs/canvas/canvas/xfade_curve.h new file mode 100644 index 0000000000..a0018b53b3 --- /dev/null +++ b/libs/canvas/canvas/xfade_curve.h @@ -0,0 +1,84 @@ +/* + Copyright (C) 2013 Paul Davis + Copyright (C) 2014 Robin Gareus + + 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. +*/ + +#ifndef __CANVAS_XFADECURVE_H__ +#define __CANVAS_XFADECURVE_H__ + +#include "canvas/visibility.h" +#include "canvas/curve.h" + +namespace ArdourCanvas { + +class LIBCANVAS_API XFadeCurve : public Item, public InterpolatedCurve +{ +public: + enum XFadePosition { + Start, + End, + }; + + XFadeCurve (Group *); + XFadeCurve (Group *, XFadePosition); + + void set_fade_position (XFadePosition xfp) { _xfadeposition = xfp; } + + void compute_bounding_box () const; + void render (Rect const & area, Cairo::RefPtr) const; + + void set_points_per_segment (uint32_t n); + void set_inout (Points const & in, Points const & out); + + void set_outline_color (Color c) { + begin_visual_change (); + _outline_color = c; + end_visual_change (); + }; + + void set_fill_color (Color c) { + begin_visual_change (); + _fill_color = c; + end_visual_change (); + } + +private: + struct CanvasCurve { + CanvasCurve() : n_samples(0) { } + Points points; + Points samples; + Points::size_type n_samples; + }; + + Cairo::Path * get_path(Rect const &, Cairo::RefPtr, CanvasCurve const &) const; + void close_path(Rect const &, Cairo::RefPtr, CanvasCurve const &p, bool) const; + + uint32_t points_per_segment; + + CanvasCurve _in; + CanvasCurve _out; + + XFadePosition _xfadeposition; + Color _outline_color; + Color _fill_color; + + void interpolate (); +}; + +} + +#endif diff --git a/libs/canvas/curve.cc b/libs/canvas/curve.cc index e5db740d66..451289e8ea 100644 --- a/libs/canvas/curve.cc +++ b/libs/canvas/curve.cc @@ -74,205 +74,10 @@ void Curve::interpolate () { samples.clear (); - interpolate (_points, points_per_segment, CatmullRomCentripetal, false, samples); + InterpolatedCurve::interpolate (_points, points_per_segment, CatmullRomCentripetal, false, samples); n_samples = samples.size(); } -/* Cartmull-Rom code from http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/19283471#19283471 - * - * Thanks to Ted for his Java version, which I translated into Ardour-idiomatic - * C++ here. - */ - -/** - * Calculate the same values but introduces the ability to "parameterize" the t - * values used in the calculation. This is based on Figure 3 from - * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf - * - * @param p An array of double values of length 4, where interpolation - * occurs from p1 to p2. - * @param time An array of time measures of length 4, corresponding to each - * p value. - * @param t the actual interpolation ratio from 0 to 1 representing the - * position between p1 and p2 to interpolate the value. - */ -static double -__interpolate (double p[4], double time[4], double t) -{ - const double L01 = p[0] * (time[1] - t) / (time[1] - time[0]) + p[1] * (t - time[0]) / (time[1] - time[0]); - const double L12 = p[1] * (time[2] - t) / (time[2] - time[1]) + p[2] * (t - time[1]) / (time[2] - time[1]); - const double L23 = p[2] * (time[3] - t) / (time[3] - time[2]) + p[3] * (t - time[2]) / (time[3] - time[2]); - const double L012 = L01 * (time[2] - t) / (time[2] - time[0]) + L12 * (t - time[0]) / (time[2] - time[0]); - const double L123 = L12 * (time[3] - t) / (time[3] - time[1]) + L23 * (t - time[1]) / (time[3] - time[1]); - const double C12 = L012 * (time[2] - t) / (time[2] - time[1]) + L123 * (t - time[1]) / (time[2] - time[1]); - return C12; -} - -/** - * Given a list of control points, this will create a list of points_per_segment - * points spaced uniformly along the resulting Catmull-Rom curve. - * - * @param points The list of control points, leading and ending with a - * coordinate that is only used for controling the spline and is not visualized. - * @param index The index of control point p0, where p0, p1, p2, and p3 are - * used in order to create a curve between p1 and p2. - * @param points_per_segment The total number of uniformly spaced interpolated - * points to calculate for each segment. The larger this number, the - * smoother the resulting curve. - * @param curve_type Clarifies whether the curve should use uniform, chordal - * or centripetal curve types. Uniform can produce loops, chordal can - * produce large distortions from the original lines, and centripetal is an - * optimal balance without spaces. - * @return the list of coordinates that define the CatmullRom curve - * between the points defined by index+1 and index+2. - */ -static void -_interpolate (const Points& points, Points::size_type index, int points_per_segment, Curve::SplineType curve_type, Points& results) -{ - double x[4]; - double y[4]; - double time[4]; - - for (int i = 0; i < 4; i++) { - x[i] = points[index + i].x; - y[i] = points[index + i].y; - time[i] = i; - } - - double tstart = 1; - double tend = 2; - - if (curve_type != Curve::CatmullRomUniform) { - double total = 0; - for (int i = 1; i < 4; i++) { - double dx = x[i] - x[i - 1]; - double dy = y[i] - y[i - 1]; - if (curve_type == Curve::CatmullRomCentripetal) { - total += pow (dx * dx + dy * dy, .25); - } else { - total += pow (dx * dx + dy * dy, .5); - } - time[i] = total; - } - tstart = time[1]; - tend = time[2]; - } - - int segments = points_per_segment - 1; - results.push_back (points[index + 1]); - - for (int i = 1; i < segments; i++) { - double xi = __interpolate (x, time, tstart + (i * (tend - tstart)) / segments); - double yi = __interpolate (y, time, tstart + (i * (tend - tstart)) / segments); - results.push_back (Duple (xi, yi)); - } - - results.push_back (points[index + 2]); -} - -/** - * This method will calculate the Catmull-Rom interpolation curve, returning - * it as a list of Coord coordinate objects. This method in particular - * adds the first and last control points which are not visible, but required - * for calculating the spline. - * - * @param coordinates The list of original straight line points to calculate - * an interpolation from. - * @param points_per_segment The integer number of equally spaced points to - * return along each curve. The actual distance between each - * point will depend on the spacing between the control points. - * @return The list of interpolated coordinates. - * @param curve_type Chordal (stiff), Uniform(floppy), or Centripetal(medium) - * @throws gov.ca.water.shapelite.analysis.CatmullRomException if - * points_per_segment is less than 2. - */ - -void -Curve::interpolate (const Points& coordinates, uint32_t points_per_segment, SplineType curve_type, bool closed, Points& results) -{ - if (points_per_segment < 2) { - return; - } - - // Cannot interpolate curves given only two points. Two points - // is best represented as a simple line segment. - if (coordinates.size() < 3) { - results = coordinates; - return; - } - - // Copy the incoming coordinates. We need to modify it during interpolation - Points vertices = coordinates; - - // Test whether the shape is open or closed by checking to see if - // the first point intersects with the last point. M and Z are ignored. - if (closed) { - // Use the second and second from last points as control points. - // get the second point. - Duple p2 = vertices[1]; - // get the point before the last point - Duple pn1 = vertices[vertices.size() - 2]; - - // insert the second from the last point as the first point in the list - // because when the shape is closed it keeps wrapping around to - // the second point. - vertices.insert(vertices.begin(), pn1); - // add the second point to the end. - vertices.push_back(p2); - } else { - // The shape is open, so use control points that simply extend - // the first and last segments - - // Get the change in x and y between the first and second coordinates. - double dx = vertices[1].x - vertices[0].x; - double dy = vertices[1].y - vertices[0].y; - - // Then using the change, extrapolate backwards to find a control point. - double x1 = vertices[0].x - dx; - double y1 = vertices[0].y - dy; - - // Actaully create the start point from the extrapolated values. - Duple start (x1, y1); - - // Repeat for the end control point. - int n = vertices.size() - 1; - dx = vertices[n].x - vertices[n - 1].x; - dy = vertices[n].y - vertices[n - 1].y; - double xn = vertices[n].x + dx; - double yn = vertices[n].y + dy; - Duple end (xn, yn); - - // insert the start control point at the start of the vertices list. - vertices.insert (vertices.begin(), start); - - // append the end control ponit to the end of the vertices list. - vertices.push_back (end); - } - - // When looping, remember that each cycle requires 4 points, starting - // with i and ending with i+3. So we don't loop through all the points. - - for (Points::size_type i = 0; i < vertices.size() - 3; i++) { - - // Actually calculate the Catmull-Rom curve for one segment. - Points r; - - _interpolate (vertices, i, points_per_segment, curve_type, r); - - // Since the middle points are added twice, once for each bordering - // segment, we only add the 0 index result point for the first - // segment. Otherwise we will have duplicate points. - - if (results.size() > 0) { - r.erase (r.begin()); - } - - // Add the coordinates for the segment to the result list. - - results.insert (results.end(), r.begin(), r.end()); - } -} - void Curve::render (Rect const & area, Cairo::RefPtr context) const { diff --git a/libs/canvas/wscript b/libs/canvas/wscript index 0c5192645c..913f8298fa 100644 --- a/libs/canvas/wscript +++ b/libs/canvas/wscript @@ -54,7 +54,8 @@ canvas_sources = [ 'text.cc', 'types.cc', 'utils.cc', - 'wave_view.cc' + 'wave_view.cc', + 'xfade_curve.cc', ] def options(opt): diff --git a/libs/canvas/xfade_curve.cc b/libs/canvas/xfade_curve.cc new file mode 100644 index 0000000000..0fff4aa03e --- /dev/null +++ b/libs/canvas/xfade_curve.cc @@ -0,0 +1,270 @@ +/* + Copyright (C) 2013 Paul Davis + Copyright (C) 2014 Robin Gareus + + 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 + +#include "canvas/utils.h" +#include "canvas/xfade_curve.h" +#include "canvas/interpolated_curve.h" + +using namespace ArdourCanvas; +using std::min; +using std::max; + +XFadeCurve::XFadeCurve (Group* parent) + : Item (parent) + , points_per_segment (24) + , _xfadeposition (Start) + , _outline_color (0x000000ff) + , _fill_color (0x22448880) +{ +} + +XFadeCurve::XFadeCurve (Group* parent, XFadePosition pos) + : Item (parent) + , points_per_segment (24) + , _xfadeposition (pos) + , _outline_color (0x000000ff) + , _fill_color (0x22448880) +{ +} + +void +XFadeCurve::compute_bounding_box () const +{ + if (!_in.points.empty() && !_out.points.empty()) { + + Rect bbox; + Points::const_iterator i; + + if (!_in.points.empty()) { + i = _in.points.begin(); + bbox.x0 = bbox.x1 = i->x; + bbox.y0 = bbox.y1 = i->y; + + ++i; + + while (i != _in.points.end()) { + bbox.x0 = min (bbox.x0, i->x); + bbox.y0 = min (bbox.y0, i->y); + bbox.x1 = max (bbox.x1, i->x); + bbox.y1 = max (bbox.y1, i->y); + ++i; + } + } else { + i = _out.points.begin(); + bbox.x0 = bbox.x1 = i->x; + bbox.y0 = bbox.y1 = i->y; + } + + if (!_out.points.empty()) { + i = _out.points.begin(); + while (i != _out.points.end()) { + bbox.x0 = min (bbox.x0, i->x); + bbox.y0 = min (bbox.y0, i->y); + bbox.x1 = max (bbox.x1, i->x); + bbox.y1 = max (bbox.y1, i->y); + ++i; + } + } + + _bounding_box = bbox.expand (1.0); + + } else { + _bounding_box = boost::optional (); + } + + _bounding_box_dirty = false; +} + +void +XFadeCurve::set_inout (Points const & in, Points const & out) +{ + if (_in.points == in && _out.points == out) { + return; + } + begin_change (); + _in.points = in; + _out.points = out; + _bounding_box_dirty = true; + interpolate (); + end_change (); +} + +void +XFadeCurve::set_points_per_segment (uint32_t n) +{ + points_per_segment = n; + interpolate (); + redraw (); +} + +void +XFadeCurve::interpolate () +{ + _in.samples.clear (); + InterpolatedCurve::interpolate (_in.points, points_per_segment, CatmullRomCentripetal, false, _in.samples); + _in.n_samples = _in.samples.size(); + + _out.samples.clear (); + InterpolatedCurve::interpolate (_out.points, points_per_segment, CatmullRomCentripetal, false, _out.samples); + _out.n_samples = _out.samples.size(); +} + +Cairo::Path * +XFadeCurve::get_path(Rect const & area, Cairo::RefPtr context, CanvasCurve const &c) const +{ + assert(c.points.size() > 1); + context->begin_new_path (); + Duple window_space; + + if (c.points.size () == 2) { + + window_space = item_to_window (c.points.front()); + context->move_to (window_space.x, window_space.y); + window_space = item_to_window (c.points.back()); + context->line_to (window_space.x, window_space.y); + + } else { + + /* find left and right-most sample */ + Points::size_type left = 0; + Points::size_type right = c.n_samples; + + for (Points::size_type idx = 0; idx < c.n_samples - 1; ++idx) { + left = idx; + window_space = item_to_window (Duple (c.samples[idx].x, 0.0)); + if (window_space.x >= area.x0) break; + } + for (Points::size_type idx = c.n_samples; idx > left + 1; --idx) { + window_space = item_to_window (Duple (c.samples[idx].x, 0.0)); + if (window_space.x <= area.x1) break; + right = idx; + } + + /* draw line between samples */ + window_space = item_to_window (Duple (c.samples[left].x, c.samples[left].y)); + context->move_to (window_space.x, window_space.y); + Coord last_x = round(window_space.x); + for (uint32_t idx = left + 1; idx < right; ++idx) { + window_space = item_to_window (Duple (c.samples[idx].x, c.samples[idx].y)); + if (last_x == round(window_space.x)) continue; + last_x = round(window_space.x); + context->line_to (last_x - .5 , window_space.y); + } + } + return context->copy_path (); +} + +void +XFadeCurve::close_path(Rect const & area, Cairo::RefPtr context, CanvasCurve const &c, bool inside) const +{ + Duple window_space; + if (inside) { + window_space = item_to_window (Duple(c.points.back().x, area.height())); + context->line_to (window_space.x, window_space.y); + window_space = item_to_window (Duple(c.points.front().x, area.height())); + context->line_to (window_space.x, window_space.y); + context->close_path(); + } else { + window_space = item_to_window (Duple(c.points.back().x, 0.0)); + context->line_to (window_space.x, window_space.y); + window_space = item_to_window (Duple(c.points.front().x, 0.0)); + context->line_to (window_space.x, window_space.y); + context->close_path(); + } +} + +void +XFadeCurve::render (Rect const & area, Cairo::RefPtr context) const +{ + if (!_bounding_box) { return; } + if (_in.points.size() < 2) { return; } + if (_out.points.size() < 2) { return; } + + Rect self = item_to_window (_bounding_box.get()); + boost::optional d = self.intersection (area); + assert (d); + Rect draw = d.get (); + + context->save (); + context->rectangle (draw.x0, draw.y0, draw.width(), draw.height()); + context->clip (); + + /* expand drawing area by several pixels on each side to avoid cairo stroking effects at the boundary. + * they will still occur, but cairo's clipping will hide them. + */ + draw = draw.expand (4.0); + + Cairo::Path *path_in = get_path(draw, context, _in); + Cairo::Path *path_out = get_path(draw, context, _out); + + Color outline_shaded = _outline_color; + outline_shaded = 0.5 * (outline_shaded & 0xff) + (outline_shaded & ~0xff); + + Color fill_shaded = _fill_color; + fill_shaded = 0.5 * (fill_shaded & 0xff) + (fill_shaded & ~0xff); + +#define IS (_xfadeposition == Start) + + /* fill primary fade */ + context->begin_new_path (); + context->append_path (IS ? *path_in : *path_out); + close_path(draw, context, IS ?_in : _out, false); + set_source_rgba (context, _fill_color); + context->fill (); + + /* fill background fade */ + context->save (); + context->begin_new_path (); + context->append_path (IS ? *path_in : *path_out); + close_path(draw, context, IS ? _in : _out, true); + //context->set_fill_rule (Cairo::FILL_RULE_EVEN_ODD); + context->clip (); + context->begin_new_path (); + context->append_path (IS ? *path_out: *path_in); + close_path(draw, context, IS ? _out : _in, true); + set_source_rgba (context, fill_shaded); + //context->set_fill_rule (Cairo::FILL_RULE_WINDING); + context->fill (); + context->restore (); + + /* draw lines over fills */ + set_source_rgba (context, IS ? _outline_color : outline_shaded); + context->set_line_width (IS ? 1.0 : .5); + + context->begin_new_path (); + context->append_path (*path_in); + context->stroke(); + + set_source_rgba (context, IS ? outline_shaded :_outline_color); + context->set_line_width (IS ? .5 : 1.0); + + context->begin_new_path (); + context->append_path (*path_out); + context->stroke(); + + context->restore (); + + delete path_in; + delete path_out; +}