Search code examples
ccairoxlib

What is the proper way of clearing an X window when painting on it via a Cairo surface


I am trying to draw on a transparent X window using Cairo. According to the official Cairo FAQ, a way to clear a transparent surface is by setting the Cairo operator to CLEAR. This doesn't seem to work in my case. I am drawing a growing and shrinking filled circle. Initially, the circle grows, but when it shrinks, the large circles remain on the window. This makes me think that perhaps I should clear the X window itself too at each redraw.

This is the code that I have come up with

XClearWindow(self->display, self->win_id);
cairo_push_group(self->context);

// Re-draw the whole canvas: Doesn't work as expected
// cairo_save(self->context);
// cairo_set_operator(self->context, CAIRO_OPERATOR_CLEAR);
// cairo_paint(self->context);
// cairo_restore(self->context);

// Invoke the draw callback <- Cairo drawing done here from Python
Canvas_on_draw(self, args_tuple, NULL);

cairo_pop_group_to_source(self->context);
cairo_paint(self->context);
cairo_surface_flush(self->surface);

XFlush(self->display);

As you can see, my solution is to call XClearWindow prior to drawing with Cairo, and then flush everything with XFlush. However, I'm not sure this is the cleanest solution, and it kind of feels like a hack rather than the proper approach. For instance, without XFlush I get considerable flickering, but the Xlib documentation seems to hint that most applications shouldn't need calling this function directly.


EDIT: After the answer below, this is what my code looks like:

    cairo_push_group(self->context);
    // Draw stuff
    cairo_pop_group_to_source(self->context);

    // The following cairo paradigm seems to have the same
    // effect as the following commented out lines:
    // XClearWindow(self->display, self->win_id);
    // cairo_paint(self->context);
    cairo_save(self->context);
    cairo_set_operator(self->context, CAIRO_OPERATOR_SOURCE);
    cairo_paint(self->context);
    cairo_restore(self->context);

This does the intended thing.


Solution

  • Add cairo_set_operator(self->context, CAIRO_OPERATOR_SOURCE); before your final cairo_paint().

    You are clearing the intermediate group that you created. That means that it is completely transparent outside of the circle. Then you draw this group to your target window with the default operator, which is OVER. Drawing completely transparent over anything just leaves the old content there. Drawing with SOURCE actually copies the source to the target surface.

    Side note: Your XClearWindow approach is not guaranteed to work unless you call cairo_surface_flush and cairo_surface_mark_dirty as needed around it.