# 1. XGetImage
XGetImage是最简单的获取窗口图像的方法。
return XGetImage(
display,
window,
0,
0,
width,
height,
AllPlanes,
ZPixmap
);
# DISPLAY
display是X11中显示器的抽象概念,不一定指一个物理显示器,也可能是一个由Xvfb创建的虚拟显示器。它可以由以下API获取:
return XOpenDisplay(display.c_str());
其中,display
是一个屏幕的标识符,它可以是类似:99
的形式(Xvfb创建的默认DISPLAY),或是NULL,表示当前DISPLAY。
# Window
Window即窗口,标识着一个应用程序窗口。因为X11是C/S架构,所以窗口的数据也分别存放在服务端和客户端两部分。在客户端这边的图像叫做XImage,在服务端那边则叫做Pixmap。X11中Window对象只是一个标识,它可以使用以下方式获得:
RootWindow(display, DefaultScreen(display));
上面是获取当前显示器的根窗口,也可以通过XGetInputFocus
获取当前焦点所在的窗口:
XGetInputFocus(display, &focused, &unknown);
还可以通过XGetWindowAttributes
获取窗口的一些信息:
XWindowAttributes window_attributes;
XGetWindowAttributes(display, window, &window_attributes);
# XImage
从XGetImage
可以获取XImage对象,这个对象中包含着窗口/屏幕(根窗口)的像素、尺寸、对齐格式等信息。
对于XImage,我们可以调用XGetPixel
函数来获取某个像素的信息。但是这种方式需要进行额外拷贝,因此CPU占用率较高。比较常见的方式还是直接读取它的data
属性。这是char*
类型数据,包含着全部的图片像素信息,对齐格式为BGRA,即使用四个u8存储一个像素信息。尺寸可以使用如下方式计算获得:
im.height * im.width * 4
另外,它的顺序是从左上到右下,与OpenGL纹理Y轴相反,因此在使用OpenGL时需要提前处理图像反转Y轴,或在创建纹理时修改纹理坐标。
# 2. XShm
正是因为XGetImage每次从服务端申请图像资源,因此需要经过多次分配和拷贝,性能较低,因此很多程序会选择使用XShmGetImage的方法进行替代。XShm是一个X11扩展,提供了通过共享内存获取图像的方法。它可以做到一次分配多次使用,从而减少了内存分配和拷贝次数。
void XwrapWindow::begin_get_image() {
int screen = DefaultScreen(display);
auto attr = this->get_attributes();
auto image = XShmCreateImage(
display,
attr.visual,
attr.depth,
ZPixmap,
NULL,
&shminfo,
attr.width,
attr.height
);
shminfo.shmid = shmget(
IPC_PRIVATE,
image->bytes_per_line * image->height,
IPC_CREAT | 0777
);
shminfo.shmaddr = image->data = (char*)shmat(shminfo.shmid, 0, 0);
shminfo.readOnly = False;
XShmAttach(display, &shminfo);
this->image = image;
}
使用XShm需要先进行初始化,这包括共享内存区的申请和图像内存分配。
void XwrapWindow::get_xshm_image() {
XShmGetImage(display, window, image, 0, 0, AllPlanes);
}
获取图像方式与XGetImage
基本一致,只是无需指定要获取的图片尺寸,这些信息包括在了已经分配好了的image对象中。这个函数可以在程序中多次调用。
void XwrapWindow::end_get_image() {
XShmDetach(display, &shminfo);
shmdt(shminfo.shmaddr);
shmctl(shminfo.shmid, IPC_RMID, 0);
XDestroyImage(image);
}
在析构阶段,首先释放共享内存区,然后销毁图片。
# 后话
除了这两种方式,还可以利用XComposite
扩展。具体思路大概是重定向窗口,然后获取窗口的Pixmap,从中获取图像。可惜几经尝试无法成功。
另外,通过X11 API无法获取最小化或在其他虚拟桌面的窗口。当然,具体还要看窗口管理器策略。有些窗口管理器会创建一个相当大的屏幕,然后将不需要的窗口移出当前视图,这样窗口仍然被认为在当前屏幕上,是可以进行捕获的。但类似i3这种使用MAP和UNMAP管理窗口的就不行了,因为窗口被UNMAP之后就不会进行更新,也无法获取Pixmap,此时调用XGetImage大概率会获得一个全黑图像。