Gdk / X11屏幕捕捉 [英] Gdk / X11 Screen Capture

查看:799
本文介绍了Gdk / X11屏幕捕捉的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想编写一个连续捕捉屏幕并对图像进行一些修改的程序。一个完整的测试程序可以在以下网址找到:



  #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

//注释下一行忙于每帧等待
//#define __SLEEP__
#define FRAME 16667
#define PERIOD 1000000
#define NAMEscreencap
#define NAMESP
#define BPP 4

struct shmimage
{
XShmSegmentInfo shminfo ;
XImage * ximage;
unsigned int * data; //将指向图像的BGRA打包像素
};

void initmage(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; (image-> shminfo.shmaddr!=(char *)-1)
{
shmdt(image-> shminfo.shmaddr)
}

if ;
image-> shminfo.shmaddr =(char *)-1;


$ b $ int createimage(Display * dsp,struct shmimage * image,int width,int height)
{
//创建共享存储区
image-> shminfo.shmid = shmget(IPC_PRIVATE,width * height * BPP,IPC_CREAT | 0600);
if(image-> shminfo.shmid == -1)
{
perror(NAME);
返回false;


//将共享内存段映射到该进程的地址空间
image-> shminfo.shmaddr =(char *)shmat(image-> shminfo .shmid,0,0);
if(image-> shminfo.shmaddr ==(char *)-1)
{
perror(NAME);
返回false;
}

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

//将共享内存段标记为删除
//即使此程序崩溃,它也会被删除
shmctl(image-> shminfo.shmid,IPC_RMID,0) ;

//分配XImage结构需要的内存
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:无法分配XImage结构\\\
);
返回false;
}

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

//请求X服务器附加共享内存段并同步
XShmAttach(dsp,& image-> shminfo);
XSync(dsp,false);
返回true;

$ b $ 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;
}

窗口createwindow(Display * dsp,int width,int height)
{
unsigned long mask = CWBackingStore;
XSetWindowAttributes属性;
attributes.backing_store = NotUseful;
mask | = CWBackingStore;
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);
返回窗口;
}

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

$ b $ 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;

//在这里你可以设置捕获屏幕的结果位置和大小
//由于这个例子的局限性,它必须符合dst-> ximage
int w = dw / 2;
int h = dh / 2;
int x =(dw - w);
int y =(dh - h)/ 2;

//假如...
if(x <0 || y< 0 || x + w> dw || y + h> dh || sw {
printf(NAME:这只是一个有限的例子);
printf(NAMESP请实现一个完整的缩放算法\\\
);
返回false;
}

unsigned int * d = dst-> data + y * dw + x;
int r = dw - w;
int j,i; (i = 0; i {
$ b为(j = 0; j {
$ b * d ++ = getpixel(src,dst,j,i,w,h);
}
d + = r;
}
返回true;

$ b $ 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();
长帧= 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;
休息;
}
else if(xevent.type == ConfigureNotify)
{
if(xevent.xconfigure.width == dstwidth
&& xevent.xconfigure。 height == dstheight)
{
initialized = true; (初始化)
{
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;
++帧;
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;
}
}
}
返回true;

$ b $ int main(int argc,char * argv [])
{
Display * dsp = XOpenDisplay(NULL);
if(!dsp)
{
printf(NAME:无法打开到X服务器的连接\\ n);
返回1;
}

if(!XShmQueryExtension(dsp))
{
XCloseDisplay(dsp);
printf(NAME:X服务器不支持XSHM扩展名\);
返回1;
}

int screen = XDefaultScreen(dsp);
struct shmimage src,dst;
initiatemage(& src);
int width = XDisplayWidth(dsp,screen);
int height = XDisplayHeight(dsp,screen);
if(!createimage(dsp,& src,width,height))
{
XCloseDisplay(dsp);
返回1;
}
启动映像(& dst);
int dstwidth = width / 2;
int dstheight = height / 2;
if(!createimage(dsp,&d; dst,dstwidth,dstheight))
{
destroyimage(dsp,& src);
XCloseDisplay(dsp);
返回1;
}

if(dst.ximage-> bits_per_pixel!= 32)
{
destroyimage(dsp,& src);
destroyimage(dsp,&dst);
XCloseDisplay(dsp);
printf(NAME:这只是一个有限的例子);
printf(NAMESP请使用\\\
添加对所有像素格式的支持);
printf(NAMESPdst.ximage-> bits_per_pixel\\\
);
printf(NAMESPdst.ximage-> red_mask\\\
);
printf(NAMESPdst.ximage-> green_mask\\\
);
printf(NAMESPdst.ximage-> blue_mask\\\
);
返回1;
}

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

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

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



你可以像这样编译这个例子:


$ b $

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


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:

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)

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

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:

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

这篇关于Gdk / X11屏幕捕捉的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆