merge with master.
[ardour.git] / libs / canvas / xfade_curve.cc
1 /*
2     Copyright (C) 2013 Paul Davis
3     Copyright (C) 2014 Robin Gareus <robin@gareus.org>
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18
19 */
20
21 #include <cmath>
22 #include <exception>
23 #include <algorithm>
24
25 #include "canvas/xfade_curve.h"
26 #include "canvas/interpolated_curve.h"
27 #include "canvas/utils.h"
28
29 using namespace ArdourCanvas;
30 using std::min;
31 using std::max;
32
33 XFadeCurve::XFadeCurve (Canvas* c)
34         : Item (c)
35         , points_per_segment (32)
36         , _xfadeposition (Start)
37         , _outline_color (0x000000ff)
38         , _fill_color (0x22448880)
39 {
40 }
41
42 XFadeCurve::XFadeCurve (Canvas* c, XFadePosition pos)
43         : Item (c)
44         , points_per_segment (32)
45         , _xfadeposition (pos)
46         , _outline_color (0x000000ff)
47         , _fill_color (0x22448880)
48 {
49 }
50
51 XFadeCurve::XFadeCurve (Item* parent)
52         : Item (parent)
53         , points_per_segment (32)
54         , _xfadeposition (Start)
55         , _outline_color (0x000000ff)
56         , _fill_color (0x22448880)
57 {
58 }
59
60 XFadeCurve::XFadeCurve (Item* parent, XFadePosition pos)
61         : Item (parent)
62         , points_per_segment (32)
63         , _xfadeposition (pos)
64         , _outline_color (0x000000ff)
65         , _fill_color (0x22448880)
66 {
67 }
68
69 void
70 XFadeCurve::compute_bounding_box () const
71 {
72         if (!_in.points.empty() && !_out.points.empty()) {
73
74                 Rect bbox;
75                 Points::const_iterator i;
76
77                 if (!_in.points.empty()) {
78                         i = _in.points.begin();
79                         bbox.x0 = bbox.x1 = i->x;
80                         bbox.y0 = bbox.y1 = i->y;
81
82                         ++i;
83
84                         while (i != _in.points.end()) {
85                                 bbox.x0 = min (bbox.x0, i->x);
86                                 bbox.y0 = min (bbox.y0, i->y);
87                                 bbox.x1 = max (bbox.x1, i->x);
88                                 bbox.y1 = max (bbox.y1, i->y);
89                                 ++i;
90                         }
91                 } else {
92                         i = _out.points.begin();
93                         bbox.x0 = bbox.x1 = i->x;
94                         bbox.y0 = bbox.y1 = i->y;
95                 }
96
97                 if (!_out.points.empty()) {
98                         i = _out.points.begin();
99                         while (i != _out.points.end()) {
100                                 bbox.x0 = min (bbox.x0, i->x);
101                                 bbox.y0 = min (bbox.y0, i->y);
102                                 bbox.x1 = max (bbox.x1, i->x);
103                                 bbox.y1 = max (bbox.y1, i->y);
104                                 ++i;
105                         }
106                 }
107
108                 _bounding_box = bbox.expand (1.0);
109
110         } else {
111                 _bounding_box = boost::optional<Rect> ();
112         }
113
114         _bounding_box_dirty = false;
115 }
116
117 void
118 XFadeCurve::set_inout (Points const & in, Points const & out)
119 {
120         if (_in.points == in && _out.points == out) {
121                 return;
122         }
123         begin_change ();
124         _in.points = in;
125         _out.points = out;
126         _bounding_box_dirty = true;
127         interpolate ();
128         end_change ();
129 }
130
131 void
132 XFadeCurve::set_points_per_segment (uint32_t n)
133 {
134         points_per_segment = n;
135         interpolate ();
136         redraw ();
137 }
138
139 void
140 XFadeCurve::interpolate ()
141 {
142         _in.samples.clear ();
143         InterpolatedCurve::interpolate (_in.points, points_per_segment, CatmullRomCentripetal, false, _in.samples);
144         _in.n_samples = _in.samples.size();
145
146         _out.samples.clear ();
147         InterpolatedCurve::interpolate (_out.points, points_per_segment, CatmullRomCentripetal, false, _out.samples);
148         _out.n_samples = _out.samples.size();
149 }
150
151 Cairo::Path *
152 XFadeCurve::get_path(Rect const & area, Cairo::RefPtr<Cairo::Context> context, CanvasCurve const &c) const
153 {
154         assert(c.points.size() > 1);
155         context->begin_new_path ();
156         Duple window_space;
157
158         if (c.points.size () == 2) {
159
160                 window_space = item_to_window (c.points.front(), false);
161                 context->move_to (window_space.x, window_space.y);
162                 window_space = item_to_window (c.points.back(), false);
163                 context->line_to (window_space.x, window_space.y);
164
165         } else {
166
167                 /* find left and right-most sample */
168                 Points::size_type left = 0;
169                 Points::size_type right = c.n_samples;
170
171                 for (Points::size_type idx = 0; idx < c.n_samples - 1; ++idx) {
172                         left = idx;
173                         window_space = item_to_window (Duple (c.samples[idx].x, 0.0), false);
174                         if (window_space.x >= area.x0) break;
175                 }
176                 for (Points::size_type idx = c.n_samples; idx > left + 1; --idx) {
177                         window_space = item_to_window (Duple (c.samples[idx].x, 0.0), false);
178                         if (window_space.x <= area.x1) break;
179                         right = idx;
180                 }
181
182                 /* draw line between samples */
183                 window_space = item_to_window (Duple (c.samples[left].x, c.samples[left].y), false);
184                 context->move_to (window_space.x, window_space.y);
185                 for (uint32_t idx = left + 1; idx < right; ++idx) {
186                         window_space = item_to_window (Duple (c.samples[idx].x, c.samples[idx].y), false);
187                         context->line_to (window_space.x, window_space.y);
188                 }
189         }
190         return context->copy_path ();
191 }
192
193 void
194 XFadeCurve::close_path(Rect const & area, Cairo::RefPtr<Cairo::Context> context, CanvasCurve const &c, bool inside) const
195 {
196         Duple window_space;
197         if (inside) {
198                 window_space = item_to_window (Duple(c.points.back().x, area.height()), false);
199                 context->line_to (window_space.x, window_space.y);
200                 window_space = item_to_window (Duple(c.points.front().x, area.height()), false);
201                 context->line_to (window_space.x, window_space.y);
202                 context->close_path();
203         } else {
204                 window_space = item_to_window (Duple(c.points.back().x, 0.0), false);
205                 context->line_to (window_space.x, window_space.y);
206                 window_space = item_to_window (Duple(c.points.front().x, 0.0), false);
207                 context->line_to (window_space.x, window_space.y);
208                 context->close_path();
209         }
210 }
211
212 void
213 XFadeCurve::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
214 {
215         if (!_bounding_box) { return; }
216         if (_in.points.size() < 2) { return; }
217         if (_out.points.size() < 2) { return; }
218
219         Rect self = item_to_window (_bounding_box.get());
220         boost::optional<Rect> d = self.intersection (area);
221         assert (d);
222         Rect draw = d.get ();
223
224         context->save ();
225         context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
226         context->clip ();
227
228         /* expand drawing area by several pixels on each side to avoid cairo stroking effects at the boundary.
229          * they will still occur, but cairo's clipping will hide them.
230          */
231         draw = draw.expand (4.0);
232
233         Cairo::Path *path_in = get_path(draw, context, _in);
234         Cairo::Path *path_out = get_path(draw, context, _out);
235
236         Color outline_shaded = _outline_color;
237         outline_shaded = 0.5 * (outline_shaded & 0xff) + (outline_shaded & ~0xff);
238
239         Color fill_shaded = _fill_color;
240         fill_shaded = 0.5 * (fill_shaded & 0xff) + (fill_shaded & ~0xff);
241
242 #define IS (_xfadeposition == Start)
243
244         /* fill primary fade */
245         context->begin_new_path ();
246         context->append_path (IS ? *path_in : *path_out);
247         close_path(draw, context, IS ?_in : _out, false);
248         set_source_rgba (context, _fill_color);
249         context->fill ();
250
251         /* fill background fade */
252         context->save ();
253         context->begin_new_path ();
254         context->append_path (IS ? *path_in : *path_out);
255         close_path(draw, context, IS ? _in : _out, true);
256         context->set_fill_rule (Cairo::FILL_RULE_EVEN_ODD);
257         context->clip ();
258         context->begin_new_path ();
259         context->append_path (IS ? *path_out: *path_in);
260         close_path(draw, context, IS ? _out : _in, true);
261         set_source_rgba (context, fill_shaded);
262         context->set_fill_rule (Cairo::FILL_RULE_WINDING);
263         context->fill ();
264         context->restore ();
265
266         /* draw lines over fills */
267         set_source_rgba (context, IS ? _outline_color : outline_shaded);
268         context->set_line_width (IS ? 1.0 : .5);
269
270         context->begin_new_path ();
271         context->append_path (*path_in);
272         context->stroke();
273
274         set_source_rgba (context, IS ? outline_shaded :_outline_color);
275         context->set_line_width (IS ? .5 : 1.0);
276
277         context->begin_new_path ();
278         context->append_path (*path_out);
279         context->stroke();
280
281         context->restore ();
282
283         delete path_in;
284         delete path_out;
285 }