[cairo] Adventures in line widths

Carl Worth cworth at cworth.org
Thu Apr 27 11:58:42 PDT 2006


So, I recently had an interesting conversation with Maxim Shemanarev
who is the author of the Anti-Grain Geometry project[1] in which we
were comparing/contrasting the priorities and goals of cairo and AGG.

One interesting aspect of the thread came when Maxim showed an example
from AGG that demonstrated that the transformation of path coordinates
could be independent from the transformation of the pen used to stroke
the path. This is something that some rendering models, (SVG 1.0, for
example), do not allow while others (PostScript for example), do.

I responded with the following example showing that cairo does have
this capability:

	http://article.gmane.org/gmane.comp.graphics.agg/2518

but when I wrote that code I found what I think is a bug in the
current implementation of cairo_set_line_width, (or at the very least
is an ambiguity). What the documentation of cairo_set_line_width
currently says is:

	Sets the current line width within the cairo context. The line
	width specifies the diameter of a pen that is circular in
	user space.

	As with the other stroke parameters, the current line width is
	examined by cairo_stroke(), cairo_stroke_extents(), and
	cairo_stroke_to_path(), but does not have any effect during path
	construction.

This is all just fine, we know the line width doesn't affect path
construction, (that is, one can't use cairo_set_line_width to make a
variable-width path). Instead there's a single width that is examined
at the time of cairo_stroke, etc.

But, the ambiguity is the precise meaning of "user space" in the
documentation above. Currently, the implementation is using the user
space at the time of cairo_stroke. I contend that that is a bug and
that cairo_set_line_width should use the current user space at the
time of that call.

Changing this behavior would make cairo_set_line_width consistent with
things like cairo_set_source that "lock" the current CTM regardless of
the fact that the source pattern will actually be used by some later
drawing operation that is operating under a different user space.

The example I wrote that made the bug obvious to me was the following
two simple code snippets. I first wrote something to transform both
the path and the "pen" (the line_width) by the same matrix, like so:

    cairo_scale (cr, xscale, yscale);
    cairo_set_line_width (cr, line_width);
    /* create a path here */
    cairo_stroke (cr);

and that worked just fine.

Then I tried to make something that would transform the path, but not
the line width, by simply calling cairo_set_line_width before calling
cairo_scale, like so:

    cairo_set_line_width (cr, line_width);
    cairo_scale (cr, xscale, yscale);
    /* create a path here */
    cairo_stroke (cr);

The surprising behavior I found here is that this code behaved no
differently at all. That is the effective line width from stroking is
the line_width passed in above, but modified by the xscale and yscale
matrix that is only created and set later.

I really can't see any motivation for making the code behave this way,
while it would be useful to allow the change in the order of
operations effect a change in the result.

Meanwhile, it is possible with current cairo to transform the pen
differently than the path. It just takes a bit more work as one must
use a cairo_save/restore pair to isolate things, like so:

    cairo_save (cr);
    {
	cairo_scale (cr, xscale, yscale);
	spline_path (cr);
    }
    cairo_restore (cr);

    cairo_set_line_width (cr, line_width);
    cairo_stroke (cr);

That's the upshot I suppose. That with the current bug, the only way
to get a "correct" result from cairo_set_line_width is to set the line
width immediately before stroking.

Now, I've just pushed a test case that demonstrates this bug. The
latest version of the test can be found at:

http://gitweb.freedesktop.org/?p=cairo;a=blob;h=master:test/line-width-scale.c

I haven't actually put a fix together yet. (I don't think it will be
too hard---maybe just shoving a matrix into the stroke_style
structure. It might get a bit ugly to have two different CTMs passed
down the stroke interface, but that may be what the semantics
require.)

But before I do make the fix, I wonder if anyone anticipates any
problem with this change. It would be a semantic change compared to
1.0. So this could be the first behavioral bug for which people have
written code that relies on the buggy behavior.

I'm guessing it won't actually be a big deal. But we'll have to get a
fix in place soon so we can test and verify that.

Oh, and meanwhile, the poppler rendering of the PDF that results from
this test case is rather bizarre, (though acroread seems to render it
as expected). I thought at first that might be due to this same cairo
bug getting hit again when rendering, but it appears that
poppler+splash treats the file the same way. So I've left the poppler
folks know about this.

-Carl

[1] http://antigrain.com/

[2] If anyone's interested in the cairo/AGG comparison see the following:

	http://thread.gmane.org/gmane.comp.graphics.agg/2494/focus=2494
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://lists.freedesktop.org/archives/cairo/attachments/20060427/17ef3163/attachment.pgp


More information about the cairo mailing list