Adding support for quadratic beziers
Shriramana Sharma <[email protected]>
| Newsgroups | gmane.comp.lib.cairo |
|---|---|
| Message-ID | <CAH-HCWUXbebdtzU+q9EH4Fr4gJgq7QY_1nOvViWKsegBwSGREA@mail.gmail.com> |
Hello -- I am new to this list. From the page http://lists.cairographics.org/archives/cairo/ I tried searching the archives for "quadratic bezier" but for some reason it took me to https://www.google.com/search?q=quadratic+bezier&sitesearch=%25%28archive_url%29s%2F -- something is broken. So I'm posting this here. Frankly I am not an expert coder or anything (or even in the programming field) but I've been using Inkscape quite some, and I've been struck by how it doesn't have support for quadratic beziers despite the SVG standard specifying it. And I went and filed this: https://bugs.launchpad.net/inkscape/+bug/1009765. While that bug can easily be fixed without any change to cairo, I thought "why doesn't cairo have this?" I have been using Qt for some programming, and saw that Qt easily implements quadratic beziers internally using cubics by degree elevation: http://qt.gitorious.org/qt/qt/blobs/4.8/src/gui/painting/qpainterpath.cpp I'm sure the people here know that a quadratic represented by the points (s, c, e) is identical to the cubic represented by (s, (s+2c)/3, (e+2c)/3, e) -- so I was wondering why isn't it available in Cairo? At least at a user level even if not in the underlying data (because I realize many/most backends don't implement a separate quadTo)? Having contributed some easy patches to QPainterPath for relative equivalents of QPainterPath::moveTo() etc after seeing Cairo have such functions (https://bugreports.qt-project.org/browse/QTBUG-26789), I thought it might be easy for me to do something similar for Cairo: But apparently because of C's non-object-oriented nature, I ran into a maze of abstractions and redirections -- To provide a quad_to on par with curve_to and using curve_to, I investigated where the user-visible curve_to is coming from: As per the version I downloaded from http://cgit.freedesktop.org/cairo/plain/src/, my trace: user visible cairo_curve_to calls cr->backend->curve_to at cairo.c l 1560 we have to find which function this curve_to actually points to for that we have to find which backend structure backend actually points to now cr->backend is equated to argument backend passed to _cairo_init at l 248 _cairo_init is called with _cairo_default_context_backend for argument backend by _cairo_default_context_init at cairo-default-context.c l 1394 (common _cairo_init declaration for cairo.c and cairo-default-context.c is at cairo_private.h l 56) structure _cairo_default_context_backend is defined at l 1278 et suivant _cairo_default_context_backend includes the function pointer _cairo_default_context_curve_to at l 1335 _cairo_default_context_curve_to is defined at l 674 et suivant it converts the coords from double to internal fixed point representation and calls _cairo_path_fixed_curve_to at l 697 _cairo_path_fixed_curve_to is defined at cairo-path-fixed.c l 580 et suivant (common _cairo_path_fixed_curve_to declaration for cairo-default-context.c and cairo-path-fixed.c is at cairoint.h l 912) therefore this is the function actually called by cr->backend->curve_to at cairo.c l 1560 So for quad_to to be implemented on par with and using cubic_to, it would have to be done here, but by this time it has been converted into fixed point where doing 2/3 is probably out of place, so it has to be done at the higher level in _cairo_default_context_curve_to. But then that is not the actual function doing the actual job. The required quirks like "if this curve does not move", "make sure subpaths are started properly" (and returning an error for rel_*_to if there is no current point) are done at the lower level in cairo-path-fixed.c only... OTOH, quad_to will have to call get_current_point to calculate the control points to pass to curve_to. So maybe it would be best to leave quad_to (and rel_quad_to) at the top level after all and not try to put it on par with curve_to (which it uses anyway). So I opted for the simple way out, and wrote two small patches for the two outermost API files. As I said, I'm not an experienced coder (much less so related to cairo), so maybe I have missed something important. So I just content myself with submitting my superficial patches to the developers and saying that I feel it would be useful if cairo had a quad_to and rel_quad_to function. Thank you for all your great work on Cairo and FOSS! -- Shriramana Sharma -- cairo mailing list [email protected] http://lists.cairographics.org/mailman/listinfo/cairo
cairo.c.patch
(application/octet-stream, 3.2 KB)
--- cairo.c.orig 2012-08-07 20:28:04.000000000 +0530
+++ cairo.c 2012-08-09 00:34:13.000000000 +0530
@@ -1569,2 +1569,49 @@
/**
+ * cairo_quad_to:
+ * @cr: a cairo context
+ * @x1: the X coordinate of the control point
+ * @y1: the Y coordinate of the control point
+ * @x2: the X coordinate of the end of the curve
+ * @y2: the Y coordinate of the end of the curve
+ *
+ * Adds a quadratic Bézier spline to the path from the current point to
+ * position (@x2, @y2) in user-space coordinates, using (@x1, @y1) as
+ * the control point. After this call the current point will be (@x2, @y2).
+ *
+ * If there is no current point before the call to cairo_quad_to()
+ * this function will behave as if preceded by a call to
+ * cairo_move_to(@cr, @x1, @y1). Effectively, this will produce a straight
+ * line from (@x1, @y1) to (@x2, @y2).
+ *
+ * Since: 1.1x
+ **/
+void
+cairo_quad_to (cairo_t *cr,
+ double x1, double y1,
+ double x2, double y2)
+{
+ cairo_status_t status;
+
+ if (unlikely (cr->status))
+ return;
+
+ double xa, ya, xb, yb, // control points of underlying cubic
+ xc, yc ; // current point
+
+ if (!cairo_has_current_point) { cairo_move_to ( cr, x1, y1 ) ; xc = x1 ; yc = y1 ; }
+ else { cairo_get_current_point ( cr, & xc, & yc ) ; }
+
+ xa = ( xc + 2 * x1 ) / 3 ; ya = ( yc + 2 * y1 ) / 3 ;
+ xb = ( x2 + 2 * x1 ) / 3 ; yb = ( x2 + 2 * y1 ) / 3 ;
+
+ status = cr->backend->curve_to (cr,
+ xa, ya,
+ xb, yb,
+ x2, y2);
+ if (unlikely (status))
+ _cairo_set_error (cr, status);
+}
+slim_hidden_def (cairo_quad_to);
+
+/**
* cairo_arc:
@@ -1830,2 +1877,50 @@
if (unlikely (status))
+ _cairo_set_error (cr, status);
+}
+
+/**
+ * cairo_rel_quad_to:
+ * @cr: a cairo context
+ * @dx1: the X offset to the control point
+ * @dy1: the Y offset to the control point
+ * @dx2: the X offset to the end of the curve
+ * @dy2: the Y offset to the end of the curve
+ *
+ * Relative-coordinate version of cairo_quad_to(). All offsets are
+ * relative to the current point. Adds a quadratic Bézier spline to the
+ * path from the current point to a point offset from the current
+ * point by (@dx2, @dy2), using a point offset by (@dx1, @dy1) as the
+ * control point. After this call the current point will be offset
+ * by (@dx2, @dy2).
+ *
+ * Given a current point of (x, y), cairo_rel_quad_to(@cr, @dx1,
+ * @dy1, @dx2, @dy2) is logically equivalent to
+ * cairo_quad_to(@cr, x+@dx1, y+@dy1, x+@dx2, y+@dy2).
+ *
+ * It is an error to call this function with no current point. Doing
+ * so will cause @cr to shutdown with a status of
+ * %CAIRO_STATUS_NO_CURRENT_POINT.
+ *
+ * Since: 1.1x
+ **/
+void
+cairo_rel_quad_to (cairo_t *cr,
+ double dx1, double dy1,
+ double dx2, double dy2)
+{
+ cairo_status_t status;
+
+ if (unlikely (cr->status))
+ return;
+
+ double dxa, dya, dxb, dyb ; // offsets of control points of underlying cubic
+
+ dxa = 2 * dx1 / 3 ; dya = 2 * dy1 / 3 ;
+ dxb = ( dx2 + 2 * dx1 ) / 3 ; dyb = ( dx2 + 2 * dy1 ) / 3 ;
+
+ status = cr->backend->rel_curve_to (cr,
+ dxa, dya,
+ dxb, dyb,
+ dx2, dy2);
+ if (unlikely (status))
_cairo_set_error (cr, status);
cairo.h.patch
(application/octet-stream, 424 B)
--- cairo.h.orig 2012-08-07 20:27:58.000000000 +0530 +++ cairo.h 2012-08-08 11:51:24.000000000 +0530 @@ -858,2 +858,7 @@ cairo_public void +cairo_quad_to (cairo_t *cr, + double x1, double y1, + double x2, double y2); + +cairo_public void cairo_arc (cairo_t *cr, @@ -889,2 +894,7 @@ +cairo_public void +cairo_rel_quad_to (cairo_t *cr, + double dx1, double dy1, + double dx2, double dy2); + cairo_public void