I want to write a program that continuously captures the screen and does some modifications to the image. A complete test program can be found at:
https://gist.github.com/blogsh/eb4dd4b96aca468c8bfa
However, I ran into some problems. The first experiment I did was using the Gdk root window, create a Cairo context from it and then using it's target as the source for another window, where the contents are painted to:
mScreenContext = Gdk::Screen::get_default()->get_root_window()->create_cairo_context()
...
context->set_source(mScreenContext->get_target(), 0, 0);
context->paint();
This works perfectly fine (variant 1 in the source above). It just draws the whole screen into another window. So my next step was to try to save the contents into a Cairo ImageSurface in order to modify it:
mImageContext->set_source(mScreenContext->get_target(), 0, 0);
mImageContext->paint();
context->set_source(mImageSurface, 0, 0);
context->paint();
The surprising thing is, that for the first drawing of the Gtk window the screen is captured and drawn. Unfortunately nothing is happening afterwards, still the initial screen is displayed. How can this behaviour explained? I must admit I don't know much about the underlying processes here, so maybe someone can give some hints?
A third variant using Gdk::Pixbuf
yields the exact same behaviour:
mScreenBuffer = Gdk::Pixbuf::create(mGdkRootWindow, 0, 0, mScreenWidth, mScreenHeight);
Gdk::Cairo::set_source_pixbuf(context, mScreenBuffer, 0, 0);
context->paint();
Finally (variant 4) I tried using X11
directly:
Display *display = XOpenDisplay((char*)0);
XImage *image = XGetImage(display, RootWindow(display, DefaultScreen(display)), 0, 0, mScreenWidth, mScreenHeight, AllPlanes, XYPixmap);
mScreenBuffer = Gdk::Pixbuf::create_from_data((const guint8*)image->data, Gdk::COLORSPACE_RGB, 0, 8, mScreenWidth, mScreenHeight, mScreenWidth);
Gdk::Cairo::set_source_pixbuf(context, mScreenBuffer, 0, 0);
context->paint();
XFree(image);
Actually, this works (although I didn't make any efforts to match the pixel format correctly yet), but it is awfully slow!
So I would appreciate any hints on what the issue with the two Gdk variants is and/or how to speed up the X11 approach. Or maybe someone knows a completely different approach to capture the screen in a fast way.
Unfortunately I'm not so familiar with that whole topic, but another idea would be to use a OpenGL-based window manager, where I could read the framebuffer directly? Does that make sense?
The main idea of the program is that I have a projector which I cannot position directly in front of a wall. So my idea is to capture the screen, do some bilinear transformation to account for the skwedness of the projection and then display the modified screen in another window, which will be shown on the projector...
XShmGetImage and XShmPutImage are faster than XGetImage and XPutImage. In the next example, I create two images: src and dst. In each iteration I save a screenshot in src, then I render a scaled version of it in dst.
The image below shows the example running in the window entitled "screencap". At low demand, it runs at 60 fps (as seen in the terminal at the top-right corner). At high demand, performance can drop to 25fps.
Test computer:
This is just an example. If you like how it performs, you should consider adding a better appropriate scaling algorithm and support for all pixels formats.
You can compile the example like this:
Call
cairo_surface_mark_dirty()
on the cairo surface that refers to the root window. Since you seem to only have a cairo context and not a surface directly: You can usecairo_surface_get_target()
to get the surface from the cairo context.Cairo assumes that you only draw to the surface through the cairo API, unless you tell it that you changed something outside of cairo. Downloading a screenshot is expensive and so cairo caches the result so that it can re-use it later.
Likely, you will also have to call
cairo_surface_flush()
before the call tocairo_surface_mark_dirty()
...