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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.