Search code examples
cimagex11alphaxlib

X11 : Cannot draw an image on transparent window created with XCreateWindow


I am trying to create a simple X11 window, which should display a PNG file with transparent areas. I want the window itself to have no (opaque) background, so that the transparent areas in the PNG shows what there is behind the window.

tl;dr I cannot put an image on a semi-transparent window; it gives "Bad Match".

I can successfully create a semi-transparent window using XCreateWindow and XMatchVisualInfo :

XSetWindowAttributes attr;
attr.colormap = XCreateColormap(display, DefaultRootWindow(display), 
        vinfo.visual, AllocNone);
attr.border_pixel = 0;
attr.background_pixel = 0x80800000; // Red, semi-transparent

Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0,
        width, height, 0, vinfo.depth, InputOutput, vinfo.visual,
        CWColormap | CWBorderPixel | CWBackPixel, &attr);

(Full source code below)

I then create an image using :

// "image32" is a generated image - see source code below
XImage *ximage = XCreateImage(display, visual, DefaultDepth(display,DefaultScreen(display)),
        ZPixmap, 0, image32, width, height, 32, 0);

And display the image during the Expose event :

XPutImage(display, window, DefaultGC(display, 0), ximage,
        0, 0, 0, 0, width, height);

I compile with gcc test.c -L/usr/X11R6/lib -lX11 -lXrandr -o test and run with ./test :

X Error of failed request:  BadMatch (invalid parameter attributes)
  Major opcode of failed request:  72 (X_PutImage)
  Serial number of failed request:  11
  Current serial number in output stream:  12

Note : If I replace the lines creating the window (XCreateWindow) with these :

Window window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0,
        width, height, 1, 0, 0);

It displays a window correctly; however, there is no transparency.

I read the docs about XCreateWindow, XPutImage, XCreateImage and tried playing around with multiple parameters, unsuccessfully.

I have read this SO question and tried playing around with color depth; since the docs mentionned "Bad Match" can be also thrown for incorrect visual, I have checked that the same visual was sent at all places in my code.

Any help is appreciated.

Thanks!

Full source code :

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

// Window size
int height = 256, width = 256;

XImage *CreateTrueColorImage(Display *display, Visual *visual)
{

    int i, j;
    unsigned char *image32=(unsigned char *)malloc(width*height*4);
    unsigned char *p=image32;
    for(i=0; i<width; i++)
    {
        for(j=0; j<height;j++)
        {
            *p++ = i;
            *p++ = i;
            *p++ = j;
            *p++ = j; // alpha channel (should progressively get transparent towards left)
        }
    }

    // Replacing "DefaultDepth(display,DefaultScreen(display))" with a hardcoded 
    // 24 or 32 still doesn't work with XCreateWindow. XCreateSimpleWindow works
    // with hardcoded 24, but not 32.
    return XCreateImage(display, visual, DefaultDepth(display,DefaultScreen(display)),
            ZPixmap, 0, image32, width, height, 32, 0);
}

int main(int argc, char **argv)
{
    XImage *ximage;
    Display *display = XOpenDisplay(NULL);
    Visual *visual = DefaultVisual(display, 0);

    XVisualInfo vinfo;
    XMatchVisualInfo(display, DefaultScreen(display), 32, TrueColor, &vinfo);

    XSetWindowAttributes attr;
    attr.colormap = XCreateColormap(display, DefaultRootWindow(display), 
            vinfo.visual, AllocNone);
    attr.border_pixel = 0;
    attr.background_pixel = 0x80800000; // Red, semi-transparent

    //Window window = XCreateSimpleWindow(display, RootWindow(display, 0), 0, 0,
    //      width, height, 1, 0, 0);
    Window window = XCreateWindow(display, DefaultRootWindow(display), 0, 0,
            width, height, 0, vinfo.depth, InputOutput, vinfo.visual,
            CWColormap | CWBorderPixel | CWBackPixel, &attr);

    ximage = CreateTrueColorImage(display, vinfo.visual);
    XSelectInput(display, window, ButtonPressMask|ExposureMask);
    XMapWindow(display, window);

    while(1)
    {
        XEvent event;
        XNextEvent(display, &event);
        switch(event.type)
        {
        case Expose:
            XPutImage(display, window, DefaultGC(display, 0), ximage,
                    0, 0, 0, 0, width, height);
            break;
        case ButtonPress:
            exit(0);
        }
    }
}

Solution

  • I managed to make it work by making two changes. First, instead of using DefaultGC(display, 0) you should create a GC for your specific window.

    GC gc = XCreateGC(display, window, 0, 0);
    

    With that if you hardcode the depth of XCreateImage to 32 it should work correctly. And you can also use the depth provided by XVisualInfo like so

    XCreateImage(display, vinfo.visual, vinfo.depth,
            ZPixmap, 0, image32, width, height, 32, 0);