tizen 2.3.1 release
[framework/graphics/cairo.git] / src / cairo-gl-hairline-stroke.c
1 /* cairo - a vector graphics library with display and print output
2  *
3  * Copyright © 2012 Samsung Electronics
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 Red Hat, Inc.
31  *
32  * Contributor(s):
33  *      Henry Song <hsong@sisa.samsung.com>
34  */
35
36 #include "cairo-gl-private.h"
37 #include "cairo-slope-private.h"
38
39 #define SCALE_TOLERANCE 0.0000001
40 #define POINT_ADJUST 0.5
41
42 static inline cairo_bool_t
43 _add_cap (cairo_gl_hairline_closure_t *hairline,
44           double                    slope_dx,
45           double                    slope_dy,
46           cairo_bool_t      lead_cap,
47           cairo_point_t     *outp)
48 {
49     double dx, dy;
50
51     if (hairline->cap_style == CAIRO_LINE_CAP_BUTT)
52         return FALSE;
53
54     dx = slope_dx * POINT_ADJUST;
55     dy = slope_dy * POINT_ADJUST;
56     hairline->line_last_capped = lead_cap;
57     cairo_matrix_transform_distance (hairline->ctm, &dx, &dy);
58     outp->x += _cairo_fixed_from_double (dx);
59     outp->y += _cairo_fixed_from_double (dy);
60
61     return TRUE;
62 }
63
64 static inline cairo_bool_t
65 _compute_normalized_device_slope (double *dx, double *dy,
66                                   const cairo_matrix_t *ctm_inverse,
67                                   double *mag_out)
68 {
69     double dx0 = *dx, dy0 = *dy;
70     double mag;
71
72     cairo_matrix_transform_distance (ctm_inverse, &dx0, &dy0);
73
74     if (dx0 == 0.0 && dy0 == 0.0) {
75         if (mag_out)
76             *mag_out = 0.0;
77         return FALSE;
78     }
79
80     if (dx0 == 0.0) {
81         *dx = 0.0;
82         if (dy0 > 0.0) {
83             mag = dy0;
84             *dy = 1.0;
85         } else {
86             mag = -dy0;
87             *dy = -1.0;
88         }
89     } else if (dy0 == 0.0) {
90         *dy = 0.0;
91         if (dx0 > 0.0) {
92             mag = dx0;
93             *dx = 1.0;
94         } else {
95             mag = -dx0;
96             *dx = -1.0;
97         }
98     } else {
99         mag = hypot (dx0, dy0);
100         *dx = dx0 / mag;
101         *dy = dy0 / mag;
102     }
103
104     if (mag_out)
105         *mag_out = mag;
106
107     return TRUE;
108 }
109
110 /* We implement hairline optimization for stroke. */
111 cairo_bool_t
112 _cairo_gl_hairline_style_is_hairline (const cairo_stroke_style_t *style,
113                                       const cairo_matrix_t       *ctm)
114 {
115     double x, y;
116     cairo_status_t status = _cairo_matrix_compute_basis_scale_factors (ctm, &x, &y, TRUE);
117
118     if (unlikely (status))
119         return FALSE;
120
121     x = fabs (x - 1.0);
122     y = fabs (y - 1.0);
123
124     return style->line_width == 1.0 &&
125         (style->line_join != CAIRO_LINE_JOIN_MITER ||
126          style->miter_limit <= 10.0) &&
127         (x <= SCALE_TOLERANCE && y <= SCALE_TOLERANCE);
128 }
129
130 static cairo_status_t
131 _path_add_first_and_last_cap (cairo_gl_hairline_closure_t *hairline)
132 {
133     cairo_point_t p[2];
134     cairo_status_t status = CAIRO_STATUS_SUCCESS;
135     cairo_bool_t needs_to_cap;
136
137     /* check last point */
138     if (hairline->initialized) {
139         if (! hairline->line_last_capped) {
140             p[0] = hairline->line_last_point;
141             p[1] = hairline->line_last_point;
142             needs_to_cap = _add_cap (hairline,
143                                      hairline->line_last_dx,
144                                      hairline->line_last_dy,
145                                      FALSE,
146                                      &p[1]);
147             if (needs_to_cap) {
148                 status = _cairo_gl_composite_emit_point_as_single_line (hairline->ctx, p);
149                 if (unlikely(status))
150                     return status;
151             }
152         }
153         if (! hairline->stroke_first_capped) {
154             p[0] = hairline->stroke_first_point;
155             p[1] = hairline->stroke_first_point;
156             needs_to_cap = _add_cap (hairline,
157                                      hairline->stroke_first_dx,
158                                      hairline->stroke_first_dy,
159                                      TRUE,
160                                      &p[0]);
161
162             if (needs_to_cap) {
163                 status = _cairo_gl_composite_emit_point_as_single_line (hairline->ctx, p);
164                 if (unlikely(status))
165                     return status;
166             }
167         }
168     }
169
170     return CAIRO_STATUS_SUCCESS;
171 }
172
173 cairo_status_t
174 _cairo_gl_hairline_move_to (void *closure,
175                             const cairo_point_t *point)
176 {
177     cairo_status_t status;
178
179     cairo_gl_hairline_closure_t *hairline = (cairo_gl_hairline_closure_t *)closure;
180
181     hairline->current_point = *point;
182     hairline->moved_to_stroke_first_point = FALSE;
183
184     /* check last point */
185     if (hairline->initialized) {
186         status = _path_add_first_and_last_cap (hairline);
187         if (unlikely(status))
188             return status;
189     }
190
191     hairline->line_last_capped = TRUE;
192     hairline->stroke_first_capped = FALSE;
193     hairline->stroke_first_point = *point;
194     hairline->initialized = TRUE;
195
196     if (hairline->dash.dashed) {
197         _cairo_stroker_dash_start (&hairline->dash);
198     }
199
200     return CAIRO_STATUS_SUCCESS;
201 }
202
203 cairo_status_t
204 _cairo_gl_hairline_line_to (void *closure,
205                             const cairo_point_t *point)
206 {
207     double slope_dx, slope_dy;
208     double mag;
209     cairo_gl_hairline_closure_t *hairline = (cairo_gl_hairline_closure_t *)closure;
210     cairo_point_t *p1 = &hairline->current_point;
211     cairo_slope_t dev_slope;
212     cairo_point_t p[2];
213
214     _cairo_slope_init (&dev_slope, p1, point);
215     slope_dx = _cairo_fixed_to_double (point->x - p1->x);
216     slope_dy = _cairo_fixed_to_double (point->y - p1->y);
217
218     if (! _compute_normalized_device_slope (&slope_dx, &slope_dy,
219                                             hairline->ctm_inverse, &mag))
220         return CAIRO_STATUS_SUCCESS;
221
222     hairline->line_last_point = *point;
223     hairline->line_last_capped = FALSE;
224     hairline->line_last_dx = slope_dx;
225     hairline->line_last_dy = slope_dy;
226
227     if (! hairline->moved_to_stroke_first_point) {
228         hairline->stroke_first_dx = slope_dx;
229         hairline->stroke_first_dy = slope_dy;
230         hairline->moved_to_stroke_first_point = TRUE;
231     }
232
233     p[0] = hairline->current_point;
234     p[1] = *point;
235     hairline->current_point = *point;
236
237     return _cairo_gl_composite_emit_point_as_single_line (hairline->ctx, p);
238 }
239
240 cairo_status_t
241 _cairo_gl_hairline_line_to_dashed (void *closure,
242                                    const cairo_point_t *point)
243 {
244     cairo_point_t p[2];
245     double remain, mag, step_length = 0;
246     double slope_dx, slope_dy;
247     double dx, dy;
248     cairo_line_t segment;
249     cairo_gl_hairline_closure_t *hairline = (cairo_gl_hairline_closure_t *)closure;
250     cairo_point_t *p1 = &hairline->current_point;
251     cairo_slope_t dev_slope;
252     cairo_status_t status;
253     cairo_bool_t needs_to_cap;
254
255     _cairo_slope_init (&dev_slope, p1, point);
256     slope_dx = _cairo_fixed_to_double (point->x - p1->x);
257     slope_dy = _cairo_fixed_to_double (point->y - p1->y);
258
259     if (! _compute_normalized_device_slope (&slope_dx, &slope_dy,
260                                             hairline->ctm_inverse, &mag))
261         return CAIRO_STATUS_SUCCESS;
262
263     remain = mag;
264     segment.p1 = *p1;
265     while (remain) {
266         step_length = MIN (hairline->dash.dash_remain, remain);
267         remain -= step_length;
268         dx = slope_dx * (mag - remain);
269         dy = slope_dy * (mag - remain);
270         cairo_matrix_transform_distance (hairline->ctm, &dx, &dy);
271         segment.p2.x = _cairo_fixed_from_double (dx) + p1->x;
272         segment.p2.y = _cairo_fixed_from_double (dy) + p1->y;
273
274         if (hairline->dash.dash_on) {
275             p[0] = segment.p1;
276             p[1] = segment.p2;
277             /* Check leading cap. */
278             if (segment.p1.x == hairline->stroke_first_point.x &&
279                 segment.p1.y == hairline->stroke_first_point.y) {
280                 /* We are at the first stroke point, and we have
281                    been here, add a leading cap. */
282                 if (hairline->moved_to_stroke_first_point) {
283                     if (hairline->dash.dashes[hairline->dash.dash_index] == hairline->dash.dash_remain)
284                         needs_to_cap = _add_cap (hairline,
285                                                  slope_dx,
286                                                  slope_dy,
287                                                  TRUE, &p[0]);
288                 }
289                 else {
290                     /* We have not been in the first stroke point,
291                        this is the first line_to call sinece move_to()
292                        save the slope, we need use it later. */
293                     hairline->stroke_first_dx = slope_dx;
294                     hairline->stroke_first_dy = slope_dy;
295                     hairline->moved_to_stroke_first_point = TRUE;
296                 }
297             }
298             else if (segment.p1.x == hairline->current_point.x &&
299                      segment.p1.y == hairline->current_point.y) {
300                 /* Start of the line segment, if we have the entire
301                    dash length, we are at the begining of a dash,
302                    add a leading cap. */
303                 if (hairline->dash.dashes[hairline->dash.dash_index] == hairline->dash.dash_remain)
304                     needs_to_cap = _add_cap (hairline,
305                                              slope_dx, slope_dy,
306                                              TRUE, &p[0]);
307             }
308             else
309                 /* We are in the middle of the line segment, add
310                    a leading cap. */
311                 needs_to_cap = _add_cap (hairline,
312                                          slope_dx, slope_dy,
313                                          TRUE, &p[0]);
314
315             /* Add trailing cap. We have not exhausted line segment,
316                or we have exhausted current dash length, add a
317                trailing cap. */
318             if (remain ||
319                 hairline->dash.dash_remain - step_length < CAIRO_FIXED_ERROR_DOUBLE)
320                 needs_to_cap = _add_cap (hairline,
321                                          slope_dx, slope_dy,
322                                          FALSE, &p[1]);
323             else {
324                 /* We indicate here that we have not added a trailing
325                    cap yet.  If next move is move_to, we will add a
326                    trailing cap, otherwise, it will be ignored. */
327                 hairline->line_last_capped = FALSE;
328                 hairline->line_last_point = segment.p2;
329                 hairline->line_last_dx = slope_dx;
330                 hairline->line_last_dy = slope_dy;
331             }
332
333             status = _cairo_gl_composite_emit_point_as_single_line (hairline->ctx, p);
334             if (unlikely (status))
335                 return status;
336         }
337
338         _cairo_stroker_dash_step (&hairline->dash, step_length);
339         segment.p1 = segment.p2;
340     }
341
342     hairline->current_point = *point;
343     return CAIRO_STATUS_SUCCESS;
344 }
345
346 cairo_status_t
347 _cairo_gl_hairline_curve_to (void *closure,
348                              const cairo_point_t *p0,
349                              const cairo_point_t *p1,
350                              const cairo_point_t *p2)
351 {
352     cairo_gl_hairline_closure_t *hairline = (cairo_gl_hairline_closure_t *)closure;
353     cairo_spline_t spline;
354     cairo_path_fixed_line_to_func_t *line_to;
355
356     line_to = hairline->dash.dashed ?
357         _cairo_gl_hairline_line_to_dashed :
358         _cairo_gl_hairline_line_to;
359
360     if (! _cairo_spline_init (&spline,
361                               (cairo_spline_add_point_func_t)line_to,
362                               closure,
363                               &hairline->current_point, p0, p1, p2))
364         return _cairo_gl_hairline_line_to (closure, p2);
365
366     return _cairo_spline_decompose (&spline, hairline->tolerance);
367 }
368
369 cairo_status_t
370 _cairo_gl_hairline_close_path (void *closure)
371 {
372     cairo_gl_hairline_closure_t *hairline = (cairo_gl_hairline_closure_t *)closure;
373
374     hairline->line_last_capped = TRUE;
375     hairline->stroke_first_capped = TRUE;
376
377     if (hairline->dash.dashed)
378         return _cairo_gl_hairline_line_to_dashed (closure,
379                                                   &hairline->stroke_first_point);
380     return _cairo_gl_hairline_line_to (closure, &hairline->stroke_first_point);
381 }
382
383 cairo_status_t
384 _cairo_gl_path_fixed_stroke_to_hairline (const cairo_path_fixed_t *path,
385                                          cairo_gl_hairline_closure_t *closure,
386                                          const cairo_stroke_style_t *style,
387                                          const cairo_matrix_t *ctm,
388                                          const cairo_matrix_t *ctm_inverse,
389                                          cairo_path_fixed_move_to_func_t *move_to,
390                                          cairo_path_fixed_line_to_func_t *line_to,
391                                          cairo_path_fixed_curve_to_func_t *curve_to,
392                                          cairo_path_fixed_close_path_func_t *close_path)
393 {
394     cairo_status_t status;
395
396     _cairo_stroker_dash_init (&closure->dash, style);
397     closure->ctm = (cairo_matrix_t *)ctm;
398     closure->ctm_inverse = (cairo_matrix_t *)ctm_inverse;
399     closure->cap_style = style->line_cap;
400     closure->initialized = FALSE;
401
402     status = _cairo_path_fixed_interpret (path,
403                                           move_to,
404                                           line_to,
405                                           curve_to,
406                                           close_path,
407                                           (void *) closure);
408     if (unlikely (status))
409         return status;
410
411     return _path_add_first_and_last_cap (closure);
412 }