<div dir="ltr"><div dir="ltr"><div dir="ltr"><div dir="ltr"><font face="monospace, monospace">Hello,<br><br>I've recently been using cairo + xcb to build a new application. I've hit a problem supporting transparency / alpha-blending, where I want to allow users to set the background window with an alpha value and have the root x11 window show through that. The raw transparency piece works fine and is easy in cairo + xcb, but I'm struggling with my main render loop -- it seems to add / accumulate the alpha values with each render, and doesn't remove previously drawn content with alpha values. I have more details below including a fully functioning prototype with my layout setup (also below and in a gist), but here's what I've been trying after much google'ing and searching the archives:<br><br>My main render loop looks roughly like this (and I understand why this would accumulate opaqueness over time):<br><br>   // BEGIN render loop<br><div dir="ltr" style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">   cairo_push_group(cairo);</div><div dir="ltr" style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">   clear_background();<br><br>   // draw (lots of) stuff<br><br>   // END render loop<br><div dir="ltr" style="text-decoration-style:initial;text-decoration-color:initial">   cairo_pop_group_to_source(cairo);</div><div dir="ltr" style="text-decoration-style:initial;text-decoration-color:initial">   cairo_paint(cairo);</div><div dir="ltr" style="text-decoration-style:initial;text-decoration-color:initial">   xcb_flush(xcon);<br>   sleep(1);</div></div><br>where clear_background() is:<br><br><div dir="ltr" style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">   cairo_set_source_rgba(cairo, 0.8, 0, 0, 0.1);</div><div dir="ltr" style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial">   cairo_paint(cairo);</div><br>With that loop, as-is, I can see why the alpha values are accumulating with each render.<br><br>To compensate, I've tried the following based on the cairo site's documentation, and browsing the archives, but with no success:<br><br>   1. <span style="font-size:small;background-color:rgb(255,255,255);text-decoration-style:initial;text-decoration-color:initial;float:none;display:inline">cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); (and subsequent resets to *_OVER)<br></span>      I've tried this before the cairo_paint() at the END of the loop, as well as in the BEGIN loop (both before and after the cairo_push_group()).<br>      My understanding is that this copies the alpha from SOURCE as-is, with no blending, but I'm not observing that in the results, regardless of where I place it (and tweak the timing to confirm each step).<br>      Use of it *always* results in a black background, which makes me think I have something larger out-of-whack.<br><br>   2. I've looked at other operators, but none seem applicable (and I've tested most in various setups like the above).<br><br>   3. Using cairo_mask() / cairo_mask_surface() when using cairo_pop_group() (the raw one, returning a cairo_pattern_t) to render after the pop, but that also fails.<br><br>My ultimate goals are basically these:<br><br>   1. Have the window always reset to a consistent background of rgba = 0.8/0/0/0.1<br>   2. Don't 'accumulate' alpha artifacts from the widgets drawn in the loop each time<br><br>My sample code below illustrates what I'm trying and my setup.<br>It draws a 200 x 200 pixel window (at x,y = 200,200).<br>It has an initial background of rgba = 0.8/0/0/0.1,<br>and a 50x50 pixel green square in the top left (rgba=0/1/0/0.5).<br>Every second it redraws, and moves the square towards the bottom right.<br><br>IDEALLY:<br>   1. The background is *always* rgba = 0.8/0/0/0.1<br>   2. Only one square (the most recent) is shown with rgba = 0/1/0/0.5<br><br>BUT what I observe (trying lots of variants):<br>   1. The background gets more opaque with each render, eventually becoming solid<br>   2. The previous renders of the green square persist and aren't cleared<br><br>The code is below, along with build instructions, and available at:<br>   <a href="https://gist.github.com/ryanflannery/1649ce3dd45cb6e16088931262283386">https://gist.github.com/ryanflannery/1649ce3dd45cb6e16088931262283386</a><br><br>Note the example includes a great deal of XCB setup, including logic to handle tiling window managers (my use case). I doubt that's of interest here, but I'm including it for completeness (but below the main cairo logic).<br><br>Any feedback appreciated (on this or other conventions I have!)<br><br>Cheers,<br>-Ryan<br><br><br><div dir="ltr">/*</div><div dir="ltr"> * NOTE: This is a little long because I'm including all XCB setup I have</div><div dir="ltr"> * (including handling tiling window managers). It's below the main cairo logic</div><div dir="ltr"> * for ease, but included for posterity. This is a subset of a larger program</div><div dir="ltr"> * I'm working on.</div><div dir="ltr"> *</div><div dir="ltr"> * WHAT THIS IS:</div><div dir="ltr"> *    A sample program illustrating a problem I'm having with cairo/xcb, where I</div><div dir="ltr"> *    want to have a window that supports transparency (alpha channel) for the</div><div dir="ltr"> *    background color, and other components, with a regular re-draw cycle</div><div dir="ltr"> *    (every 1-second), but the alpha-channel portion of the render appears</div><div dir="ltr"> *    additive / cumulative. That is: every render increases the alpha value,</div><div dir="ltr"> *    thus making it more opaque / less transparent every second, and doesn't</div><div dir="ltr"> *    clear-out the previously rendered content.</div><div dir="ltr"> *</div><div dir="ltr"> *    I've read and tried a number of things with the cairo API (and xcb) but</div><div dir="ltr"> *    cannot seem to figure it out.</div><div dir="ltr"> *</div><div dir="ltr"> * WHAT THIS PROGRAM DOES:</div><div dir="ltr"> *    It renders a 200 pixel by 200 pixel square, with a 1-pixel black border,</div><div dir="ltr"> *    at the (200,200) (x,y) coordinate of an X11 display.</div><div dir="ltr"> *    It starts with a semi-transparent red background and a small seim-</div><div dir="ltr"> *    transparent green square drawn in the upper-left corner.</div><div dir="ltr"> *    Every second, it re-renders the display, re-drawing the backgrond and</div><div dir="ltr"> *    re-drawing the square slightly more to the lower-right corner (advancing</div><div dir="ltr"> *    along the diagonal).</div><div dir="ltr"> *    The square is green pure green (r=0, g=1, b=0) with alpha = 0.5.</div><div dir="ltr"> *    The initial background color is r=0.8, g=0, b=0, alpha = 0.1.</div><div dir="ltr"> *</div><div dir="ltr"> * DESIRED END RESULT:</div><div dir="ltr"> *    1. The window ALWAYS has background r=0.8, g=0, b=0, a=0.1</div><div dir="ltr"> *    2. The green square is ERASED with each loop and only the most recently</div><div dir="ltr"> *       drawn square is shown, with color r=0, g=1, b=0, a=0.5</div><div dir="ltr"> *</div><div dir="ltr"> * WHAT I OBSERVE:</div><div dir="ltr"> *    1. The first render is perfect</div><div dir="ltr"> *    2. Each subsequent render "adds" the alpha channel to the previous one</div><div dir="ltr"> *       - Thus the transparency is eliminated in a few iterations</div><div dir="ltr"> *    3. I have a good handle on *why* this is happening, but don't see how</div><div dir="ltr"> *       to correct it.</div><div dir="ltr"> *</div><div dir="ltr"> * THINGS I'VE TRIED:</div><div dir="ltr"> *    1. cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);</div><div dir="ltr"> *       Called before the loop -and- the cairo_paint() after the</div><div dir="ltr"> *       cairo_pop_group_to_source() call (and reset it to *_OVER afterwards)</div><div dir="ltr"> *    2. Moving the cairo_clear_background() call below to before (and after)</div><div dir="ltr"> *       the cairo_push_group() call.</div><div dir="ltr"> *    3. In the #2 variant, wrapping that with calls to cairo_set_operator()</div><div dir="ltr"> *       with CAIRO_OPERATOR_SOURCE (and subsequent calls to reset to _OVER).</div><div dir="ltr"> *    4. I've looked at other operators, but they don't seem applicable (and</div><div dir="ltr"> *       have tested most in many configurations).</div><div dir="ltr"> *    5. Also looked at using cairo_mask() / cairo_mask_surface() per</div><div dir="ltr"> *       <a href="https://stackoverflow.com/questions/34831744/draw-icon-with-transparency-in-xlib-and-cairo">https://stackoverflow.com/questions/34831744/draw-icon-with-transparency-in-xlib-and-cairo</a></div><div dir="ltr"> *    6. So much google'ing/duckduckgo'ing</div><div dir="ltr"> *</div><div dir="ltr"> * TO BUILD/RUN:</div><div dir="ltr"> *    compile:</div><div dir="ltr"> *       $(CC) cairo_example.c -c `pkg-config --cflags cairo` -o cairo_example.o</div><div dir="ltr"> *    link:</div><div dir="ltr"> *       $(CC) cairo_example.o `pkg-config --libs cairo` -o cairo_example</div><div dir="ltr"> */</div><div dir="ltr"><br></div><div dir="ltr">#include <err.h></div><div dir="ltr">#include <stdlib.h></div><div dir="ltr">#include <string.h></div><div dir="ltr">#include <unistd.h></div><div dir="ltr"><br></div><div dir="ltr">#include <xcb/xcb.h></div><div dir="ltr">#include <xcb/xcb_icccm.h></div><div dir="ltr">#include <cairo/cairo.h></div><div dir="ltr">#include <cairo/cairo-xcb.h></div><div dir="ltr"><br></div><div dir="ltr">/*</div><div dir="ltr"> * XCB info. Full setup is included for posterity, but below main() for ease</div><div dir="ltr"> * of reading</div><div dir="ltr"> */</div><div dir="ltr">int               default_screen;</div><div dir="ltr">xcb_connection_t *xcon;</div><div dir="ltr">xcb_screen_t     *xscreen;</div><div dir="ltr">xcb_drawable_t   xwindow;</div><div dir="ltr">xcb_visualtype_t *xvisual;</div><div dir="ltr"><br></div><div dir="ltr">void setup_xcb(); /* what actually does the xcb setup */</div><div dir="ltr"><br></div><div dir="ltr">/* starting location & dimensions of window */</div><div dir="ltr">const int x = 200;</div><div dir="ltr">const int y = 200;</div><div dir="ltr">const int w = 200;</div><div dir="ltr">const int h = 200;</div><div dir="ltr"><br></div><div dir="ltr">/* relevant cairo stuff */</div><div dir="ltr">cairo_t          *cairo;</div><div dir="ltr">cairo_surface_t  *surface;</div><div dir="ltr"><br></div><div dir="ltr">/* effectively what i do to clear the background */</div><div dir="ltr">void</div><div dir="ltr">cairo_clear_background()</div><div dir="ltr">{</div><div dir="ltr">   cairo_set_source_rgba(cairo, 0.8, 0, 0, 0.1);</div><div dir="ltr">   cairo_paint(cairo);</div><div dir="ltr">}</div><div dir="ltr"><br></div><div dir="ltr">int</div><div dir="ltr">main()</div><div dir="ltr">{</div><div dir="ltr">   /* xcb setup (all below main, for ease, but also posterity) */</div><div dir="ltr">   setup_xcb();</div><div dir="ltr"><br></div><div dir="ltr">   /* cairo setup */</div><div dir="ltr">   surface = cairo_xcb_surface_create(</div><div dir="ltr">         xcon,</div><div dir="ltr">         xwindow,</div><div dir="ltr">         xvisual,</div><div dir="ltr">         w, h);</div><div dir="ltr"><br></div><div dir="ltr">   cairo = cairo_create(surface);</div><div dir="ltr"><br></div><div dir="ltr">   /* map window & first draw */</div><div dir="ltr">   xcb_map_window(xcon, xwindow);</div><div dir="ltr">   cairo_clear_background();</div><div dir="ltr">   xcb_flush(xcon);</div><div dir="ltr"><br></div><div dir="ltr">   /* begin main draw loop */</div><div dir="ltr">   for (int i = 0; i < 11; i++) {</div><div dir="ltr"><br></div><div dir="ltr">      /* START: create new group/buffer, set it's background  */</div><div dir="ltr">      cairo_push_group(cairo);</div><div dir="ltr">      cairo_clear_background();</div><div dir="ltr"><br></div><div dir="ltr">      /* now do all my drawing... */</div><div dir="ltr">      cairo_set_source_rgba(cairo, 0, 1, 0, 0.5);</div><div dir="ltr">      cairo_rectangle(cairo,</div><div dir="ltr">            i * 10 + 10,   /* x */</div><div dir="ltr">            i * 10 + 10,   /* y */</div><div dir="ltr">            50, 50);       /* w, h */</div><div dir="ltr">      cairo_fill(cairo);</div><div dir="ltr"><br></div><div dir="ltr">      /* END: pop group/buffer (exposing it) and render */</div><div dir="ltr">      cairo_pop_group_to_source(cairo);</div><div dir="ltr">      /*cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); XXX Makes the background black (?) */</div><div dir="ltr">      cairo_paint(cairo);</div><div dir="ltr">      /*cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); only done if th eprevious cairo_set_operator() is done */</div><div dir="ltr">      xcb_flush(xcon);</div><div dir="ltr">      sleep(1);</div><div dir="ltr">   }</div><div dir="ltr"><br></div><div dir="ltr">   /* cleanup */</div><div dir="ltr">   cairo_surface_destroy(surface);</div><div dir="ltr">   cairo_destroy(cairo);</div><div dir="ltr">   xcb_disconnect(xcon);</div><div dir="ltr"><br></div><div dir="ltr">   return 0;</div><div dir="ltr">}</div><div dir="ltr"><br></div><div dir="ltr">/* XCB Setup Stuff */</div><div dir="ltr"><br></div><div dir="ltr">xcb_screen_t*</div><div dir="ltr">get_xscreen(xcb_connection_t *c, int screen)</div><div dir="ltr">{</div><div dir="ltr">   xcb_screen_iterator_t i = xcb_setup_roots_iterator(xcb_get_setup(c));;</div><div dir="ltr">   for (; i.rem; --screen, xcb_screen_next(&i)) {</div><div dir="ltr">      if (0 == screen)</div><div dir="ltr">         return i.data;</div><div dir="ltr">   }</div><div dir="ltr">   return NULL;</div><div dir="ltr">}</div><div dir="ltr"><br></div><div dir="ltr">xcb_visualtype_t*</div><div dir="ltr">get_xvisual(xcb_screen_t *screen)</div><div dir="ltr">{</div><div dir="ltr">   xcb_depth_iterator_t i = xcb_screen_allowed_depths_iterator(screen);</div><div dir="ltr">   for (; i.rem; xcb_depth_next(&i)) {</div><div dir="ltr">      xcb_visualtype_iterator_t vi;</div><div dir="ltr">      vi = xcb_depth_visuals_iterator(i.data);</div><div dir="ltr">      for (; vi.rem; xcb_visualtype_next(&vi)) {</div><div dir="ltr">         if (screen->root_visual == vi.data->visual_id) {</div><div dir="ltr">            return vi.data;</div><div dir="ltr">         }</div><div dir="ltr">      }</div><div dir="ltr">   }</div><div dir="ltr"><br></div><div dir="ltr">   return NULL;</div><div dir="ltr">}</div><div dir="ltr"><br></div><div dir="ltr">/*</div><div dir="ltr"> * XCB setup to handle tiling window managers - this can (probably) be safely</div><div dir="ltr"> * ignored. I'm only incuding it for completeness' sake.</div><div dir="ltr"> */</div><div dir="ltr">void</div><div dir="ltr">wm_hints()</div><div dir="ltr">{</div><div dir="ltr">   enum {</div><div dir="ltr">      NET_WM_XINFO_TYPE,</div><div dir="ltr">      NET_WM_XINFO_TYPE_DOCK,</div><div dir="ltr">      NET_WM_DESKTOP,</div><div dir="ltr">      NET_WM_STRUT_PARTIAL,</div><div dir="ltr">      NET_WM_STRUT,</div><div dir="ltr">      NET_WM_STATE,</div><div dir="ltr">      NET_WM_STATE_STICKY,</div><div dir="ltr">      NET_WM_STATE_ABOVE</div><div dir="ltr">   };</div><div dir="ltr"><br></div><div dir="ltr">   static const char *atoms[] = {</div><div dir="ltr">      "_NET_WM_XINFO_TYPE",</div><div dir="ltr">      "_NET_WM_XINFO_TYPE_DOCK",</div><div dir="ltr">      "_NET_WM_DESKTOP",</div><div dir="ltr">      "_NET_WM_STRUT_PARTIAL",</div><div dir="ltr">      "_NET_WM_STRUT",</div><div dir="ltr">      "_NET_WM_STATE",</div><div dir="ltr">      "_NET_WM_STATE_STICKY",</div><div dir="ltr">      "_NET_WM_STATE_ABOVE"</div><div dir="ltr">   };</div><div dir="ltr">   const size_t natoms = sizeof(atoms)/sizeof(char*);</div><div dir="ltr"><br></div><div dir="ltr">   xcb_intern_atom_cookie_t xcookies[natoms];</div><div dir="ltr">   xcb_atom_t               xatoms[natoms];</div><div dir="ltr">   xcb_intern_atom_reply_t *xatom_reply;</div><div dir="ltr">   size_t i;</div><div dir="ltr"><br></div><div dir="ltr">   for (i = 0; i < natoms; i++)</div><div dir="ltr">      xcookies[i] = xcb_intern_atom(xcon, 0, strlen(atoms[i]), atoms[i]);</div><div dir="ltr"><br></div><div dir="ltr">   for (i = 0; i < natoms; i++) {</div><div dir="ltr">      xatom_reply = xcb_intern_atom_reply(xcon, xcookies[i], NULL);</div><div dir="ltr">      if (!xatom_reply)</div><div dir="ltr">         errx(1, "%s: xcb atom reply failed for %s", __FUNCTION__, atoms[i]);</div><div dir="ltr"><br></div><div dir="ltr">      xatoms[i] = xatom_reply->atom;</div><div dir="ltr">      free(xatom_reply);</div><div dir="ltr">   }</div><div dir="ltr"><br></div><div dir="ltr">   enum {</div><div dir="ltr">      left,           right,</div><div dir="ltr">      top,            bottom,</div><div dir="ltr">      left_start_y,   left_end_y,</div><div dir="ltr">      right_start_y,  right_end_y,</div><div dir="ltr">      top_start_x,    top_end_x,</div><div dir="ltr">      bottom_start_x, bottom_end_x</div><div dir="ltr">   };</div><div dir="ltr">   unsigned long struts[12] = { 0 };</div><div dir="ltr"><br></div><div dir="ltr">   struts[top] = y + h;</div><div dir="ltr">   struts[top_start_x] = x;</div><div dir="ltr">   struts[top_end_x] = x + w;</div><div dir="ltr"><br></div><div dir="ltr"><span style="white-space:pre">       </span>xcb_change_property(xcon, XCB_PROP_MODE_REPLACE, xwindow,</div><div dir="ltr">         xatoms[NET_WM_XINFO_TYPE], XCB_ATOM_ATOM, 32, 1,</div><div dir="ltr">         &xatoms[NET_WM_XINFO_TYPE_DOCK]);</div><div dir="ltr"><span style="white-space:pre">  </span>xcb_change_property(xcon, XCB_PROP_MODE_APPEND, xwindow,</div><div dir="ltr">         xatoms[NET_WM_STATE], XCB_ATOM_ATOM, 32, 2,</div><div dir="ltr">         &xatoms[NET_WM_STATE_STICKY]);</div><div dir="ltr"><span style="white-space:pre">   </span>xcb_change_property(xcon, XCB_PROP_MODE_REPLACE, xwindow,</div><div dir="ltr">         xatoms[NET_WM_DESKTOP], XCB_ATOM_CARDINAL, 32, 1,</div><div dir="ltr">         (const uint32_t []){ -1 } );</div><div dir="ltr"><span style="white-space:pre">  </span>xcb_change_property(xcon, XCB_PROP_MODE_REPLACE, xwindow,</div><div dir="ltr">         xatoms[NET_WM_STRUT_PARTIAL], XCB_ATOM_CARDINAL, 32, 12, struts);</div><div dir="ltr"><span style="white-space:pre">       </span>xcb_change_property(xcon, XCB_PROP_MODE_REPLACE, xwindow,</div><div dir="ltr">         xatoms[NET_WM_STRUT], XCB_ATOM_CARDINAL, 32, 4, struts);</div><div dir="ltr"><br></div><div dir="ltr">   /* remove window from window manager tabbing */</div><div dir="ltr">   const uint32_t val[] = { 1 };</div><div dir="ltr">   xcb_change_window_attributes(xcon, xwindow,</div><div dir="ltr">         XCB_CW_OVERRIDE_REDIRECT, val);</div><div dir="ltr">}</div><div dir="ltr"><br></div><div dir="ltr">void</div><div dir="ltr">setup_xcb()</div><div dir="ltr">{</div><div dir="ltr">   xcon = xcb_connect(NULL, &default_screen);</div><div dir="ltr">   if (xcb_connection_has_error(xcon)) {</div><div dir="ltr">      xcb_disconnect(xcon);</div><div dir="ltr">      errx(1, "Failed to establish connection to X");</div><div dir="ltr">   }</div><div dir="ltr"><br></div><div dir="ltr">   if (NULL == (xscreen = get_xscreen(xcon, default_screen)))</div><div dir="ltr">      errx(1, "Failed to retrieve X screen");</div><div dir="ltr"><br></div><div dir="ltr">   if (NULL == (xvisual = get_xvisual(xscreen)))</div><div dir="ltr">      errx(1, "Failed to retrieve X visual context");</div><div dir="ltr"><br></div><div dir="ltr">   static uint32_t valwin[2] = {</div><div dir="ltr">      XCB_NONE,</div><div dir="ltr">      XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS</div><div dir="ltr">   };</div><div dir="ltr"><br></div><div dir="ltr">   xwindow = xcb_generate_id(xcon);</div><div dir="ltr">   xcb_create_window(</div><div dir="ltr">         xcon,</div><div dir="ltr">         XCB_COPY_FROM_PARENT,</div><div dir="ltr">         xwindow,</div><div dir="ltr">         xscreen->root,</div><div dir="ltr">         x, y,</div><div dir="ltr">         y, h,</div><div dir="ltr">         1,    /* border width */</div><div dir="ltr">         XCB_WINDOW_CLASS_INPUT_OUTPUT,</div><div dir="ltr">         xscreen->root_visual,</div><div dir="ltr">         XCB_CW_EVENT_MASK | XCB_CW_BACK_PIXMAP,</div><div dir="ltr">         valwin);</div><div dir="ltr"><br></div><div dir="ltr">   wm_hints();</div><div dir="ltr">}</div><div><br></div></font></div></div></div></div>