summaryrefslogtreecommitdiff
path: root/src/cairo-arc.c
blob: 390397bae10426d2be99da45ae1717c7832207c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
/* cairo - a vector graphics library with display and print output
 *
 * Copyright © 2002 University of Southern California
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is University of Southern
 * California.
 *
 * Contributor(s):
 *	Carl D. Worth <cworth@cworth.org>
 */

#include "cairoint.h"

#include "cairo-arc-private.h"

#define MAX_FULL_CIRCLES 65536

/* Spline deviation from the circle in radius would be given by:

	error = sqrt (x**2 + y**2) - 1

   A simpler error function to work with is:

	e = x**2 + y**2 - 1

   From "Good approximation of circles by curvature-continuous Bezier
   curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
   Design 8 (1990) 22-41, we learn:

	abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)

   and
	abs (error) =~ 1/2 * e

   Of course, this error value applies only for the particular spline
   approximation that is used in _cairo_gstate_arc_segment.
*/
static double
_arc_error_normalized (double angle)
{
    return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
}

static double
_arc_max_angle_for_tolerance_normalized (double tolerance)
{
    double angle, error;
    int i;

    /* Use table lookup to reduce search time in most cases. */
    struct {
	double angle;
	double error;
    } table[] = {
	{ M_PI / 1.0,   0.0185185185185185036127 },
	{ M_PI / 2.0,   0.000272567143730179811158 },
	{ M_PI / 3.0,   2.38647043651461047433e-05 },
	{ M_PI / 4.0,   4.2455377443222443279e-06 },
	{ M_PI / 5.0,   1.11281001494389081528e-06 },
	{ M_PI / 6.0,   3.72662000942734705475e-07 },
	{ M_PI / 7.0,   1.47783685574284411325e-07 },
	{ M_PI / 8.0,   6.63240432022601149057e-08 },
	{ M_PI / 9.0,   3.2715520137536980553e-08 },
	{ M_PI / 10.0,  1.73863223499021216974e-08 },
	{ M_PI / 11.0,  9.81410988043554039085e-09 },
    };
    int table_size = ARRAY_LENGTH (table);

    for (i = 0; i < table_size; i++)
	if (table[i].error < tolerance)
	    return table[i].angle;

    ++i;
    do {
	angle = M_PI / i++;
	error = _arc_error_normalized (angle);
    } while (error > tolerance);

    return angle;
}

static int
_arc_segments_needed (double	      angle,
		      double	      radius,
		      cairo_matrix_t *ctm,
		      double	      tolerance)
{
    double major_axis, max_angle;

    /* the error is amplified by at most the length of the
     * major axis of the circle; see cairo-pen.c for a more detailed analysis
     * of this. */
    major_axis = _cairo_matrix_transformed_circle_major_axis (ctm, radius);
    max_angle = _arc_max_angle_for_tolerance_normalized (tolerance / major_axis);

    return ceil (fabs (angle) / max_angle);
}

/* We want to draw a single spline approximating a circular arc radius
   R from angle A to angle B. Since we want a symmetric spline that
   matches the endpoints of the arc in position and slope, we know
   that the spline control points must be:

	(R * cos(A), R * sin(A))
	(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
	(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
	(R * cos(B), R * sin(B))

   for some value of h.

   "Approximation of circular arcs by cubic polynomials", Michael
   Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
   various values of h along with error analysis for each.

   From that paper, a very practical value of h is:

	h = 4/3 * R * tan(angle/4)

   This value does not give the spline with minimal error, but it does
   provide a very good approximation, (6th-order convergence), and the
   error expression is quite simple, (see the comment for
   _arc_error_normalized).
*/
static void
_cairo_arc_segment (cairo_t *cr,
		    double   xc,
		    double   yc,
		    double   radius,
		    double   angle_A,
		    double   angle_B)
{
    double r_sin_A, r_cos_A;
    double r_sin_B, r_cos_B;
    double h;

    r_sin_A = radius * sin (angle_A);
    r_cos_A = radius * cos (angle_A);
    r_sin_B = radius * sin (angle_B);
    r_cos_B = radius * cos (angle_B);

    h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);

    cairo_curve_to (cr,
		    xc + r_cos_A - h * r_sin_A,
		    yc + r_sin_A + h * r_cos_A,
		    xc + r_cos_B + h * r_sin_B,
		    yc + r_sin_B - h * r_cos_B,
		    xc + r_cos_B,
		    yc + r_sin_B);
}

static void
_cairo_arc_in_direction (cairo_t	  *cr,
			 double		   xc,
			 double		   yc,
			 double		   radius,
			 double		   angle_min,
			 double		   angle_max,
			 cairo_direction_t dir)
{
    if (cairo_status (cr))
        return;

    assert (angle_max >= angle_min);

    if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) {
	angle_max = fmod (angle_max - angle_min, 2 * M_PI);
	angle_min = fmod (angle_min, 2 * M_PI);
	angle_max += angle_min + 2 * M_PI * MAX_FULL_CIRCLES;
    }

    /* Recurse if drawing arc larger than pi */
    if (angle_max - angle_min > M_PI) {
	double angle_mid = angle_min + (angle_max - angle_min) / 2.0;
	if (dir == CAIRO_DIRECTION_FORWARD) {
	    _cairo_arc_in_direction (cr, xc, yc, radius,
				     angle_min, angle_mid,
				     dir);

	    _cairo_arc_in_direction (cr, xc, yc, radius,
				     angle_mid, angle_max,
				     dir);
	} else {
	    _cairo_arc_in_direction (cr, xc, yc, radius,
				     angle_mid, angle_max,
				     dir);

	    _cairo_arc_in_direction (cr, xc, yc, radius,
				     angle_min, angle_mid,
				     dir);
	}
    } else if (angle_max != angle_min) {
	cairo_matrix_t ctm;
	int i, segments;
	double step;

	cairo_get_matrix (cr, &ctm);
	segments = _arc_segments_needed (angle_max - angle_min,
					 radius, &ctm,
					 cairo_get_tolerance (cr));
	step = (angle_max - angle_min) / segments;
	segments -= 1;

	if (dir == CAIRO_DIRECTION_REVERSE) {
	    double t;

	    t = angle_min;
	    angle_min = angle_max;
	    angle_max = t;

	    step = -step;
	}

	cairo_line_to (cr,
		       xc + radius * cos (angle_min),
		       yc + radius * sin (angle_min));

	for (i = 0; i < segments; i++, angle_min += step) {
	    _cairo_arc_segment (cr, xc, yc, radius,
				angle_min, angle_min + step);
	}

	_cairo_arc_segment (cr, xc, yc, radius,
			    angle_min, angle_max);
    } else {
	cairo_line_to (cr,
		       xc + radius * cos (angle_min),
		       yc + radius * sin (angle_min));
    }
}

/**
 * _cairo_arc_path:
 * @cr: a cairo context
 * @xc: X position of the center of the arc
 * @yc: Y position of the center of the arc
 * @radius: the radius of the arc
 * @angle1: the start angle, in radians
 * @angle2: the end angle, in radians
 *
 * Compute a path for the given arc and append it onto the current
 * path within @cr. The arc will be accurate within the current
 * tolerance and given the current transformation.
 **/
void
_cairo_arc_path (cairo_t *cr,
		 double	  xc,
		 double	  yc,
		 double	  radius,
		 double	  angle1,
		 double	  angle2)
{
    _cairo_arc_in_direction (cr, xc, yc,
			     radius,
			     angle1, angle2,
			     CAIRO_DIRECTION_FORWARD);
}

/**
 * _cairo_arc_path_negative:
 * @xc: X position of the center of the arc
 * @yc: Y position of the center of the arc
 * @radius: the radius of the arc
 * @angle1: the start angle, in radians
 * @angle2: the end angle, in radians
 * @ctm: the current transformation matrix
 * @tolerance: the current tolerance value
 * @path: the path onto which the arc will be appended
 *
 * Compute a path for the given arc (defined in the negative
 * direction) and append it onto the current path within @cr. The arc
 * will be accurate within the current tolerance and given the current
 * transformation.
 **/
void
_cairo_arc_path_negative (cairo_t *cr,
			  double   xc,
			  double   yc,
			  double   radius,
			  double   angle1,
			  double   angle2)
{
    _cairo_arc_in_direction (cr, xc, yc,
			     radius,
			     angle2, angle1,
			     CAIRO_DIRECTION_REVERSE);
}