2 * Copyright (C) 2013-2017 Paul Davis <paul@linuxaudiosystems.com>
3 * Copyright (C) 2014-2015 Robin Gareus <robin@gareus.org>
4 * Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "canvas/curve.h"
27 using namespace ArdourCanvas;
31 Curve::Curve (Canvas* c)
34 , points_per_segment (16)
39 Curve::Curve (Item* parent)
42 , points_per_segment (16)
47 /** When rendering the curve, we will always draw a fixed number of straight
48 * line segments to span the x-axis extent of the curve. More segments:
49 * smoother visual rendering. Less rendering: closer to a visibily poly-line
53 Curve::set_points_per_segment (uint32_t n)
55 /* this only changes our appearance rather than the bounding box, so we
56 just need to schedule a redraw rather than notify the parent of any
59 points_per_segment = n;
65 Curve::compute_bounding_box () const
67 PolyItem::compute_bounding_box ();
69 /* possibly add extents of any point indicators here if we ever do that */
73 Curve::set (Points const& p)
83 InterpolatedCurve::interpolate (_points, points_per_segment, CatmullRomCentripetal, false, samples);
84 n_samples = samples.size();
88 Curve::render (Rect const & area, Cairo::RefPtr<Cairo::Context> context) const
90 if (!_outline || _points.size() < 2 || !_bounding_box) {
94 Rect self = item_to_window (_bounding_box);
95 Rect d = self.intersection (area);
99 /* Our approach is to always draw n_segments across our total size.
101 * This is very inefficient if we are asked to only draw a small
102 * section of the curve. For now we rely on cairo clipping to help
107 setup_outline_context (context);
109 if (_points.size() == 2) {
115 window_space = item_to_window (_points.front());
116 context->move_to (window_space.x, window_space.y);
117 window_space = item_to_window (_points.back());
118 context->line_to (window_space.x, window_space.y);
121 switch (curve_fill) {
126 context->stroke_preserve ();
127 window_space = item_to_window (Duple(_points.back().x, draw.height()));
128 context->line_to (window_space.x, window_space.y);
129 window_space = item_to_window (Duple(_points.front().x, draw.height()));
130 context->line_to (window_space.x, window_space.y);
131 context->close_path();
132 setup_fill_context(context);
136 context->stroke_preserve ();
137 window_space = item_to_window (Duple(_points.back().x, 0.0));
138 context->line_to (window_space.x, window_space.y);
139 window_space = item_to_window (Duple(_points.front().x, 0.0));
140 context->line_to (window_space.x, window_space.y);
141 context->close_path();
142 setup_fill_context(context);
149 /* curve of at least 3 points */
151 /* x-axis limits of the curve, in window space coordinates */
153 Duple w1 = item_to_window (Duple (_points.front().x, 0.0));
154 Duple w2 = item_to_window (Duple (_points.back().x, 0.0));
156 /* clamp actual draw to area bound by points, rather than our bounding box which is slightly different */
159 context->rectangle (draw.x0, draw.y0, draw.width(), draw.height());
162 /* expand drawing area by several pixels on each side to avoid cairo stroking effects at the boundary.
163 they will still occur, but cairo's clipping will hide them.
166 draw = draw.expand (4.0);
168 /* now clip it to the actual points in the curve */
170 if (draw.x0 < w1.x) {
174 if (draw.x1 >= w2.x) {
178 /* find left and right-most sample */
180 Points::size_type left = 0;
181 Points::size_type right = n_samples;
183 for (Points::size_type idx = 0; idx < n_samples - 1; ++idx) {
185 window_space = item_to_window (Duple (samples[idx].x, 0.0));
186 if (window_space.x >= draw.x0) break;
188 for (Points::size_type idx = n_samples; idx > left + 1; --idx) {
189 window_space = item_to_window (Duple (samples[idx].x, 0.0));
190 if (window_space.x <= draw.x1) break;
194 /* draw line between samples */
195 window_space = item_to_window (Duple (samples[left].x, samples[left].y));
196 context->move_to (window_space.x, window_space.y);
197 for (uint32_t idx = left + 1; idx < right; ++idx) {
198 window_space = item_to_window (Duple (samples[idx].x, samples[idx].y));
199 context->line_to (window_space.x, window_space.y);
202 switch (curve_fill) {
207 context->stroke_preserve ();
208 window_space = item_to_window (Duple (samples[right-1].x, draw.height()));
209 context->line_to (window_space.x, window_space.y);
210 window_space = item_to_window (Duple (samples[left].x, draw.height()));
211 context->line_to (window_space.x, window_space.y);
212 context->close_path();
213 setup_fill_context(context);
217 context->stroke_preserve ();
218 window_space = item_to_window (Duple (samples[right-1].x, 0.0));
219 context->line_to (window_space.x, window_space.y);
220 window_space = item_to_window (Duple (samples[left].x, 0.0));
221 context->line_to (window_space.x, window_space.y);
222 context->close_path();
223 setup_fill_context(context);
232 setup_outline_context (context);
233 for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) {
234 Duple window_space (item_to_window (*p));
235 context->arc (window_space.x, window_space.y, 5.0, 0.0, 2 * M_PI);
242 Curve::covers (Duple const & pc) const
244 Duple point = window_to_item (pc);
246 /* O(N) N = number of points, and not accurate */
248 for (Points::const_iterator p = _points.begin(); p != _points.end(); ++p) {
250 const Coord dx = point.x - (*p).x;
251 const Coord dy = point.y - (*p).y;
252 const Coord dx2 = dx * dx;
253 const Coord dy2 = dy * dy;
255 if ((dx2 < 2.0 && dy2 < 2.0) || (dx2 + dy2 < 4.0)) {