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

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);
}
}
}

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);

Related

How can I get at the raw pixel buffer of the window after drawing with OpenGL in SDL2?

This piece of SDL2 code draws some white pixels on-screen using OpenGL, then grabs the pixels field of the window's SDL_Surface and loops through it, printing out the values of the contents. Even though I just drew a white triangle, the loop shows that there's nothing but zeros in that buffer (the code just prints 0 to standard out over and over).
How can I actually get at the modified pixel buffer, in something like RGB or ARGB or RGBA format?
#include <SDL2/SDL.h>
#include <SDL2/SDL_opengl.h>
#include <GL/glu.h>
int main()
{
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
const int WINDOW_WIDTH = 100;
const int WINDOW_HEIGHT = 100;
SDL_Window *window = SDL_CreateWindow("OpenGL Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_OPENGL);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_SetSwapInterval(1);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
GLenum error = glGetError();
if( error != GL_NO_ERROR )
{
printf( "Error initializing OpenGL! %s\n", gluErrorString(error));
}
glClearColor(0, 0, 0, 1);
int quit = 0;
SDL_Event event;
while (!quit)
{
while (SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_QUIT:
quit = 1;
break;
}
}
glBegin(GL_TRIANGLES);
glColor3f(255, 255, 255);
glVertex2f(0, 0);
glVertex2f(0, 1);
glVertex2f(1, 0);
glEnd();
SDL_GL_SwapWindow(window);
SDL_Surface *surface = SDL_GetWindowSurface(window);
int pixel_depth = SDL_BYTESPERPIXEL(surface->format->format);
char *pixels = (char*) surface->pixels;
int max_value = 0;
for (int i = 0; i < WINDOW_WIDTH * WINDOW_HEIGHT * pixel_depth; i++)
{
if (pixels[i] > max_value)
{
max_value = pixels[i];
}
}
SDL_FreeSurface(surface);
SDL_Log("%d", max_value);
}
SDL_Quit();
return 0;
}
SDL_GetWindowSurface() doesn't work with OpenGL:
You may not combine this with 3D or the rendering API on this window.
Use glReadPixels() instead.
Use PBO to read data from from the pixel buffer.
glReadBuffer(GL_COLOR_ATTACHMENT0);
writeIndex = (writeIndex + 1) % 2;
readIndex = (readIndex + 1) % 2;
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[writeIndex]);
// copy from framebuffer to PBO asynchronously. it will be ready in the NEXT frame
glReadPixels(0, 0, SCR_WIDTH, SCR_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
// now read other PBO which should be already in CPU memory
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo[readIndex]);
unsigned char* Data = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);

Drawing to X11 root window

I've followed How to upload 32 bit image to server-side pixmap and XDestroy(ximg); segfaults.
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
int
main(int argc, char **argv) {
Display *dpy;
Window root;
Screen *screen;
GC gc;
XImage *ximg;
Pixmap pm;
int depth, h, w;
char *img;
dpy = XOpenDisplay(0);
if (!dpy) {
fputs("cannot open display\n", stderr);
return 1;
}
screen = DefaultScreenOfDisplay(dpy);
h = HeightOfScreen(screen);
w = WidthOfScreen(screen);
root = DefaultRootWindow(dpy);
depth = DefaultDepth(dpy, DefaultScreen(dpy));
img = malloc(depth/8 * h * w);
if (img == NULL) {
perror("malloc() failed");
return 1;
}
memset(img, 0xFF, depth/8 * h * w);
ximg = XCreateImage(dpy, CopyFromParent, depth, ZPixmap, 0, img, w, h, 32, 0);
pm = XCreatePixmap(dpy, root, w, h, depth);
gc = XCreateGC(dpy, pm, 0, NULL);
XPutImage(dpy, pm, gc, ximg, 0, 0, 0, 0, w, h);
XCopyArea(dpy, pm, root, gc, 0, 0, w, h, 0, 0);
free(img);
XFreePixmap(dpy, pm);
XDestroyImage(ximg);
XFreeGC(dpy, gc);
XCloseDisplay(dpy);
return 0;
}
Also, according to How to draw an image from file on window with Xlib I should include something like
XEvent ev;
while (1) {
XNextEvent(dpy, &ev);
if (ev.type == Expose)
XCopyArea(dpy, pm, root, gc, 0, 0, w, h, 0, 0);
}
before cleaning up, but how/when do I give system resources back then?
UPDATE
Apparently free(img); was causing the segfault, because XDestroyImage(ximg); is itself freeing the image. But still, where do I put that event loop and what terminating condition should it have so that I can free system resources accordingly?
According to XDestroyImage(3):
Note that when the image is created using XCreateImage, XGetImage, or XSubImage, the destroy procedure that the XDestroyImage function calls frees both the image structure and the data pointed to by the image structure.
So free(img); is redundant and will cause a SegFault.

Linux Xlib erroneous alpha handling [duplicate]

I would like to create a semi transparent white window in XLib, but the window is not semi translucent, it remains fully opaque. I use the compton compositor and there are transparent windows in the system, so the problem is in the code:
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
int main(int argc, char* argv[])
{
Display* display = XOpenDisplay(NULL);
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 = 0x80ffffff;
Window win = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 300, 200, 0, vinfo.depth, InputOutput, vinfo.visual, CWColormap | CWBorderPixel | CWBackPixel, &attr);
XSelectInput(display, win, StructureNotifyMask);
GC gc = XCreateGC(display, win, 0, 0);
Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(display, win, &wm_delete_window, 1);
XMapWindow(display, win);
int keep_running = 1;
XEvent event;
while (keep_running) {
XNextEvent(display, &event);
switch(event.type) {
case ClientMessage:
if (event.xclient.message_type == XInternAtom(display, "WM_PROTOCOLS", 1) && (Atom)event.xclient.data.l[0] == XInternAtom(display, "WM_DELETE_WINDOW", 1))
keep_running = 0;
break;
default:
break;
}
}
XDestroyWindow(display, win);
XCloseDisplay(display);
return 0;
}
X11 expects pre-multiplied colours, i.e. real opaque colours need to be multiplied by the alpha value (and scaled accordingly, i.e. divided by 256 when channel widths is 8 bits). This format is easier to work with when you need to combine many levels. See formulas here. There's less computation when everything is pre-multiplied.
So you need to multiply each of your R, G and B channels by the alpha value (0x80) and divide by 256.
Setting the background to 0x80808080 gives the desired result:
Note the result is different from what #patthoyts suggests: here only the window proper is semi-transparent, the WM decoration stays opaque; there both the window proper and the decoration are made transparent by the WM (and the WM does the necessary colour blending).
You need to set _NET_WM_WINDOW_OPACITY. Here is a snippet to add in before you map the window:
double alpha = 0.8;
unsigned long opacity = (unsigned long)(0xFFFFFFFFul * alpha);
Atom XA_NET_WM_WINDOW_OPACITY = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
XChangeProperty(display, win, XA_NET_WM_WINDOW_OPACITY, XA_CARDINAL, 32,
PropModeReplace, (unsigned char *)&opacity, 1L);
Note you should add #include <X11/Xatom.h> to get the declaration of XA_CARDINAL.
I'm not entirely sure how stable this interface is. This appears to be a proposed extension to the Extended Window Manager Hints specification but has not made it into any final revision from what I can see. I know that this is how Tk implements transparency support on unix though.
The result looks like:

Empty or transparent window with Xlib showing border lines only

Is there a way to create a window with Xlib which only display the border lines, title, close button and that you can move with the mouse? The content of the window must be empty (or "totally transparent", although "transparency" sounds more like an effect I don't need). Basically the window should show the background area.
I'm not sure if it is what you want, but following code creates an X window with transparent background but still using the window decoration of your window manager.
It will only work though if your X11 and graphics hardware configuration supports visuals with a depth of 32 bit.
#include <X11/Xlib.h>
#include <X11/Xutil.h>
int main(int argc, char* argv[])
{
Display* display = XOpenDisplay(NULL);
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 = 0;
Window win = XCreateWindow(display, DefaultRootWindow(display), 0, 0, 300, 200, 0, vinfo.depth, InputOutput, vinfo.visual, CWColormap | CWBorderPixel | CWBackPixel, &attr);
XSelectInput(display, win, StructureNotifyMask);
GC gc = XCreateGC(display, win, 0, 0);
Atom wm_delete_window = XInternAtom(display, "WM_DELETE_WINDOW", 0);
XSetWMProtocols(display, win, &wm_delete_window, 1);
XMapWindow(display, win);
int keep_running = 1;
XEvent event;
while (keep_running) {
XNextEvent(display, &event);
switch(event.type) {
case ClientMessage:
if (event.xclient.message_type == XInternAtom(display, "WM_PROTOCOLS", 1) && (Atom)event.xclient.data.l[0] == XInternAtom(display, "WM_DELETE_WINDOW", 1))
keep_running = 0;
break;
default:
break;
}
}
XDestroyWindow(display, win);
XCloseDisplay(display);
return 0;
}

How to upload 32 bit image to server-side pixmap

I'm trying to create server-side RGBA pixmap from client side buffer. CreatePixmap & CreateImage work ok for 32 and 24 bit, but XPutImage result in Match Error returned by server
X Error of failed request: BadMatch (invalid parameter attributes)
Major opcode of failed request: 72 (X_PutImage)
Serial number of failed request: 8
Current serial number in output stream: 8
server does support 32 bit pixmaps (xdpyinfo output: https://gist.github.com/2582961). Same behaviour on ubuntu 12.04 (X.Org version: 1.11.3) and OSX with X.app (X.Org version: 1.10.3)
Why following code fails?
#include <stdlib.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
int width = 100;
int height = 100;
int depth = 32; // works fine with depth = 24
int bitmap_pad = 32; // 32 for 24 and 32 bpp, 16, for 15&16
int bytes_per_line = 0; // number of bytes in the client image between the start of one scanline and the start of the next
Display *display=XOpenDisplay(0);
unsigned char *image32=(unsigned char *)malloc(width*height*4);
XImage *img = XCreateImage(display, CopyFromParent, depth, ZPixmap, 0, image32, width, height, bitmap_pad, bytes_per_line);
Pixmap p = XCreatePixmap(display, XDefaultRootWindow(display), width, height, depth);
XPutImage(display, p, DefaultGC(display, 0), img, 0, 0, 0, 0, width, height); // 0, 0, 0, 0 are src x,y and dst x,y
XEvent ev;
while (1) {
XNextEvent(display, &ev);
}
}
Update: It looks like I finally got answer: use GC associated with pixmap instead of DefaultGC (which has depth of root window)
#include <stdlib.h>
#include <X11/Xlib.h>
int main(int argc, char **argv)
{
int width = 100;
int height = 100;
int depth = 32; // works fine with depth = 24
int bitmap_pad = 32; // 32 for 24 and 32 bpp, 16, for 15&16
int bytes_per_line = 0; // number of bytes in the client image between the start of one scanline and the start of the next
Display *display=XOpenDisplay(0);
unsigned char *image32=(unsigned char *)malloc(width*height*4);
XImage *img = XCreateImage(display, CopyFromParent, depth, ZPixmap, 0, image32, width, height, bitmap_pad, bytes_per_line);
Pixmap p = XCreatePixmap(display, XDefaultRootWindow(display), width, height, depth);
XGCValues gcvalues;
GC gc = XCreateGC(display, p, 0, &gcvalues);
XPutImage(display, p, gc, img, 0, 0, 0, 0, width, height); // 0, 0, 0, 0 are src x,y and dst x,y
XEvent ev;
while (1) {
XNextEvent(display, &ev);
}
}
The problem is with DefaultGC() which return a GC with bit depth of system default screen. If you look at line 53 of your gist paste you see that this is 24:
depth of root window: 24 planes
On line 63 you see that it uses 0x22 as default which is shown in more detail in line 64 to 70:
visual:
visual id: 0x22
class: TrueColor
depth: 24 planes
available colormap entries: 256 per subfield
red, green, blue masks: 0xff0000, 0xff00, 0xff
significant bits in color specification: 8 bits
You could probably do this a bit nicer but as a start you can try this:
Note: This uses system visuals so most probably only support depth of 24 or 32.
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#ifdef DEBUG
int dbg = 1;
#else
int dbg = 0;
#endif
/* Return a GC based on depth */
int gc_depth(int depth, Display *dpy, Window scr, Window root, GC *gc)
{
Window win;
Visual *visual;
XVisualInfo vis_info;
XSetWindowAttributes win_attr;
unsigned long win_mask;
if(!XMatchVisualInfo(dpy, scr, depth, TrueColor, &vis_info)) {
fprintf(stderr,
" * ERR: %d depth not supported\n",
depth
);
return 1;
}
visual = vis_info.visual;
win_attr.colormap = XCreateColormap(dpy, root, visual, AllocNone);
win_attr.background_pixel = 0;
win_attr.border_pixel = 0;
win_mask = CWBackPixel | CWColormap | CWBorderPixel;
win = XCreateWindow(
dpy, root,
0, 0,
100, 100, /* dummy size */
0, depth,
InputOutput, visual,
win_mask, &win_attr);
/* To flush out any errors */
if (dbg) XSync(dpy, True);
*gc = XCreateGC(dpy, win, 0, 0);
if (dbg) XSync(dpy, True);
XDestroyWindow(dpy, win);
if (dbg) XSync(dpy, True);
return 0;
}
int main(void)
{
int w = 100;
int h = 100;
int depth = 32;
int bitmap_pad = 32;
int bpl = 0;
Display *dpy;
Window root;
Window scr;
GC gc;
int root_depth;
Pixmap pm;
XImage *img;
unsigned char *buf_img;
if(!(dpy = XOpenDisplay(NULL))) {
fprintf(stderr,
" * ERR: Failed to open display.\n");
return 1;
}
#ifdef DEBUG
/* To get errors in order, slows down
* One can also define int _Xdebug = 1;
* */
XSynchronize(dpy, True);
#endif
root = XDefaultRootWindow(dpy);
scr = XDefaultScreen(dpy);
if ((buf_img = malloc(w * h * 4)) == NULL) {
fprintf(stderr,
" * ERR: Unable to alloacte %d bytes\n",
w * h * 4);
return 1;
}
root_depth = DefaultDepth(dpy, scr);
fprintf(stderr,
"Default depth: %d\n",
root_depth);
/* This should be doen more nice */
if (depth != root_depth) {
if (gc_depth(depth, dpy, scr, root, &gc) != 0)
return 1;
} else {
gc = DefaultGC(dpy, 0);
}
img = XCreateImage(
dpy, CopyFromParent,
depth, ZPixmap,
0, (char *)buf_img,
w, h,
bitmap_pad, bpl);
/* To flush out any errors */
if (dbg) XSync(dpy, True);
pm = XCreatePixmap(
dpy, root,
w, h,
depth);
if (dbg) XSync(dpy, True);
XPutImage(
dpy, pm,
gc, img,
0, 0,
0, 0,
w, h);
if (dbg) XSync(dpy, True);
XFreePixmap(dpy, pm);
XDestroyImage(img);
XFreeGC(dpy, gc);
if (dbg) XSync(dpy, True);
fprintf(stderr,
"OK!\n");
return 0;
}
Well, your code works for 32 bits images if you just create a GC passing a drawable on argument which is 32 bits. XCreateGC(dpy, drawable, 0, 0), where drawable can be a pixmap with 32 bits depth. It works perfect with me.

Resources