[cairo] Aligning graphics with the device pixel grid

Bill Spitzak spitzak at d2.com
Fri Oct 22 12:08:18 PDT 2004

On Friday 22 October 2004 09:51 am, Carl Worth wrote:

> snap_point_for_fill - ...
> snap_point_for_stroke ...

I feel like this is the right approach!

> 1) What points in the path should be snapped? We could specify this
>    vaguely, allowing us to do something simple, (snap all points? snap
>    points next to horizontal/vertical portions of the path?). Then, if
>    we wanted to get fancier for extremal points of curves, etc. we could
>    do that. What happens if we snap curve control points?

I think it is safe to define this vaguely, at least until a real 
implementation has been tried and tweaked. My guess is that the path would be 
flattened and that all points at the end of a segment where dx or dy is zero, 
or where the sign of one segment is different than the sign of the other, 
should be adjusted. There may also have to be considerations of rounding, see 

> 2) Do the snapping functions need to be modified to handle
>    transformations that have more than scaling and translation? If so,
>    do they become no-ops then or do we look for horizontal/vertical
>    portions in device space?

I would do everything in device space. Certainly 90 degree rotations must be 

> 3) The results of cairo_snap_path_for_stroke are dependent on the
>    current line width. That width may change before the path is
>    stroked. Do we just document this? It would be possible to avoid the
>    problem by combining the snap and stroke into a new snapped_stroke
>    function, but that would have the disadvantage of preventing the user
>    from examining/modifying the snapped coordinate values.

I would just document that. Even setting the *same* line width may mess 
things up. It is possible that Cairo will cache some more info from 
snap-for-stroke such as a special pen.

> 4) Should cairo_snap_path_for_stroke also take care of adjusting the
>    line width so that it transforms to an integer?

Yes. This will probably simplify it as it limits the set of line widths it 
needs to be able to handle.

> 5) If we say yes to 4, then what do we do about line width in the face
>    of non-uniform scaling in X and Y?

I think the work should be done in device space, where different line widths 
depending on angle must already be handled. I would adjust this device-space 
pen as needed.

>    Keith has been doing some work with arbitrary pens in twin, and I was
>    already interested in playing with that. Maybe this gives us a good
>    reason to look at that again.

Yes it would be nice to be able to directly specify all the linewidths you 
can actually get by scaling and rotating the current transform. It seems to 
me that there are 3 unknowns, but the best interface may be a 2x2 matrix and 
the pen is a .5 radius circle transformed by this matrix. Line caps would 
also be transformed by this matrix.

> 6) Should we add a state bit in the graphics state so that all strokes
>    and fills can be automatically snapped? This would again reduce the
>    number of calls necessary. Any reason this would be a bad idea? Any
>    suggestion for the name of the function call?

This would prevent the path-reading-back calls from being able to choose 
between the unadjusted, fill, and stroke adjusted paths.


Doing int(x+.5) is bad because the whole thing shifts when it passes through 
the origin. Use floor(x+.5).

However it may be nice to actually provide a "center" to the snap to allow 
symmetric objects to remain symmetric. The center is an xy point, it is 
rounded to the nearest 1/2 pixel, and the whole path is moved by the same 
amount before adjustment. Then adjustments are done but all non-integer 
distances from the center are rounded *away* from the center (away is better 
so tiny objects don't disappear).

In your example this could make that center square appear centered.

More information about the cairo mailing list