[cairo] Problem with degenerate lines

Krzysztof Kosiński tweenk.pl at gmail.com
Sat Nov 6 15:04:48 PDT 2010

W dniu 6 listopada 2010 22:24 użytkownik Steve Harrington
<steven.harrington at comcast.net> napisał:
> On 11/06/2010 01:35 PM, Krzysztof Kosiński wrote:
> 2010/11/6 Steve Harrington <steven.harrington at comcast.net>:
> Searching the archives I found this method of drawing a single point:
>      cairo_move_to( cr, x, y );
>      cairo_close_path( cr );
>      cairo_stroke( cr );
> Which works fine in some cases.  However, the following code (in the expose
> handler) doesn't work as expected.  See the comments in the code for the
> results and times needed to draw them.  BTW is also seems to show a fairly
> gross inefficiency somewhere in the drawing chain.
> Nothing gets drawn because you need to set a square line cap.
> cairo_set_line_cap(cr, CAIRO_LINE_CAP_SQUARE);
> Are you sure of this?  I read (in the same article I got the above code
> from) that:
> [cairo] Drawing points?
> Carl Worth cworth at cworth.org
> Fri Jun 19 09:51:17 PDT 2009
> .................
> Within the implementation (and test suite) we call these "degenerate"
> paths and we explicitly support drawing round caps for such degenerate
> paths. So this should work perfectly for the case of
> CAIRO_LINE_CAP_ROUND and you'll get the diameter controlled by
> cairo_set_line_width just like you want.
> However, I believe that we have specified that degenerate paths do not
> draw anything in the case of CAIRO_LINE_CAP_SQUARE. The problem their is
> that there's ambiguity as to what direction the square caps should be
> drawn in. (Contrast this with the case of stroking a dashed line with
> square caps and a dash pattern with a zero-length "on" section. In that
> case, we do have direction information and we do draw the caps.)
> ....................
> or is this comment obsolete/incorrect?

I wasn't aware of that. I guess Carl Worth is a lot better expert than me :)

> The drawing will be inefficient because you need to add 0.5, not 1 to
> each coordinate. Otherwise you end up drawing "in between" pixels,
> which turns off optimizations in Cairo for pixel-aligned drawing. It's
> better to do something like this, which also avoids computing the
> stroke.
> cairo_rectangle(cr, i, j, i+1, j+j);
> cairo_fill(cr);
> In fact I do this in other routines and it does indeed work.  I was hoping
> that the move_to/close_path approach would be faster.  Also, I intentionally
> (at least in this example) did not change the scale of the CTM.  This should
> give an isomorphic transformation between user integer coordinates and
> actual pixels.  Is this true or no?

Yes, the default transformation maps user units to device units
directly. In case of pixel-based surfaces like Xlib and image, it
means 1 Cairo unit = 1 pixel.

> For this kind of per-pixel drawing, instead of using Cairo drawing
> routines, you would be better off writing the gradient to an image
> surface, and then blitting it to the screen in one go. You can also
> use OpenMP to parallelize it.
> Thank you.  I had decided that this was the approach to take but I was
> having problems finding a starting point.  I'll be studying this in detail.

Keep in mind that you should probably benchmark both the parallelized
and non-parallelized code, as OpenMP overhead dominates at small
per-thread work sizes and OpenMP code might end up actually being
slower than serial code when the widget is small. You need to compile
and link with the -fopenmp flag (for GCC) to use it.

> cairo_surface_t *grad =
> cairo_image_surface_create(CAIRO_FORMAT_ARGB32, Allocation.width,
> Allocation.height);
> int stride = cairo_image_surface_get_stride(grad), i, j;
> unsigned char *px = cairo_image_surface_get_data(grad);
> #pragma openmp parallel for
> for (i = 0; i < Allocation.height; ++i) {
>   for (j = 0; j < Allocation.width; ++j) {
>     guint32 *pixel = (guint32*)(px + i*stride + j*sizeof(guint32));
>     guint32 r = round((double)i / Allocation.width * 255.0);
>     guint32 g = round((double)j / Allocation.height * 255.0);
>     guint32 b = round((double)(i*j) /
> (Allocation.width*Allocation.height) * 255.0);
>     *pixel = (255 << 24) | (r << 16) | (g << 8) | (b);
>   }
> }
> cairo_surface_mark_dirty(grad);
> cairo_translate(cr, Allocation.x, Allocation.y);
> cairo_set_source_surface(cr, grad, 0, 0);
> cairo_paint(cr);
> cairo_surface_destroy(grad);
> Note, I haven't debugged this code, so it might contain some errors.
> Not to worry.  I'm sure I'll find all your errors and many more of my own.
> I'm very grateful for the #pragma line.  I hadn't noticed that that
> construct had been ported to gcc.
> ...
> Nope - no errors in your code (and it runs in 0.03 seconds).  Looks like
> I'll have to work harder to add some errors later.

That's great :)

> Any idea why the large difference in execute times for:
>     cairo_line_to( cr, i, j+1 );   ~0.88 seconds
> and
>     cairo_line_to( cr, i+1, j+1 );  ~3.72 seconds?
> Thanks a lot for the ideas.

In the first case you are drawing a horizontal line which is 1 pixel
long and affects 2 pixels. In the second case you draw a line which is
sqrt(2) pixels long and affects 7 pixels, and which is not
axis-aligned - there are some optimizations which can be applied to
horizontal and vertical lines.

Regards, Krzysztof

More information about the cairo mailing list