[GRASS-dev] Cairo monitor driver

Glynn Clements glynn at gclements.plus.com
Wed Oct 17 00:46:37 EDT 2007

Lars Ahlzen wrote:

> > I've committed [the cairo driver] to CVS, with a few changes; mostly, just
> > conforming to existing coding and formatting conventions.
> Thanks! I very much appreciate the time you obviously spent on looking 
> at the code, cleaning it up and adding it.
> > The main one is that code should stick to ANSI C89...
> > 
> > Variables which aren't used outside of the file in which they are
> > defined should be declared "static".
> Noted. I guess I should have enabled the -ansi flag in GCC.

If you do this, you need to manually enable certain Unix features with
e.g. -D_GNU_SOURCE. Otherwise, any headers specified by ANSI C
(stdio.h etc) only define the functions which are specified by ANSI C
(i.e. no fdopen() in stdio.h, etc).

> > A question about the set_drawing_op/finish_drawing_op stuff: is this
> > meant to merge distinct line segments into a single path? If so, I'll
> > remove it.
> > 
> > The lack of begin/end operations for paths (move/cont) is a flaw in
> > the interface, and is one of the planned interface changes for 7.x. 
> > Note that the PS driver doesn't attempt to hack around this; if you
> > want a single path, you have to use the polyline operation.
> Yes, it was primarily to ensure that lines are connected (for nice line 
> joins etc). But I guess you're right - that's what polyline is for.

Alas, many things which could use it don't. It doesn't help that the
R_polyline_* interface is less convenient than R_{move,cont}_*. Adding
begin/end for paths is a priority for 7.x.

> I believe this means that pretty much all of drawing.c can go. One would 
> have to call cairo_stroke or cairo_fill directly from the drawing 
> functions instead.

This should make the code significantly easier to understand. At the
present time, the cairo driver is probably more useful as an example
of how to use cairo than anything else.

> > The global cairo_translate(cairo, 0.5, 0.5) is wrong. If you want to
> > work around the problems with lines having their endpoints on pixel
> > boundaries (rather than centres), apply the translation only to lines. 
> > Other operations (box/polygon fills, images, text) should *not* be
> > translated in this way. FWIW, the PS driver doesn't even translate
> > lines.
> Well, this one is a little bit tricky.
> You're right that filled shapes and images must not be translated to 
> remain sharp. However, this would not really produce a correct result 
> either (with correct being defined as similar to other drivers).

Other drivers (XDRIVER, PNG, PS) don't translate fills; PS doesn't
translate lines either.

> If ONLY lines were translated and nothing more was done, the 1/2 pixel 
> offset between lines and filled shapes would very likely cause issues 
> where lines and filled areas meet.

Existing code has typically been written with the 1/2 pixel offset in
mind. Or it just doesn't care.

In general, you can't make lines and areas tesselate. For single-pixel
lines, Inherent differences in the rendering algorithms mean that the
steps occur in different places. For thick lines, joins and caps mean
that the outline of the stroke (the area occupied by the "line",
rather than the zero-thickness mathematical line) is hard to predict.

Note that cases where orthogonal "lines" need to align exactly often
use a single pixel wide/high rectangle rather than a line, to avoid
this issue.

> Furthermore, there's a problem with polygon size. Consider a case of a 
> filled rectangle from (10,2) to (20,3). Mathematically (and the way 
> Cairo sees things) this is a 10 units wide by 1 unit tall rectangle.

That's also how the drivers see it.
> Pixel based drivers (PNG, X, etc) would draw an 11 pixel wide and 2 
> pixel tall rectangle.

That's incorrect. The drivers handle fills correctly, i.e. coordinates
are interpreted as points in 2D space, not as pixel indices. E.g. 
R_box_abs(10, 2, 20, 3) will fill 10 pixels for the X and PNG drivers,
i.e. all <x,y> where 10 <= x < 20 and y == 2.

> Cairo, assuming no translation, would draw a 10 pixel wide and 1
> pixel tall rectangle. OTOH, with the translation and antialiasing
> enabled, cairo would draw an 11 pixel wide and 2 pixel tall
> rectangle, but with fuzzy edges (on a pixel-based "canvas"), which
> seemed (and still seems) like the less bad of the two options to me.

You're assuming that the drivers get fills wrong; they don't. With
lines, they have no choice (well, the pixel-based ones don't; the PS
driver doesn't do translation).

> So, why not just disable translation for filled rectangles and make them 
> one unit wider and taller (for pixel-based output)? This approach might 
> work for rectangles, but not for arbitrary shapes, such as a triangle - 
> at least not in any trivial way.
> I don't believe that adding the offset to lines only would solve the 
> fundamental problem, or even really improve the situation, although I'm 
> happy to try it if you insist.

It definitely doesn't belong on fills, and it's debatable whether it
should be applied to lines.

> One could also forget about the whole offset thing and accept fuzzy 
> lines, knowing that this is only a problem because of the current lack 
> of floating point coordinates. It's either sharp lines or sharp filled 
> shapes. Having tried both, I personally much prefer the former, though. 

There is a third option: only use even line widths.

And a fourth: use lines which are many pixels wide on a device with
high resolution (300+ DPI) so that one pixel either way doesn't matter
(this is why the PS driver doesn't bother with translation).

> Quoting from the official Cairo FAQ regarding fuzzy lines [1]:
> "It is not possible to set up sample locations in a way so that both 
> fills and strokes with integer coordinates work nicely, so one had to be 
> preferred over the other. One argument in favor of preferring fills is 
> that all fills with integer coordinates align nicely this way. The best 
> that can be done with strokes is to make all even-integer-width strokes 
> align nicely (as they do in cairo) or to make odd-integer-width strokes 
> align (which would then break the fill alignment)."
> [1] http://www.cairographics.org/FAQ/#sharp_lines
> Regarding the comparison with the PS driver, note that the cairo driver 
> applies the translation ONLY for pixel-based formats (PNG). It doesn't 
> translate anything for PS output (or any other vector format) either.

The main problem I have with translation is that it tends to force the
concept of pixels into whatever uses it. Note that this issue *won't*
go away when we switch to FP coordinates; making the caller add half a
unit would be even worse than the current situation.

Realistically, I think that you just have to live with either fuzzy or
offset lines on low-resolution devices. Getting sharp lines in all
cases requires that high-level code pays attention to details which it
really has no business paying attention to (i.e. the pixel grid).

It's only possible to "solve" it for common cases because so much code
is hardwired to single pixel lines (which is less than ideal, with
monitor resolutions getting ever higher; I run at 1920x1440 on a 22"
monitor, and programs which size everything in pixels are a nuisance).

Once you start dealing with user-defined line widths, you don't really
have much choice but to align lines (the centre of the stroke, not the
edge) with polygon boundaries. If you *need* to draw a boundary
*around* a polygon, you have to make the boundary itself a polygon.

> > The raster library should be extended to allow direct rendering via
> > cairo, but we should get it working fully first.
> > 
> > I note that clipping isn't enabled, and that the bitmap primitive
> > (used for freetype text, amongst other things) is a no-op. Both of
> > these are relatively important; the bitmap primitive would be less
> > important if the driver implements the draw_text method using cairo's
> > built-in font rendering.
> > AFAICT, clipping should just be a matter of calling cairo_reset_clip()
> > and cairo_clip(). Ignore the encapsulation issues; again, that's an
> > interface flaw (which also affects the PS driver).
> I agree, clipping and Draw_bitmap should be added ASAP. Neither should 
> be very difficult to implement.
> Please prove me wrong here, but one of the problems I've had is that the 
> documentation on the monitor interface (such as driver struct) in the 
> Grass Programmer's manual is virtually nonexistent. Most of the cairo 
> driver work was done by looking at the PNG and PS drivers with some 
> trial-and-error thrown in. Does complete documentation for these areas 
> exist?


Although, the PS driver is probably the best reference for the cairo
driver, as cairo was heavily modelled on PostScript, and the PS driver
is the closest to how it "should" work (as it doesn't have to deal
with pixel-grid issues).

> Given the text rendering support in Cairo, I hope that adding good 
> quality text rendering will be relatively straightforward as well.
> I take it that the best thing for me to do right now would be to work on 
>   the mentioned issues and missing features and post a patch? Perhaps 
> add basic documentation in display/drivers/cairo as well?

There's not much point in documenting the internals. You wrote it, I
wrote (or, at least, entirely re-wrote) most of the code it was based
upon, and you can guess how many other people are likely to work on it
by looking at the names in the "cvs log" output for any driver-related
code (hint: most of it says glynn, glynn, glynn, glynn, ...).

IOW, if you write documentation, most of it will end up becoming out
of date and getting rewritten long before anyone ever reads it.

Glynn Clements <glynn at gclements.plus.com>

More information about the grass-dev mailing list