/work/workdir/UnpackedTarball/cairo/src/cairo-arc.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* cairo - a vector graphics library with display and print output |
2 | | * |
3 | | * Copyright © 2002 University of Southern California |
4 | | * |
5 | | * This library is free software; you can redistribute it and/or |
6 | | * modify it either under the terms of the GNU Lesser General Public |
7 | | * License version 2.1 as published by the Free Software Foundation |
8 | | * (the "LGPL") or, at your option, under the terms of the Mozilla |
9 | | * Public License Version 1.1 (the "MPL"). If you do not alter this |
10 | | * notice, a recipient may use your version of this file under either |
11 | | * the MPL or the LGPL. |
12 | | * |
13 | | * You should have received a copy of the LGPL along with this library |
14 | | * in the file COPYING-LGPL-2.1; if not, write to the Free Software |
15 | | * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA |
16 | | * You should have received a copy of the MPL along with this library |
17 | | * in the file COPYING-MPL-1.1 |
18 | | * |
19 | | * The contents of this file are subject to the Mozilla Public License |
20 | | * Version 1.1 (the "License"); you may not use this file except in |
21 | | * compliance with the License. You may obtain a copy of the License at |
22 | | * http://www.mozilla.org/MPL/ |
23 | | * |
24 | | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY |
25 | | * OF ANY KIND, either express or implied. See the LGPL or the MPL for |
26 | | * the specific language governing rights and limitations. |
27 | | * |
28 | | * The Original Code is the cairo graphics library. |
29 | | * |
30 | | * The Initial Developer of the Original Code is University of Southern |
31 | | * California. |
32 | | * |
33 | | * Contributor(s): |
34 | | * Carl D. Worth <cworth@cworth.org> |
35 | | */ |
36 | | |
37 | | #include "cairoint.h" |
38 | | |
39 | | #include "cairo-arc-private.h" |
40 | | |
41 | 0 | #define MAX_FULL_CIRCLES 65536 |
42 | | |
43 | | /* Spline deviation from the circle in radius would be given by: |
44 | | |
45 | | error = sqrt (x**2 + y**2) - 1 |
46 | | |
47 | | A simpler error function to work with is: |
48 | | |
49 | | e = x**2 + y**2 - 1 |
50 | | |
51 | | From "Good approximation of circles by curvature-continuous Bezier |
52 | | curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric |
53 | | Design 8 (1990) 22-41, we learn: |
54 | | |
55 | | abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4) |
56 | | |
57 | | and |
58 | | abs (error) =~ 1/2 * e |
59 | | |
60 | | Of course, this error value applies only for the particular spline |
61 | | approximation that is used in _cairo_gstate_arc_segment. |
62 | | */ |
63 | | static double |
64 | | _arc_error_normalized (double angle) |
65 | 0 | { |
66 | 0 | return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2); |
67 | 0 | } |
68 | | |
69 | | static double |
70 | | _arc_max_angle_for_tolerance_normalized (double tolerance) |
71 | 0 | { |
72 | 0 | double angle, error; |
73 | 0 | int i; |
74 | | |
75 | | /* Use table lookup to reduce search time in most cases. */ |
76 | 0 | struct { |
77 | 0 | double angle; |
78 | 0 | double error; |
79 | 0 | } table[] = { |
80 | 0 | { M_PI / 1.0, 0.0185185185185185036127 }, |
81 | 0 | { M_PI / 2.0, 0.000272567143730179811158 }, |
82 | 0 | { M_PI / 3.0, 2.38647043651461047433e-05 }, |
83 | 0 | { M_PI / 4.0, 4.2455377443222443279e-06 }, |
84 | 0 | { M_PI / 5.0, 1.11281001494389081528e-06 }, |
85 | 0 | { M_PI / 6.0, 3.72662000942734705475e-07 }, |
86 | 0 | { M_PI / 7.0, 1.47783685574284411325e-07 }, |
87 | 0 | { M_PI / 8.0, 6.63240432022601149057e-08 }, |
88 | 0 | { M_PI / 9.0, 3.2715520137536980553e-08 }, |
89 | 0 | { M_PI / 10.0, 1.73863223499021216974e-08 }, |
90 | 0 | { M_PI / 11.0, 9.81410988043554039085e-09 }, |
91 | 0 | }; |
92 | 0 | int table_size = ARRAY_LENGTH (table); |
93 | 0 | const int max_segments = 1000; /* this value is chosen arbitrarily. this gives an error of about 1.74909e-20 */ |
94 | |
|
95 | 0 | for (i = 0; i < table_size; i++) |
96 | 0 | if (table[i].error < tolerance) |
97 | 0 | return table[i].angle; |
98 | | |
99 | 0 | ++i; |
100 | |
|
101 | 0 | do { |
102 | 0 | angle = M_PI / i++; |
103 | 0 | error = _arc_error_normalized (angle); |
104 | 0 | } while (error > tolerance && i < max_segments); |
105 | |
|
106 | 0 | return angle; |
107 | 0 | } |
108 | | |
109 | | static int |
110 | | _arc_segments_needed (double angle, |
111 | | double radius, |
112 | | cairo_matrix_t *ctm, |
113 | | double tolerance) |
114 | 0 | { |
115 | 0 | double major_axis, max_angle; |
116 | | |
117 | | /* the error is amplified by at most the length of the |
118 | | * major axis of the circle; see cairo-pen.c for a more detailed analysis |
119 | | * of this. */ |
120 | 0 | major_axis = _cairo_matrix_transformed_circle_major_axis (ctm, radius); |
121 | 0 | max_angle = _arc_max_angle_for_tolerance_normalized (tolerance / major_axis); |
122 | |
|
123 | 0 | return ceil (fabs (angle) / max_angle); |
124 | 0 | } |
125 | | |
126 | | /* We want to draw a single spline approximating a circular arc radius |
127 | | R from angle A to angle B. Since we want a symmetric spline that |
128 | | matches the endpoints of the arc in position and slope, we know |
129 | | that the spline control points must be: |
130 | | |
131 | | (R * cos(A), R * sin(A)) |
132 | | (R * cos(A) - h * sin(A), R * sin(A) + h * cos (A)) |
133 | | (R * cos(B) + h * sin(B), R * sin(B) - h * cos (B)) |
134 | | (R * cos(B), R * sin(B)) |
135 | | |
136 | | for some value of h. |
137 | | |
138 | | "Approximation of circular arcs by cubic polynomials", Michael |
139 | | Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides |
140 | | various values of h along with error analysis for each. |
141 | | |
142 | | From that paper, a very practical value of h is: |
143 | | |
144 | | h = 4/3 * R * tan(angle/4) |
145 | | |
146 | | This value does not give the spline with minimal error, but it does |
147 | | provide a very good approximation, (6th-order convergence), and the |
148 | | error expression is quite simple, (see the comment for |
149 | | _arc_error_normalized). |
150 | | */ |
151 | | static void |
152 | | _cairo_arc_segment (cairo_t *cr, |
153 | | double xc, |
154 | | double yc, |
155 | | double radius, |
156 | | double angle_A, |
157 | | double angle_B) |
158 | 0 | { |
159 | 0 | double r_sin_A, r_cos_A; |
160 | 0 | double r_sin_B, r_cos_B; |
161 | 0 | double h; |
162 | |
|
163 | 0 | r_sin_A = radius * sin (angle_A); |
164 | 0 | r_cos_A = radius * cos (angle_A); |
165 | 0 | r_sin_B = radius * sin (angle_B); |
166 | 0 | r_cos_B = radius * cos (angle_B); |
167 | |
|
168 | 0 | h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0); |
169 | |
|
170 | 0 | cairo_curve_to (cr, |
171 | 0 | xc + r_cos_A - h * r_sin_A, |
172 | 0 | yc + r_sin_A + h * r_cos_A, |
173 | 0 | xc + r_cos_B + h * r_sin_B, |
174 | 0 | yc + r_sin_B - h * r_cos_B, |
175 | 0 | xc + r_cos_B, |
176 | 0 | yc + r_sin_B); |
177 | 0 | } |
178 | | |
179 | | static void |
180 | | _cairo_arc_in_direction (cairo_t *cr, |
181 | | double xc, |
182 | | double yc, |
183 | | double radius, |
184 | | double angle_min, |
185 | | double angle_max, |
186 | | cairo_direction_t dir) |
187 | 0 | { |
188 | 0 | if (cairo_status (cr)) |
189 | 0 | return; |
190 | | |
191 | 0 | assert (angle_max >= angle_min); |
192 | | |
193 | 0 | if (angle_max - angle_min > 2 * M_PI * MAX_FULL_CIRCLES) { |
194 | 0 | angle_max = fmod (angle_max - angle_min, 2 * M_PI); |
195 | 0 | angle_min = fmod (angle_min, 2 * M_PI); |
196 | 0 | angle_max += angle_min + 2 * M_PI * MAX_FULL_CIRCLES; |
197 | 0 | } |
198 | | |
199 | | /* Recurse if drawing arc larger than pi */ |
200 | 0 | if (angle_max - angle_min > M_PI) { |
201 | 0 | double angle_mid = angle_min + (angle_max - angle_min) / 2.0; |
202 | 0 | if (dir == CAIRO_DIRECTION_FORWARD) { |
203 | 0 | _cairo_arc_in_direction (cr, xc, yc, radius, |
204 | 0 | angle_min, angle_mid, |
205 | 0 | dir); |
206 | |
|
207 | 0 | _cairo_arc_in_direction (cr, xc, yc, radius, |
208 | 0 | angle_mid, angle_max, |
209 | 0 | dir); |
210 | 0 | } else { |
211 | 0 | _cairo_arc_in_direction (cr, xc, yc, radius, |
212 | 0 | angle_mid, angle_max, |
213 | 0 | dir); |
214 | |
|
215 | 0 | _cairo_arc_in_direction (cr, xc, yc, radius, |
216 | 0 | angle_min, angle_mid, |
217 | 0 | dir); |
218 | 0 | } |
219 | 0 | } else if (angle_max != angle_min) { |
220 | 0 | cairo_matrix_t ctm; |
221 | 0 | int i, segments; |
222 | 0 | double step; |
223 | |
|
224 | 0 | cairo_get_matrix (cr, &ctm); |
225 | 0 | segments = _arc_segments_needed (angle_max - angle_min, |
226 | 0 | radius, &ctm, |
227 | 0 | cairo_get_tolerance (cr)); |
228 | 0 | step = (angle_max - angle_min) / segments; |
229 | 0 | segments -= 1; |
230 | |
|
231 | 0 | if (dir == CAIRO_DIRECTION_REVERSE) { |
232 | 0 | double t; |
233 | |
|
234 | 0 | t = angle_min; |
235 | 0 | angle_min = angle_max; |
236 | 0 | angle_max = t; |
237 | |
|
238 | 0 | step = -step; |
239 | 0 | } |
240 | |
|
241 | 0 | cairo_line_to (cr, |
242 | 0 | xc + radius * cos (angle_min), |
243 | 0 | yc + radius * sin (angle_min)); |
244 | |
|
245 | 0 | for (i = 0; i < segments; i++, angle_min += step) { |
246 | 0 | _cairo_arc_segment (cr, xc, yc, radius, |
247 | 0 | angle_min, angle_min + step); |
248 | 0 | } |
249 | |
|
250 | 0 | _cairo_arc_segment (cr, xc, yc, radius, |
251 | 0 | angle_min, angle_max); |
252 | 0 | } else { |
253 | 0 | cairo_line_to (cr, |
254 | 0 | xc + radius * cos (angle_min), |
255 | 0 | yc + radius * sin (angle_min)); |
256 | 0 | } |
257 | 0 | } |
258 | | |
259 | | /** |
260 | | * _cairo_arc_path: |
261 | | * @cr: a cairo context |
262 | | * @xc: X position of the center of the arc |
263 | | * @yc: Y position of the center of the arc |
264 | | * @radius: the radius of the arc |
265 | | * @angle1: the start angle, in radians |
266 | | * @angle2: the end angle, in radians |
267 | | * |
268 | | * Compute a path for the given arc and append it onto the current |
269 | | * path within @cr. The arc will be accurate within the current |
270 | | * tolerance and given the current transformation. |
271 | | **/ |
272 | | void |
273 | | _cairo_arc_path (cairo_t *cr, |
274 | | double xc, |
275 | | double yc, |
276 | | double radius, |
277 | | double angle1, |
278 | | double angle2) |
279 | 0 | { |
280 | 0 | _cairo_arc_in_direction (cr, xc, yc, |
281 | 0 | radius, |
282 | 0 | angle1, angle2, |
283 | 0 | CAIRO_DIRECTION_FORWARD); |
284 | 0 | } |
285 | | |
286 | | /** |
287 | | * _cairo_arc_path_negative: |
288 | | * @xc: X position of the center of the arc |
289 | | * @yc: Y position of the center of the arc |
290 | | * @radius: the radius of the arc |
291 | | * @angle1: the start angle, in radians |
292 | | * @angle2: the end angle, in radians |
293 | | * @ctm: the current transformation matrix |
294 | | * @tolerance: the current tolerance value |
295 | | * @path: the path onto which the arc will be appended |
296 | | * |
297 | | * Compute a path for the given arc (defined in the negative |
298 | | * direction) and append it onto the current path within @cr. The arc |
299 | | * will be accurate within the current tolerance and given the current |
300 | | * transformation. |
301 | | **/ |
302 | | void |
303 | | _cairo_arc_path_negative (cairo_t *cr, |
304 | | double xc, |
305 | | double yc, |
306 | | double radius, |
307 | | double angle1, |
308 | | double angle2) |
309 | 0 | { |
310 | 0 | _cairo_arc_in_direction (cr, xc, yc, |
311 | 0 | radius, |
312 | 0 | angle2, angle1, |
313 | 0 | CAIRO_DIRECTION_REVERSE); |
314 | 0 | } |