2015-10-06 170 views
0

我想写一个程序,不断捕捉屏幕并对图像做一些修改。Gdk/X11屏幕捕捉

https://gist.github.com/blogsh/eb4dd4b96aca468c8bfa

不过,我遇到了一些问题:一个完整​​的测试程序可以在这里找到。我所做的就是使用GDK根窗口的第一个实验,从中创建一个开罗上下文,然后使用它的目标为源动力的另一个窗口,其中内容被粉刷:

mScreenContext = Gdk::Screen::get_default()->get_root_window()->create_cairo_context() 
... 
context->set_source(mScreenContext->get_target(), 0, 0); 
context->paint(); 

这工作完全正常(变种1在上面的来源)。它只是将整个屏幕绘制到另一个窗口中。所以,我的下一步是尝试内容到开罗ImageSurface保存,以便修改:

mImageContext->set_source(mScreenContext->get_target(), 0, 0); 
mImageContext->paint(); 

context->set_source(mImageSurface, 0, 0); 
context->paint(); 

令人惊讶的是,对于gtk窗口的第一绘制画面被捕捉并绘制。不幸的是,之后没有任何事情发生,仍然显示初始屏幕。这种行为如何解释?我必须承认我对这里的底层流程并不了解,所以也许有人可以提供一些提示?

使用Gdk::Pixbuf第三个变种产生完全相同的行为:

mScreenBuffer = Gdk::Pixbuf::create(mGdkRootWindow, 0, 0, mScreenWidth, mScreenHeight); 
Gdk::Cairo::set_source_pixbuf(context, mScreenBuffer, 0, 0); 
context->paint(); 

最后(变型4)我尝试使用X11直接:

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

其实,这工作(虽然我没有尽可能正确匹配像素格式),但速度非常慢!

所以,我将不胜感激关于两个Gdk变体的问题是什么和/或如何加快X11方法。或者,也许有人知道一种完全不同的方法来快速捕捉屏幕。

不幸的是,我对整个主题并不是很熟悉,但是另一个想法是使用基于OpenGL的窗口管理器,我可以直接读取framebuffer?那有意义吗?

该方案的主要想法是,我有一台投影仪,我不能直接放在墙前。所以我的想法是捕捉屏幕,做一些双线性变换来说明投影的翘曲,然后在另一个窗口中显示修改后的屏幕,这将显示在投影机上...

+0

我还增加了问题的一个Python版本。源代码有点短,可能更容易理解... – blogsh

回答

1

XShmGetImage和XShmPutImage更快比XGetImage和XPutImage。 在下一个示例中,我创建了两个图像:src和dst。在每次迭代中,我都会在src中保存截图,然后在dst中渲染它的缩放版本。

下图显示了在名为“screencap”的窗口中运行的示例。 低需求时,运行速度为60 fps(如右上角的终端所示)。在高需求下,性能可降至25fps。

测试计算机:

Display resolution: 1920x1080 
Graphic card: ATI Radeon HD 4200 (integrated) 
CPU: AMD Phenom(tm) II X4 945, 3013.85 MHz 
Window manager: XFCE 4.12 (compositing off) 
Operating system: OpenBSD 5.9 
Tested also in Linux (openSUSE Leap 42.1) 

screencap running

#include <stdio.h> 
#include <errno.h> 
#include <stdlib.h> 
#include <math.h> 
#include <stdbool.h> 
#include <sys/shm.h> 
#include <X11/Xlib.h> 
#include <X11/Xutil.h> 
#include <X11/extensions/XShm.h> 
#ifdef __linux__ 
    #include <sys/time.h> 
#endif 

// comment the next line to busy-wait at each frame 
//#define __SLEEP__ 
#define FRAME 16667 
#define PERIOD 1000000 
#define NAME "screencap" 
#define NAMESP "   " 
#define BPP 4 

struct shmimage 
{ 
    XShmSegmentInfo shminfo ; 
    XImage * ximage ; 
    unsigned int * data ; // will point to the image's BGRA packed pixels 
} ; 

void initimage(struct shmimage * image) 
{ 
    image->ximage = NULL ; 
    image->shminfo.shmaddr = (char *) -1 ; 
} 

void destroyimage(Display * dsp, struct shmimage * image) 
{ 
    if(image->ximage) 
    { 
     XShmDetach(dsp, &image->shminfo) ; 
     XDestroyImage(image->ximage) ; 
     image->ximage = NULL ; 
    } 

    if(image->shminfo.shmaddr != (char *) -1) 
    { 
     shmdt(image->shminfo.shmaddr) ; 
     image->shminfo.shmaddr = (char *) -1 ; 
    } 
} 

int createimage(Display * dsp, struct shmimage * image, int width, int height) 
{ 
    // Create a shared memory area 
    image->shminfo.shmid = shmget(IPC_PRIVATE, width * height * BPP, IPC_CREAT | 0600) ; 
    if(image->shminfo.shmid == -1) 
    { 
     perror(NAME) ; 
     return false ; 
    } 

    // Map the shared memory segment into the address space of this process 
    image->shminfo.shmaddr = (char *) shmat(image->shminfo.shmid, 0, 0) ; 
    if(image->shminfo.shmaddr == (char *) -1) 
    { 
     perror(NAME) ; 
     return false ; 
    } 

    image->data = (unsigned int*) image->shminfo.shmaddr ; 
    image->shminfo.readOnly = false ; 

    // Mark the shared memory segment for removal 
    // It will be removed even if this program crashes 
    shmctl(image->shminfo.shmid, IPC_RMID, 0) ; 

    // Allocate the memory needed for the XImage structure 
    image->ximage = XShmCreateImage(dsp, XDefaultVisual(dsp, XDefaultScreen(dsp)), 
         DefaultDepth(dsp, XDefaultScreen(dsp)), ZPixmap, 0, 
         &image->shminfo, 0, 0) ; 
    if(!image->ximage) 
    { 
     destroyimage(dsp, image) ; 
     printf(NAME ": could not allocate the XImage structure\n") ; 
     return false ; 
    } 

    image->ximage->data = (char *)image->data ; 
    image->ximage->width = width ; 
    image->ximage->height = height ; 

    // Ask the X server to attach the shared memory segment and sync 
    XShmAttach(dsp, &image->shminfo) ; 
    XSync(dsp, false) ; 
    return true ; 
} 

void getrootwindow(Display * dsp, struct shmimage * image) 
{ 
    XShmGetImage(dsp, XDefaultRootWindow(dsp), image->ximage, 0, 0, AllPlanes) ; 
} 

long timestamp() 
{ 
    struct timeval tv ; 
    struct timezone tz ; 
    gettimeofday(&tv, &tz) ; 
    return tv.tv_sec*1000000L + tv.tv_usec ; 
} 

Window createwindow(Display * dsp, int width, int height) 
{ 
    unsigned long mask = CWBackingStore ; 
    XSetWindowAttributes attributes ; 
    attributes.backing_store = NotUseful ; 
    mask |= CWBackingStore ; 
    Window window = XCreateWindow(dsp, DefaultRootWindow(dsp), 
      0, 0, width, height, 0, 
      DefaultDepth(dsp, XDefaultScreen(dsp)), 
      InputOutput, CopyFromParent, mask, &attributes) ; 
    XStoreName(dsp, window, NAME); 
    XSelectInput(dsp, window, StructureNotifyMask) ; 
    XMapWindow(dsp, window); 
    return window ; 
} 

void destroywindow(Display * dsp, Window window) 
{ 
    XDestroyWindow(dsp, window); 
} 

unsigned int getpixel(struct shmimage * src, struct shmimage * dst, 
         int j, int i, int w, int h) 
{ 
    int x = (float)(i * src->ximage->width)/(float)w ; 
    int y = (float)(j * src->ximage->height)/(float)h ; 
    return src->data[ y * src->ximage->width + x ] ; 
} 

int processimage(struct shmimage * src, struct shmimage * dst) 
{ 
    int sw = src->ximage->width ; 
    int sh = src->ximage->height ; 
    int dw = dst->ximage->width ; 
    int dh = dst->ximage->height ; 

    // Here you can set the resulting position and size of the captured screen 
    // Because of the limitations of this example, it must fit in dst->ximage 
    int w = dw/2 ; 
    int h = dh/2 ; 
    int x = (dw - w) ; 
    int y = (dh - h)/2 ; 

    // Just in case... 
    if(x < 0 || y < 0 || x + w > dw || y + h > dh || sw < dw || sh < dh) 
    { 
     printf(NAME ": This is only a limited example\n") ; 
     printf(NAMESP " Please implement a complete scaling algorithm\n") ; 
     return false ; 
    } 

    unsigned int * d = dst->data + y * dw + x ; 
    int r = dw - w ; 
    int j, i ; 
    for(j = 0 ; j < h ; ++j) 
    { 
     for(i = 0 ; i < w ; ++i) 
     { 
      *d++ = getpixel(src, dst, j, i, w, h) ; 
     } 
     d += r ; 
    } 
    return true ; 
} 

int run(Display * dsp, Window window, struct shmimage * src, struct shmimage * dst) 
{ 
    XGCValues xgcvalues ; 
    xgcvalues.graphics_exposures = False ; 
    GC gc = XCreateGC(dsp, window, GCGraphicsExposures, &xgcvalues) ; 

    Atom delete_atom = XInternAtom(dsp, "WM_DELETE_WINDOW", False) ; 
    XSetWMProtocols(dsp, window, &delete_atom, True) ; 

    XEvent xevent ; 
    int running = true ; 
    int initialized = false ; 
    int dstwidth = dst->ximage->width ; 
    int dstheight = dst->ximage->height ; 
    long framets = timestamp() ; 
    long periodts = timestamp() ; 
    long frames = 0 ; 
    int fd = ConnectionNumber(dsp) ; 
    while(running) 
    { 
     while(XPending(dsp)) 
     { 
      XNextEvent(dsp, &xevent) ; 
      if((xevent.type == ClientMessage && xevent.xclient.data.l[0] == delete_atom) 
       || xevent.type == DestroyNotify) 
      { 
       running = false ; 
       break ; 
      } 
      else if(xevent.type == ConfigureNotify) 
      { 
       if(xevent.xconfigure.width == dstwidth 
        && xevent.xconfigure.height == dstheight) 
       { 
        initialized = true ; 
       } 
      } 
     } 
     if(initialized) 
     { 
      getrootwindow(dsp, src) ; 
      if(!processimage(src, dst)) 
      { 
       return false ; 
      } 
      XShmPutImage(dsp, window, gc, dst->ximage, 
          0, 0, 0, 0, dstwidth, dstheight, False) ; 
      XSync(dsp, False) ; 

      int frameus = timestamp() - framets ; 
      ++frames ; 
      while(frameus < FRAME) 
      { 
       #if defined(__SLEEP__) 
       usleep(FRAME - frameus) ; 
       #endif 
       frameus = timestamp() - framets ; 
      } 
      framets = timestamp() ; 

      int periodus = timestamp() - periodts ; 
      if(periodus >= PERIOD) 
      { 
       printf("fps: %d\n", (int)round(1000000.0L * frames/periodus)) ; 
       frames = 0 ; 
       periodts = framets ; 
      } 
     } 
    } 
    return true ; 
} 

int main(int argc, char * argv[]) 
{ 
    Display * dsp = XOpenDisplay(NULL) ; 
    if(!dsp) 
    { 
     printf(NAME ": could not open a connection to the X server\n") ; 
     return 1 ; 
    } 

    if(!XShmQueryExtension(dsp)) 
    { 
     XCloseDisplay(dsp) ; 
     printf(NAME ": the X server does not support the XSHM extension\n") ; 
     return 1 ; 
    } 

    int screen = XDefaultScreen(dsp) ; 
    struct shmimage src, dst ; 
    initimage(&src) ; 
    int width = XDisplayWidth(dsp, screen) ; 
    int height = XDisplayHeight(dsp, screen) ; 
    if(!createimage(dsp, &src, width, height)) 
    { 
     XCloseDisplay(dsp) ; 
     return 1 ; 
    } 
    initimage(&dst) ; 
    int dstwidth = width/2 ; 
    int dstheight = height/2 ; 
    if(!createimage(dsp, &dst, dstwidth, dstheight)) 
    { 
     destroyimage(dsp, &src) ; 
     XCloseDisplay(dsp) ; 
     return 1 ; 
    } 

    if(dst.ximage->bits_per_pixel != 32) 
    { 
     destroyimage(dsp, &src) ; 
     destroyimage(dsp, &dst) ; 
     XCloseDisplay(dsp) ; 
     printf(NAME ": This is only a limited example\n") ; 
     printf(NAMESP " Please add support for all pixel formats using: \n") ; 
     printf(NAMESP "  dst.ximage->bits_per_pixel\n") ; 
     printf(NAMESP "  dst.ximage->red_mask\n") ; 
     printf(NAMESP "  dst.ximage->green_mask\n") ; 
     printf(NAMESP "  dst.ximage->blue_mask\n") ; 
     return 1 ; 
    } 

    Window window = createwindow(dsp, dstwidth, dstheight) ; 
    run(dsp, window, &src, &dst) ; 
    destroywindow(dsp, window) ; 

    destroyimage(dsp, &src) ; 
    destroyimage(dsp, &dst) ; 
    XCloseDisplay(dsp) ; 
    return 0 ; 
} 

这仅仅是一个例子。如果你喜欢它的表现,你应该考虑添加一个更好的缩放算法,并支持所有像素格式。

您可以编译这样的例子:这是指根窗口开罗表面上

gcc screencap.c -o screencap -std=c99 -I/usr/X11R6/include -L/usr/X11R6/lib -lX11 -lXext -lm 
+0

谢谢!我问这个问题已经有一段时间了,同时我用XGetImage解决了问题,复制图像数据,然后使用OpenGL和着色器渲染变换。 Shm扩展可能会加快速度,所以我会看看! – blogsh

+0

我得到'screencap.c:函数'时间戳': screencap.c:105:20:错误:'tz'的存储大小未知' –

0

呼叫cairo_surface_mark_dirty()。由于您似乎只有一个cairo上下文而不是直接表面:您可以使用cairo_surface_get_target()从cairo上下文中获取曲面。

开罗假定你只通过cairo API绘制表面,除非你告诉它你改变了开罗以外的东西。下载截图很昂贵,所以cairo会缓存结果,以便稍后重新使用它。

有可能的,你还必须调用cairo_surface_mark_dirty()之前调用cairo_surface_flush() ...