Cairo lets you clip drawing to arbitrary regions defined by paths. The basic technique is:
- construct your path
- use
cairo_clip()
to intersect the clip region with the interior of your path.
Note that the intersection is with the interior of your path—that is, the area
that would be filled if you did a cairo_fill()
operation on your path. But supposing
you have a path, and you want drawing to be clipped to the exterior of that path?
The way to reverse the definitions of “interior” and “exterior” is simple: simply add
another subpath to your path, surrounding the entire drawing area. If you use
CAIRO_FILL_RULE_WINDING
for your fill rule (the default), then make sure the enclosing
subpath is defined in the opposite direction to the inner one. Now the interior of
the path becomes the area between the subpaths, rather than inside the inner subpath.
Alternatively, you can use CAIRO_FILL_RULE_EVEN_ODD
for your fill rule, then the
subpath directions don’t matter.
const int img_size = 300;
cairo_surface_t * const pix = cairo_image_surface_create
(
/*format =*/ CAIRO_FORMAT_RGB24,
/*width =*/ img_size,
/*height =*/ img_size
);
cairo_t * const cr = cairo_create(pix);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
/* use overwriting operator to make clear that overwriting is blocked by clipping */
cairo_set_source_rgb(cr, 1, 1, 1); /* background */
cairo_paint(cr);
for (bool outside = false;;)
{
/* Do two drawing passes, once clipped inside the region, then clipped outside. */
/* Because of the clipping, the second pass does not overwrite the first. */
cairo_reset_clip(cr);
cairo_new_path(cr);
cairo_arc /* example inner region--note subpath is defined clockwise */
(
/*cr =*/ cr,
/*xc =*/ img_size / 2.0,
/*yc =*/ img_size / 2.0,
/*radius =*/ img_size / 4.0,
/*angle1 =*/ 0,
/*angle2 =*/ 2 * M_PI
);
if (outside)
{
/* add outer subpath in anticlockwise direction */
cairo_new_sub_path(cr);
cairo_move_to(cr, img_size, 0);
cairo_line_to(cr, 0, 0);
cairo_line_to(cr, 0, img_size);
cairo_line_to(cr, img_size, img_size);
cairo_close_path(cr);
} /*if*/
cairo_clip(cr);
if (outside) /* use different colours to distinguish cases */
{
cairo_set_source_rgb(cr, 1.0, 0.91, 0.83);
}
else
{
cairo_set_source_rgb(cr, 0.1, 0.7, 0.67);
} /*if*/
cairo_rectangle
/* example shape that extends both inside and outside inner clipping region */
(
/*cr =*/ cr,
/*x =*/ 0,
/*y =*/ img_size / 4.0,
/*width =*/ img_size,
/*height =*/ img_size / 2.0
);
cairo_fill(cr);
if (outside)
break;
outside = true;
} /*for*/
Sample output:
This technique also extends to more than one inner subpath, defining multiple discontiguous inner regions to be clipped out of the drawing area.