十二、Linux實現截屏小工具

系列文章目錄

本系列文章記錄在Linux操作系統下,如何在不依賴QT、GTK等開源GUI庫的情況下,基于x11窗口系統(xlib)圖形界面應用程序開發。之所以使用x11進行窗口開發,是在開發一個基于duilib跨平臺的界面庫項目,使用gtk時,發現基于gtk的開發,依賴的動態庫太多,發布時太麻煩,gtk不支持靜態庫編譯發布。所以我們決定使用更底層的xlib接口。在此記錄下linux系統下基于xlib接口的界面開發

一、xlib創建窗口
二、xlib事件
三、xlib窗口圖元
四、xlib區域
五、xlib繪制按鈕控件
六、繪制圖片
七、xlib窗口渲染
八、實現編輯框控件
九、異形窗口
十、基于xlib實現定時器
十一、xlib繪制編輯框-續
十二、Linux實現截屏小工具


文章目錄

  • 系列文章目錄
    • 1.實現第一個截屏程序
    • 2.實現鼠標選擇區域
    • 3.解決鼠標拖動閃爍問題
    • 4.總結


在這篇文章中我們將記錄如何在Linux操作系統下基xlib接口實現一個截屏工具。以下實驗代碼在ubuntu20.04下測試運行。理論上以下代碼可以不經過修改或少量修改后編譯運行在各種國產化操作系統 之上。

1.實現第一個截屏程序

想要在Linux下實現截屏我們首先要做的就是獲取關于桌面這個窗口的句柄,然后從桌面這個窗口中獲取屏幕每個像素的rgb值 ,最后存儲到磁盤文件。xlib為我們提供了獲取桌面窗口的方式即RootWindow,xlib提供的XGetImage和XGetPixel可以從Window或是Pixmap類型的對象中獲取到圖像相應像素的rgb顏色值。第一個小程序,我們截取整個屏幕,保存為bmp圖片。實現代碼如下

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>#pragma pack(push, 1)
typedef struct {unsigned short bfType;          // 文件類型,必須是0x4D42 ('BM')unsigned int bfSize;            // 文件大小unsigned short bfReserved1;     // 保留,必須為0unsigned short bfReserved2;     // 保留,必須為0unsigned int bfOffBits;         // 從文件頭到實際位圖數據之間的字節偏移量
} BITMAPFILEHEADER;typedef struct {unsigned int biSize;            // 本結構所占字節數signed int biWidth;             // 圖像寬度signed int biHeight;            // 圖像高度unsigned short biPlanes;        // 目標設備的平面數,總設置為1unsigned short biBitCount;      // 每個像素的位數(1/4/8/24/32)unsigned int biCompression;     // 壓縮類型unsigned int biSizeImage;       // 圖像數據大小signed int biXPelsPerMeter;     // 水平分辨率signed int biYPelsPerMeter;     // 垂直分辨率unsigned int biClrUsed;         // 使用的顏色數unsigned int biClrImportant;    // 重要顏色數
} BITMAPINFOHEADER;
#pragma pack(pop)void WriteBmp(const char* filename, int width, int height, unsigned char* data) {FILE *fp = fopen(filename, "wb");if (!fp) return;// 計算行填充至4字節邊界所需的額外字節int padding = (4 - (width * 3) % 4) % 4;unsigned  int filesize = 54 + (3 * width + padding) * height;BITMAPFILEHEADER fileHeader = {0x4D42, filesize, 0, 0, 54};BITMAPINFOHEADER fileInfo = {40, width, height, 1, 24, 0, 0, 0, 0, 0, 0};fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp);fwrite(&fileInfo, sizeof(BITMAPINFOHEADER), 1, fp);for(int i = 0; i < height; ++i) {fwrite(data + (height - 1 - i) * width * 3, 3, width, fp); // BMP存儲時按行倒序存儲fseek(fp, padding, SEEK_CUR); // 跳過padding字節}fclose(fp);
}unsigned char *GetRootWindowPixels(Display *display,int x,int y,int width,int height) {int screen = DefaultScreen(display);Window rootWindow = RootWindow(display,screen);XImage *image = XGetImage(display,rootWindow,x,y,width,height,AllPlanes,ZPixmap);unsigned char *buffer = new unsigned char[width*height*3];for(int y=0;y<height;y++){unsigned char *rowData = buffer + y * width*3;for(int x=0;x<width;x++){//獲取每個像素的rgb值unsigned long pixel = XGetPixel(image,x,y);rowData[3*x+2] = (pixel>>16)&0xFF;rowData[3*x+1] = (pixel>>8)&0xFF;rowData[3*x+0] = (pixel>>0)&0xFF;}}XDestroyImage(image);return buffer;
}int main() {Display *display;int screen;/* 打開與X服務器的連接 */display = XOpenDisplay(NULL);if (display == NULL) {fprintf(stderr, "無法打開X顯示器\n");exit(1);}screen = DefaultScreen(display);int width = DisplayWidth(display,screen);int height = DisplayHeight(display,screen);unsigned char *buffer = GetRootWindowPixels(display,0,0,width,height);WriteBmp("screen.bmp",width,height,buffer);delete []buffer;XCloseDisplay(display);return 0;
}

以上示例程序中,我們把屏幕的截圖保存到bmp圖片。這里保存為bmp是因為該圖片格式相對簡單,根據已有的rgb顏色值寫生成圖片文件時,不需要太多額外的代碼或是第三方庫的參與即可生成圖片文件。以上WriteBmp函數代碼由通義大語言模型生成;接下來示例程序,我們不再給出WriteBmp的實現,后續保存圖片默認都是使用該函數將結果生成bmp圖片文件。編譯以上程序運行后即可在可執行文件同目錄下看到一個名為screen.bmp的文件。保存的是我們運行程序時的屏幕上的像素。

2.實現鼠標選擇區域

前一個示例中我們直接把整個屏幕的數據全部都存儲下來,放到了bmp圖片中,作為屏幕截圖工具,我們有時并不需要整個屏幕,可能只需要其中的一部分區域。這時我們可以發揮界面應用程序的優勢,鼠標、鍵盤的交互。我們創建一個全屏的窗口,把桌面圖像和一個半透明的黑色進行混合,繪制到我們創建的全屏窗口上,這樣便于用戶區別當前是對桌面進行操作還是在做截屏操作;此外我們還需要一個Pixmap來保存截圖工具開始時屏幕的狀態,一是因為隨著用戶操作的進行,桌面上的像素可能發生變化,二是當我們對全屏窗口區域進行選擇時,我們可以使用這個保存的窗口像素來填充選中的區域,使得選中區域高亮顯示。

暫存桌面像素

將桌面圖片暫存到內存的實現如下

void CreateRootPixmap(Display *display,Window window,int width, int height) {int screen = DefaultScreen(display);glbRootPixmap = XCreatePixmap(display, window, width, height, DefaultDepth(display,screen));XImage *rootImage = XGetImage(display, RootWindow(display,screen),0,0,width,height,AllPlanes,ZPixmap);GC gc = XCreateGC(display,window,0,nullptr);XPutImage(display, glbRootPixmap, gc, rootImage, 0, 0, 0, 0, width, height);XFreeGC(display,gc);XDestroyImage(rootImage);
}

創建黑色半透明桌面圖片

創建一個黑色半透明混合桌面圖片的pixmap實現如下

void CreateDarkPixmap(Display *display,Window window, int width, int height) {int screen = DefaultScreen(display);glbDarkPixmap = XCreatePixmap(display,window, width,height, DefaultDepth(display,screen));//Pixmap blackBackground = XCreatePixmap(display, window,width,height, 32);XRenderPictFormat *format  = XRenderFindVisualFormat(display,DefaultVisual(display,screen));XRenderColor color = {0x0,0x0,0x0,0x8000};Picture blackBackgroundPicture = XRenderCreatePicture(display,blackBackground,XRenderFindStandardFormat(display, PictStandardARGB32),0,nullptr);XRenderFillRectangle(display,PictOpSrc,blackBackgroundPicture,&color,0,0,width,height);Picture picture = XRenderCreatePicture(display,glbRootPixmap, format,0, nullptr);Picture window_picture = XRenderCreatePicture(display,glbDarkPixmap,format,0,nullptr);XRenderComposite(display,PictOpOver,picture, None,window_picture,0,0,0,0,0,0,width,height);XRenderComposite(display,PictOpOver,blackBackgroundPicture, None,window_picture,0,0,0,0,0,0,width,height);XRenderFreePicture(display,blackBackgroundPicture);XRenderFreePicture(display,window_picture);XRenderFreePicture(display, picture);//XFreeGC(m_x11Window->display,gc);XFreePixmap(display,blackBackground);
}

改變窗口樣式

此外我們創建的窗口,還需要去掉窗口管理器給窗口默認的標題欄、邊框信息、在截圖時不希望切換窗口。此外還需要將窗口的CWOverrideRedirect屬性設置為True,否則像ubuntu系統下通知欄不會被覆蓋。實現以上功能的代碼如下

void ChangeWindowStyle(Display *display, Window window) {Atom wm_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);Atom wm_type_dock = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False);XChangeProperty(display, window, wm_type, XA_ATOM, 32, PropModeReplace,(unsigned char *)&wm_type_dock, 1);// Disable window decorate,we will create a window without title bar and borderAtom wm_state = XInternAtom(display, "_NET_WM_STATE", False);Atom wm_state_skip_taskbar = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False);Atom wm_state_skip_pager = XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeReplace,(unsigned char *)&wm_state_skip_taskbar, 1);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeAppend,(unsigned char *)&wm_state_skip_pager, 1);// setting the window to top levelAtom wm_state_above = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeAppend,(unsigned char *)&wm_state_above, 1);XSetWindowAttributes attrs;attrs.override_redirect = True;XChangeWindowAttributes(display, window, CWOverrideRedirect, &attrs);
}

繪制

在窗口的Expose事件中,即繪制事件中。我們把創建黑色透明Pixmap數據拷貝到窗口顯示;同時讓當前窗口執接收鍵盤輸入。設置override_redirect后,如果沒有調XSetInputFocus,當前全屏窗口不會響應鍵盤輸入。實現邏輯如下

if (event.type == Expose && event.xexpose.count == 0) {//當前全屏窗口接收鍵盤輸入XSetInputFocus(display, window,RevertToParent, CurrentTime);XCopyArea(display,glbDarkPixmap,window,gc,0,0,width,height,0,0);}

鼠標事件

要實現通過鼠標選擇截圖區域,在鼠標按下時記錄鼠標位置,鼠標松開后,再次記下當前鼠標位置。兩個點形成的矩形區域就是截圖區域。

實現如下

if (event.type == ButtonPress) {if (event.xbutton.button == Button1) {mousePressed = true;mousePressedPoint.x = event.xbutton.x;mousePressedPoint.y = event.xbutton.y;}}if (event.type == ButtonRelease) {if (event.xbutton.button == Button1 && mousePressed) {XPoint mouseReleasePoint;mouseReleasePoint.x = event.xbutton.x;mouseReleasePoint.y = event.xbutton.y;mousePressed = false;//此時鼠標選中區域為(x1,y1,x2,y2)//此時我們可以從glbRootPixmap的(x1,y1)偏移處,獲取x2-x1寬度,y2-y1高度的像素數據,調用WriteBmp寫入文件。//省略XGetImage,WriteBmp代碼//完成后退出截圖程序break;}}

此外當鼠標左鍵按下,并移動鼠標到鼠標松開的過程,我們希望鼠標選中的區域能夠使用桌面的像素進行高亮顯示。實現如下

if (event.type == MotionNotify && mousePressed) {//鼠標按下時的坐標點不一定是左上角的坐標。找到選中區域左上角坐標。int x1 = mousePressedPoint.x < event.xmotion.x?mousePressedPoint.x:event.xmotion.x;int y1 = mousePressedPoint.y < event.xmotion.y?mousePressedPoint.y:event.xmotion.y;int selectionWidth = abs(mousePressedPoint.x - event.xmotion.x);int selectionHeight = abs(mousePressedPoint.y - event.xmotion.y);//先用黑色透明混合圖片覆蓋整個屏幕XCopyArea(display,glbDarkPixmap,window,gc,0,0,width,height,0,0);//再用桌面真實數據高亮選中區域XCopyArea(display,glbRootPixmap,window,gc,x1,y1,selectionWidth,selectionHeight,x1,y1);}

接下來給出一個完整的,可以編譯運行的代碼

#include <dirent.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/Xatom.h>Pixmap  glbDarkPixmap;//存儲50%透明黑色與桌面混合后的像素
Pixmap  glbRootPixmap;//存儲截圖工具運行時,桌面的像素值void CreateRootPixmap(Display *display,Window window,int width, int height) {int screen = DefaultScreen(display);glbRootPixmap = XCreatePixmap(display, window, width, height, DefaultDepth(display,screen));XImage *rootImage = XGetImage(display, RootWindow(display,screen),0,0,width,height,AllPlanes,ZPixmap);GC gc = XCreateGC(display,window,0,nullptr);XPutImage(display, glbRootPixmap, gc, rootImage, 0, 0, 0, 0, width, height);XFreeGC(display,gc);XDestroyImage(rootImage);
}void CreateDarkPixmap(Display *display,Window window, int width, int height) {int screen = DefaultScreen(display);glbDarkPixmap = XCreatePixmap(display,window, width,height, DefaultDepth(display,screen));//Pixmap blackBackground = XCreatePixmap(display, window,width,height, 32);XRenderPictFormat *format  = XRenderFindVisualFormat(display,DefaultVisual(display,screen));XRenderColor color = {0x0,0x0,0x0,0x8000};Picture blackBackgroundPicture = XRenderCreatePicture(display,blackBackground,XRenderFindStandardFormat(display, PictStandardARGB32),0,nullptr);XRenderFillRectangle(display,PictOpSrc,blackBackgroundPicture,&color,0,0,width,height);Picture picture = XRenderCreatePicture(display,glbRootPixmap, format,0, nullptr);Picture window_picture = XRenderCreatePicture(display,glbDarkPixmap,format,0,nullptr);XRenderComposite(display,PictOpOver,picture, None,window_picture,0,0,0,0,0,0,width,height);XRenderComposite(display,PictOpOver,blackBackgroundPicture, None,window_picture,0,0,0,0,0,0,width,height);XRenderFreePicture(display,blackBackgroundPicture);XRenderFreePicture(display,window_picture);XRenderFreePicture(display, picture);//XFreeGC(m_x11Window->display,gc);XFreePixmap(display,blackBackground);
}void ChangeWindowStyle(Display *display, Window window) {Atom wm_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);Atom wm_type_dock = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False);XChangeProperty(display, window, wm_type, XA_ATOM, 32, PropModeReplace,(unsigned char *)&wm_type_dock, 1);// Disable window decorate,we will create a window without title bar and borderAtom wm_state = XInternAtom(display, "_NET_WM_STATE", False);Atom wm_state_skip_taskbar = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False);Atom wm_state_skip_pager = XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeReplace,(unsigned char *)&wm_state_skip_taskbar, 1);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeAppend,(unsigned char *)&wm_state_skip_pager, 1);// setting the window to top levelAtom wm_state_above = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeAppend,(unsigned char *)&wm_state_above, 1);XSetWindowAttributes attrs;attrs.override_redirect = True;XChangeWindowAttributes(display, window, CWOverrideRedirect, &attrs);
}XPoint mousePressedPoint;
bool   mousePressed = false;int main() {Display *display;int screen;/* 打開與X服務器的連接 */display = XOpenDisplay(NULL);if (display == NULL) {fprintf(stderr, "無法打開X顯示器\n");exit(1);}screen = DefaultScreen(display);int width = DisplayWidth(display,screen);int height = DisplayHeight(display,screen);Window window = XCreateSimpleWindow(display, RootWindow(display,screen),0,0,width,height,0,BlackPixel(display, screen),WhitePixel(display, screen));//改變窗口樣式,使其能夠全屏顯示。ChangeWindowStyle(display,window);//將桌面像素保存到rootPixmapCreateRootPixmap(display,window,width,height);//將桌面與黑色半透明混合存儲到glbDarkPixmapCreateDarkPixmap(display,window,width,height);XSelectInput(display,window,ExposureMask|KeyPressMask|ButtonPressMask | ButtonReleaseMask | PointerMotionMask);GC gc = XCreateGC(display,window,0,nullptr);XMapWindow(display,window);XEvent  event;while (1) {XNextEvent(display,&event);if (event.type == Expose && event.xexpose.count == 0) {//當前全屏窗口接收鍵盤輸入XSetInputFocus(display, window,RevertToParent, CurrentTime);XCopyArea(display,glbDarkPixmap,window,gc,0,0,width,height,0,0);}if (event.type == KeyPress) {KeySym keysym = XLookupKeysym(&event.xkey,0);if (keysym == XK_Escape) {break;}}if (event.type == ButtonPress) {if (event.xbutton.button == Button1) {mousePressed = true;mousePressedPoint.x = event.xbutton.x;mousePressedPoint.y = event.xbutton.y;}}if (event.type == ButtonRelease) {if (event.xbutton.button == Button1 && mousePressed) {XPoint mouseReleasePoint;mouseReleasePoint.x = event.xbutton.x;mouseReleasePoint.y = event.xbutton.y;mousePressed = false;//此時鼠標選中區域為(x1,y1,x2,y2)//此時我們可以從glbRootPixmap的(x1,y1)偏移處,獲取x2-x1寬度,y2-y1高度的像素數據,調用WriteBmp寫入文件。//省略XGetImage,WriteBmp代碼//完成后退出截圖程序break;}}if (event.type == MotionNotify && mousePressed) {//鼠標按下時的坐標點不一定是左上角的坐標。找到選中區域左上角坐標。int x1 = mousePressedPoint.x < event.xmotion.x?mousePressedPoint.x:event.xmotion.x;int y1 = mousePressedPoint.y < event.xmotion.y?mousePressedPoint.y:event.xmotion.y;int selectionWidth = abs(mousePressedPoint.x - event.xmotion.x);int selectionHeight = abs(mousePressedPoint.y - event.xmotion.y);//先用黑色透明混合圖片覆蓋整個屏幕XCopyArea(display,glbDarkPixmap,window,gc,0,0,width,height,0,0);//再用桌面真實數據高亮選中區域XCopyArea(display,glbRootPixmap,window,gc,x1,y1,selectionWidth,selectionHeight,x1,y1);}}XFreeGC(display,gc);XDestroyWindow(display,window);XCloseDisplay(display);return 0;
}

編譯運行以上程序。一個效果如下

在這里插入圖片描述

如果嘗試編譯運行以上代碼,當我們使用鼠標拖動選擇截圖區域時,出現了很嚴重的“閃爍”現象。在我們開發第一個版本的截屏程序時,確實出現了很嚴重的閃爍問題,問了通義大語言模型,給出的解決方案就是使用高級的Qt或是gtk、cairo圖形庫。但是前面已經做了那么的事,不能就這樣放棄了。

3.解決鼠標拖動閃爍問題

以上拖動鼠標時出現閃爍現象是因為當鼠標按下后,拖動鼠標的過程中,我們先用黑色混合圖片重新覆蓋整個窗口,再用原有桌面圖片覆蓋選中區域。仔細分析可以發現每次鼠標拖動過程其變化的區域都是一小部分,但我們需要把整個屏幕區域全部都重繪,還要再繪制選中區域,閃爍現象主要是我們做了大量不必要的重繪工作。如果我們能計算出鼠標拖動變化前后,哪些區域發生了改變,針對這些變化的區域繪制黑色混合圖片或是桌面原有圖片,這樣我們繪制的就是極小一部分區域。問了一些大語言模型,也沒有給出特別好建議,當時腦子閃現是否可以利用集合差集運算來解決這個問題。集合的差集表述為

A ? B = { x ∣ x ∈ A a n d x ? B } A-B = \{{x|x\in \ A \ and\ x \notin \ B}\} A?B={xx?A?and?x/?B}

首先我們先看幾個鼠標變動的示意圖,再進行分析

情形1

在這里插入圖片描述

上圖中鼠標原本選中的區域是Rect1,鼠標向下角拖動,形成的矩形區域是Rect2,從上面可以知道選中的區域在向下和向右都擴大了,這時我們應該使用截圖程序運行時桌面真實像素數據填充這兩塊區域。對于上述兩個區形Rect1-Rect2為空集,Rect2-Rect1為下邊和右邊兩個區域的并集。

情形2

在這里插入圖片描述

以上操作鼠標通過移動縮小了選中區域。此時Rect1-Rect2為下側綠色虛線與下側紅色實線形成的矩形區域以及右側綠色虛線與右側紅色實線形成矩形,兩個矩形的并集,對于這個結果我們需要使用黑色混合圖片進行重新覆蓋。而Rect2-Rect為空集

情形3

在這里插入圖片描述

此情形下選中區域在高度上縮小了,在寬度上擴大了。根據集合差集定義Rect1-Rect2就是下側綠色線與下側紅色線形成的區域,這是我們縮小高度的區域,應該使用黑色混合圖片覆蓋。Rect2-Rect1為右側綠色Rect2的小塊,這是選中區域寬度擴大區域,應該使用桌面真實像素覆蓋。

情形4

在這里插入圖片描述

在此情形下Rect1-Rect2為右側紅線和右側綠色形成的矩形區域,需要使用黑色混合圖片覆蓋。Rect2-Rect1為下側紅色線與下側綠色之間的矩形區域,應該使用桌面真實像素覆蓋。

有了以上幾種情形的分析我們可以得到,在進行截圖時如果原有選中的區域為Rect1,鼠標拖動后的區域為Rect2。那么使用Rect1-Rect2得到的矩形區域就是我們需要使用黑色混合圖片覆蓋的區域,而Rect2-Rect1就是我們需要使用桌面真實像素覆蓋的區域。以上只是對鼠標在選中區域右下角方向操作的分析。在其它幾個方向上的操作完全可以利用集合差操作實現。這里就不再分析。

以下是我們借助通義千問生成的矩形的并、交、差運算。實現如下

typedef struct _UIRect{int x;int y;int width;int height;
} UIRect; /*** The following code is generated by Tongyi Qianwen Larger Language Model*/int get_right(const UIRect *rect) {return rect->x + rect->width;
}int get_bottom(const UIRect *rect) {return rect->y + rect->height;
}UIRect rect_union(const UIRect *rect1, const UIRect *rect2) {UIRect result;// calculate the TopLeft of Union Rectangleresult.x = (rect1->x < rect2->x) ? rect1->x : rect2->x;result.y = (rect1->y < rect2->y) ? rect1->y : rect2->y;// calculate the BottomRight of Union Rectangleint right = (get_right(rect1) > get_right(rect2)) ? get_right(rect1) : get_right(rect2);int bottom = (get_bottom(rect1) > get_bottom(rect2)) ? get_bottom(rect1) : get_bottom(rect2);result.width = right - result.x;result.height = bottom - result.y;return result;
}UIRect rect_intersection(const UIRect *rect1, const UIRect *rect2) {UIRect result;// calculate the TopLeft of Intersection Rectangleresult.x = (rect1->x > rect2->x) ? rect1->x : rect2->x;result.y = (rect1->y > rect2->y) ? rect1->y : rect2->y;//calculate BottomRight of Intersection Rectangleint right = (get_right(rect1) < get_right(rect2)) ? get_right(rect1) : get_right(rect2);int bottom = (get_bottom(rect1) < get_bottom(rect2)) ? get_bottom(rect1) : get_bottom(rect2);// if the Intersection is Empty,return empty Rectangle.if (result.x >= right || result.y >= bottom) {result.x = result.y = result.width = result.height = 0;return result;}result.width = right - result.x;result.height = bottom - result.y;return result;
}void rect_difference(const UIRect *rect1, const UIRect *rect2, UIRect *result, int *count) {*count = 0;//check if the intersection of two rectangles is empty,if the intersection is empty,return rect1.UIRect intersection = rect_intersection(rect1, rect2);if (intersection.width == 0 || intersection.height == 0) {result[*count] = *rect1;(*count)++;return;}//if rect1 is a subset of rect2,the difference set should be empty.if (intersection.x <= rect1->x &&intersection.y <= rect1->y &&get_right(&intersection) >= get_right(rect1) &&get_bottom(&intersection) >= get_bottom(rect1)) {return;}// The difference set may generate up to four rectangles// Topif (rect1->y < intersection.y) {result[*count].x = rect1->x;result[*count].y = rect1->y;result[*count].width = rect1->width;result[*count].height = intersection.y - rect1->y;(*count)++;}// Bottomif (get_bottom(rect1) > get_bottom(&intersection)) {result[*count].x = rect1->x;result[*count].y = get_bottom(&intersection);result[*count].width = rect1->width;result[*count].height = get_bottom(rect1) - get_bottom(&intersection);(*count)++;}// Leftif (rect1->x < intersection.x) {result[*count].x = rect1->x;result[*count].y = intersection.y;result[*count].width = intersection.x - rect1->x;result[*count].height = intersection.height;(*count)++;}// Rightif (get_right(rect1) > get_right(&intersection)) {result[*count].x = get_right(&intersection);result[*count].y = intersection.y;result[*count].width = get_right(rect1) - get_right(&intersection);result[*count].height = intersection.height;(*count)++;}
}

有了以上對于矩形的交并差運算。接下來就是處理當鼠標移動時,我們通過新老兩個矩形區域的差集來確實哪些區域應該填充桌面真實圖片,哪些區域應該填充黑色混合圖片。鼠標移動修改后代碼如下

if (event.type == MotionNotify && mousePressed) {//鼠標按下時的坐標點不一定是左上角的坐標。找到選中區域左上角坐標。int x1 = mousePressedPoint.x < event.xmotion.x?mousePressedPoint.x:event.xmotion.x;int y1 = mousePressedPoint.y < event.xmotion.y?mousePressedPoint.y:event.xmotion.y;int selectionWidth = abs(mousePressedPoint.x - event.xmotion.x);int selectionHeight = abs(mousePressedPoint.y - event.xmotion.y);UIRect newRect = {x1,y1,selectionWidth,selectionHeight};if (lastSelectionRect.x == -1) {lastSelectionRect = newRect;XCopyArea(display,glbRootPixmap,window,gc,x1,y1,selectionWidth,selectionHeight,x1,y1);continue;}UIRect  diffRectArray[4] = {0};int count = 0;rect_difference(&newRect,&lastSelectionRect,diffRectArray,&count);//Rect2 - Rect1結果矩形使用桌面圖像覆蓋for (auto &diffRect : diffRectArray) {if (diffRect.width<=0||diffRect.height<=0) {continue;}XCopyArea(display,glbRootPixmap,window,gc,diffRect.x,diffRect.y,diffRect.width,diffRect.height,diffRect.x,diffRect.y);}memset(diffRectArray,0,sizeof(diffRectArray));rect_difference(&lastSelectionRect,&newRect,diffRectArray,&count);for(auto & diffRect : diffRectArray){ //Rect1 - Rect2 使用黑色混合圖片覆蓋if(diffRect.width <= 0 || diffRect.height <= 0){continue;}XCopyArea(display, glbDarkPixmap, window,gc, diffRect.x, diffRect.y,diffRect.width, diffRect.height, diffRect.x, diffRect.y);}lastSelectionRect = newRect;}

以上程序完整代碼如下:

#include <cstring>
#include <dirent.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/extensions/Xrender.h>
#include <X11/Xatom.h>Pixmap  glbDarkPixmap;//存儲50%透明黑色與桌面混合后的像素
Pixmap  glbRootPixmap;//存儲截圖工具運行時,桌面的像素值void CreateRootPixmap(Display *display,Window window,int width, int height) {int screen = DefaultScreen(display);glbRootPixmap = XCreatePixmap(display, window, width, height, DefaultDepth(display,screen));XImage *rootImage = XGetImage(display, RootWindow(display,screen),0,0,width,height,AllPlanes,ZPixmap);GC gc = XCreateGC(display,window,0,nullptr);XPutImage(display, glbRootPixmap, gc, rootImage, 0, 0, 0, 0, width, height);XFreeGC(display,gc);XDestroyImage(rootImage);
}void CreateDarkPixmap(Display *display,Window window, int width, int height) {int screen = DefaultScreen(display);glbDarkPixmap = XCreatePixmap(display,window, width,height, DefaultDepth(display,screen));//Pixmap blackBackground = XCreatePixmap(display, window,width,height, 32);XRenderPictFormat *format  = XRenderFindVisualFormat(display,DefaultVisual(display,screen));XRenderColor color = {0x0,0x0,0x0,0x8000};Picture blackBackgroundPicture = XRenderCreatePicture(display,blackBackground,XRenderFindStandardFormat(display, PictStandardARGB32),0,nullptr);XRenderFillRectangle(display,PictOpSrc,blackBackgroundPicture,&color,0,0,width,height);Picture picture = XRenderCreatePicture(display,glbRootPixmap, format,0, nullptr);Picture window_picture = XRenderCreatePicture(display,glbDarkPixmap,format,0,nullptr);XRenderComposite(display,PictOpOver,picture, None,window_picture,0,0,0,0,0,0,width,height);XRenderComposite(display,PictOpOver,blackBackgroundPicture, None,window_picture,0,0,0,0,0,0,width,height);XRenderFreePicture(display,blackBackgroundPicture);XRenderFreePicture(display,window_picture);XRenderFreePicture(display, picture);//XFreeGC(m_x11Window->display,gc);XFreePixmap(display,blackBackground);
}void ChangeWindowStyle(Display *display, Window window) {Atom wm_type = XInternAtom(display, "_NET_WM_WINDOW_TYPE", False);Atom wm_type_dock = XInternAtom(display, "_NET_WM_WINDOW_TYPE_DOCK", False);XChangeProperty(display, window, wm_type, XA_ATOM, 32, PropModeReplace,(unsigned char *)&wm_type_dock, 1);// Disable window decorate,we will create a window without title bar and borderAtom wm_state = XInternAtom(display, "_NET_WM_STATE", False);Atom wm_state_skip_taskbar = XInternAtom(display, "_NET_WM_STATE_SKIP_TASKBAR", False);Atom wm_state_skip_pager = XInternAtom(display, "_NET_WM_STATE_SKIP_PAGER", False);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeReplace,(unsigned char *)&wm_state_skip_taskbar, 1);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeAppend,(unsigned char *)&wm_state_skip_pager, 1);// setting the window to top levelAtom wm_state_above = XInternAtom(display, "_NET_WM_STATE_ABOVE", False);XChangeProperty(display, window, wm_state, XA_ATOM, 32, PropModeAppend,(unsigned char *)&wm_state_above, 1);XSetWindowAttributes attrs;attrs.override_redirect = True;XChangeWindowAttributes(display, window, CWOverrideRedirect, &attrs);
}typedef struct _UIRect{int x;int y;int width;int height;
} UIRect;/*** The following code is generated by Tongyi Qianwen Larger Language Model*/int get_right(const UIRect *rect) {return rect->x + rect->width;
}int get_bottom(const UIRect *rect) {return rect->y + rect->height;
}UIRect rect_union(const UIRect *rect1, const UIRect *rect2) {UIRect result;// calculate the TopLeft of Union Rectangleresult.x = (rect1->x < rect2->x) ? rect1->x : rect2->x;result.y = (rect1->y < rect2->y) ? rect1->y : rect2->y;// calculate the BottomRight of Union Rectangleint right = (get_right(rect1) > get_right(rect2)) ? get_right(rect1) : get_right(rect2);int bottom = (get_bottom(rect1) > get_bottom(rect2)) ? get_bottom(rect1) : get_bottom(rect2);result.width = right - result.x;result.height = bottom - result.y;return result;
}UIRect rect_intersection(const UIRect *rect1, const UIRect *rect2) {UIRect result;// calculate the TopLeft of Intersection Rectangleresult.x = (rect1->x > rect2->x) ? rect1->x : rect2->x;result.y = (rect1->y > rect2->y) ? rect1->y : rect2->y;//calculate BottomRight of Intersection Rectangleint right = (get_right(rect1) < get_right(rect2)) ? get_right(rect1) : get_right(rect2);int bottom = (get_bottom(rect1) < get_bottom(rect2)) ? get_bottom(rect1) : get_bottom(rect2);// if the Intersection is Empty,return empty Rectangle.if (result.x >= right || result.y >= bottom) {result.x = result.y = result.width = result.height = 0;return result;}result.width = right - result.x;result.height = bottom - result.y;return result;
}void rect_difference(const UIRect *rect1, const UIRect *rect2, UIRect *result, int *count) {*count = 0;//check if the intersection of two rectangles is empty,if the intersection is empty,return rect1.UIRect intersection = rect_intersection(rect1, rect2);if (intersection.width == 0 || intersection.height == 0) {result[*count] = *rect1;(*count)++;return;}//if rect1 is a subset of rect2,the difference set should be empty.if (intersection.x <= rect1->x &&intersection.y <= rect1->y &&get_right(&intersection) >= get_right(rect1) &&get_bottom(&intersection) >= get_bottom(rect1)) {return;}// The difference set may generate up to four rectangles// Topif (rect1->y < intersection.y) {result[*count].x = rect1->x;result[*count].y = rect1->y;result[*count].width = rect1->width;result[*count].height = intersection.y - rect1->y;(*count)++;}// Bottomif (get_bottom(rect1) > get_bottom(&intersection)) {result[*count].x = rect1->x;result[*count].y = get_bottom(&intersection);result[*count].width = rect1->width;result[*count].height = get_bottom(rect1) - get_bottom(&intersection);(*count)++;}// Leftif (rect1->x < intersection.x) {result[*count].x = rect1->x;result[*count].y = intersection.y;result[*count].width = intersection.x - rect1->x;result[*count].height = intersection.height;(*count)++;}// Rightif (get_right(rect1) > get_right(&intersection)) {result[*count].x = get_right(&intersection);result[*count].y = intersection.y;result[*count].width = get_right(rect1) - get_right(&intersection);result[*count].height = intersection.height;(*count)++;}
}XPoint mousePressedPoint;
bool   mousePressed = false;
UIRect  lastSelectionRect = {-1,-1,0,0};int main() {Display *display;int screen;/* 打開與X服務器的連接 */display = XOpenDisplay(NULL);if (display == NULL) {fprintf(stderr, "無法打開X顯示器\n");exit(1);}screen = DefaultScreen(display);int width = DisplayWidth(display,screen);int height = DisplayHeight(display,screen);Window window = XCreateSimpleWindow(display, RootWindow(display,screen),0,0,width,height,0,BlackPixel(display, screen),WhitePixel(display, screen));//改變窗口樣式,使其能夠全屏顯示。ChangeWindowStyle(display,window);//將桌面像素保存到rootPixmapCreateRootPixmap(display,window,width,height);//將桌面與黑色半透明混合存儲到glbDarkPixmapCreateDarkPixmap(display,window,width,height);XSelectInput(display,window,ExposureMask|KeyPressMask|ButtonPressMask | ButtonReleaseMask | PointerMotionMask);GC gc = XCreateGC(display,window,0,nullptr);XMapWindow(display,window);XEvent  event;while (1) {XNextEvent(display,&event);if (event.type == Expose && event.xexpose.count == 0) {//當前全屏窗口接收鍵盤輸入XSetInputFocus(display, window,RevertToParent, CurrentTime);XCopyArea(display,glbDarkPixmap,window,gc,0,0,width,height,0,0);}if (event.type == KeyPress) {KeySym keysym = XLookupKeysym(&event.xkey,0);if (keysym == XK_Escape) {break;}}if (event.type == ButtonPress) {if (event.xbutton.button == Button1) {mousePressed = true;mousePressedPoint.x = event.xbutton.x;mousePressedPoint.y = event.xbutton.y;}}if (event.type == ButtonRelease) {if (event.xbutton.button == Button1 && mousePressed) {XPoint mouseReleasePoint;mouseReleasePoint.x = event.xbutton.x;mouseReleasePoint.y = event.xbutton.y;mousePressed = false;//此時鼠標選中區域為(x1,y1,x2,y2)//此時我們可以從glbRootPixmap的(x1,y1)偏移處,獲取x2-x1寬度,y2-y1高度的像素數據,調用WriteBmp寫入文件。//省略XGetImage,WriteBmp代碼//完成后退出截圖程序break;}}if (event.type == MotionNotify && mousePressed) {//鼠標按下時的坐標點不一定是左上角的坐標。找到選中區域左上角坐標。int x1 = mousePressedPoint.x < event.xmotion.x?mousePressedPoint.x:event.xmotion.x;int y1 = mousePressedPoint.y < event.xmotion.y?mousePressedPoint.y:event.xmotion.y;int selectionWidth = abs(mousePressedPoint.x - event.xmotion.x);int selectionHeight = abs(mousePressedPoint.y - event.xmotion.y);UIRect newRect = {x1,y1,selectionWidth,selectionHeight};if (lastSelectionRect.x == -1) {lastSelectionRect = newRect;XCopyArea(display,glbRootPixmap,window,gc,x1,y1,selectionWidth,selectionHeight,x1,y1);continue;}UIRect  diffRectArray[4] = {0};int count = 0;rect_difference(&newRect,&lastSelectionRect,diffRectArray,&count);//Rect2 - Rect1結果矩形使用桌面圖像覆蓋for (auto &diffRect : diffRectArray) {if (diffRect.width<=0||diffRect.height<=0) {continue;}XCopyArea(display,glbRootPixmap,window,gc,diffRect.x,diffRect.y,diffRect.width,diffRect.height,diffRect.x,diffRect.y);}memset(diffRectArray,0,sizeof(diffRectArray));rect_difference(&lastSelectionRect,&newRect,diffRectArray,&count);for(auto & diffRect : diffRectArray){if(diffRect.width <= 0 || diffRect.height <= 0){continue;}XCopyArea(display, glbDarkPixmap, window,gc, diffRect.x, diffRect.y,diffRect.width, diffRect.height, diffRect.x, diffRect.y);}lastSelectionRect = newRect;}}XFreeGC(display,gc);XDestroyWindow(display,window);XCloseDisplay(display);return 0;
}

編譯以上程序,運行后。當我們再次使用鼠標拖動選中截圖區域時,不會再有閃爍現象了。拖動操作非常絲滑。

4.總結

以上我們基于xlib實現了linux操作系統下一個小小的屏幕截圖工具。以上代碼可以在國產操作系統上編譯運行,前提是國產操作系統的GUI采用X11窗口系統,如果窗口系統使用的wayland,以上截圖功能無法使用。此外我們可以stb_image、libpng、libjpeg等開源庫將以上截圖結果保存為bmp、jpg、png等圖片格式。此外我們為程序添加選擇圖片保存名稱和路徑的功能。我在github開源了一個跨平臺的屏幕截圖工具,該工具可運行在windows、linux以及各國產化操作系統下。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/906556.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/906556.shtml
英文地址,請注明出處:http://en.pswp.cn/news/906556.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

藍橋杯分享經驗

系列文章目錄 提示&#xff1a;小白先看系列 第一章 藍橋杯的錢白給嗎 文章目錄 系列文章目錄前言一、自我介紹二、經驗講解:1.基礎知識2.進階知識3.個人觀點 三、總結四、后續 前言 第十六屆藍橋杯已經省賽已經結束了&#xff0c;相信很多小伙伴也已經得到自己的成績了。接下…

XC3588H搭載國產麒麟系統可用于政務/社保一體機嗎?

答案是肯定的。 向成電子XC3588H搭載的國產銀河麒麟系統和國產星光麒麟系統已完成適配&#xff0c;適用于政務服務、社保服務一體機的所有外設&#xff0c;運行穩定流暢。 在數字化政務快速發展的今天&#xff0c;政務服務終端的穩定性、安全性與高效性成為提升群眾辦事體驗的關…

如何排查服務器 CPU 溫度過高的問題并解決?

服務器CPU溫度過高是一個常見的問題&#xff0c;可能導致服務器性能下降、系統穩定性問題甚至硬件損壞。有效排查和解決服務器CPU溫度過高的問題對于確保服務器正常運行和延長硬件壽命至關重要。本文將介紹如何排查服務器CPU溫度過高的問題&#xff0c;并提供解決方法&#xff…

物聯網、云計算技術加持,助推樓宇自控系統實現智能高效管理

在建筑智能化發展的進程中&#xff0c;樓宇自控系統作為實現建筑高效管理的核心載體&#xff0c;正面臨著數據海量復雜、設備協同困難、管理響應遲緩等挑戰。而物聯網與云計算技術的深度融合&#xff0c;為樓宇自控系統的升級提供了全新的解決方案&#xff0c;賦予其智能感知、…

uni-app使用大集

1、手動修改頁面標題 uni.setNavigationBarTitle({title: 修改標題 }); 2、單選 不止有 radio-group&#xff0c;還有 uni-data-checkbox 數據選擇器 <!-- html部分 --> <uni-data-checkbox v-model"sex" :localdata"checkboxList"></u…

(6)python爬蟲--selenium

文章目錄 前言一、初識selenium二、安裝selenium2.1 查看chrome版本并禁止chrome自動更新2.1.1 查看chrome版本2.1.2 禁止chrome更新自動更新 2.2 安裝對應版本的驅動程序2.3安裝selenium包 三、selenium關于瀏覽器的使用3.1 創建瀏覽器、設置、打開3.2 打開/關閉網頁及瀏覽器3…

基于OpenCV的人臉微笑檢測實現

文章目錄 引言一、技術原理二、代碼實現2.1 關鍵代碼解析2.1.1 模型加載2.1.2 圖像翻轉2.1.3 人臉檢測 微笑檢測 2.2 顯示效果 三、參數調優建議四、總結 引言 在計算機視覺領域&#xff0c;人臉檢測和表情識別一直是熱門的研究方向。今天我將分享一個使用Python和OpenCV實現…

Java 大視界 -- 基于 Java 的大數據分布式存儲在視頻會議系統海量視頻數據存儲與回放中的應用(263)

&#x1f496;親愛的朋友們&#xff0c;熱烈歡迎來到 青云交的博客&#xff01;能與諸位在此相逢&#xff0c;我倍感榮幸。在這飛速更迭的時代&#xff0c;我們都渴望一方心靈凈土&#xff0c;而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識&#xff0c;也…

Kotlin 極簡小抄 P9 - 數組(數組的創建、數組元素的訪問與修改、數組遍歷、數組操作、多維數組、數組與可變參數)

Kotlin 概述 Kotlin 由 JetBrains 開發&#xff0c;是一種在 JVM&#xff08;Java 虛擬機&#xff09;上運行的靜態類型編程語言 Kotlin 旨在提高開發者的編碼效率和安全性&#xff0c;同時保持與 Java 的高度互操作性 Kotlin 是 Android 應用開發的首選語言&#xff0c;也可…

gitlab+portainer 實現Ruoyi Vue前端CI/CD

1. 場景 最近整了一個Ruoyi Vue 項目&#xff0c;需要實現CICD&#xff0c;經過一番坎坷&#xff0c;最終達成&#xff0c;現將技術要點和踩坑呈現。 具體操作流程和后端大同小異&#xff0c;后端操作參考連接如下&#xff1a; https://blog.csdn.net/leinminna/article/detai…

RNN神經網絡

RNN神經網絡 1-核心知識 1-解釋RNN神經網絡2-RNN和傳統的神經網絡有什么區別&#xff1f;3-RNN和LSTM有什么區別&#xff1f;4-transformer的歸一化有哪幾種實現方式 2-知識問答 1-解釋RNN神經網絡 Why&#xff1a;與我何干&#xff1f; 在我們的生活中&#xff0c;很多事情…

javaweb-html

1.交互流程&#xff1a; 瀏覽器向服務器發送http請求&#xff0c;服務器對瀏覽器進行回應&#xff0c;并發送字符串&#xff0c;瀏覽器能對這些字符串&#xff08;html代碼&#xff09;進行解釋&#xff1b; 三大web語言&#xff1a;&#xff08;1&#xff09;html&#xff1a…

從混亂到高效:我們是如何重構 iOS 上架流程的(含 Appuploader實踐)

從混亂到高效&#xff1a;我們是如何重構 iOS 上架流程的 在開發團隊中&#xff0c;有一類看不見卻至關重要的問題&#xff1a;環境依賴。 特別是 iOS App 的發布流程&#xff0c;往往牢牢綁死在一臺特定的 Mac 上。每次需要發版本&#xff0c;都要找到“那臺 Mac”&#xff…

FPGA:CLB資源以及Verilog編碼面積優化技巧

本文將先介紹Kintex-7系列器件的CLB&#xff08;可配置邏輯塊&#xff09;資源&#xff0c;然后分享在Verilog編碼時節省CLB資源的技巧。以下內容基于Kintex-7系列的架構特點&#xff0c;并結合實際設計經驗進行闡述。 一、Kintex-7系列器件的CLB資源介紹 Kintex-7系列是Xilin…

在linux里上傳本地項目到github中

首先先安裝git&#xff0c;安裝完git后&#xff0c;輸入如下操作指令&#xff1a; 輸入自己的用戶名和郵箱&#xff08;為注冊GITHUB賬號時的用戶名和郵箱&#xff09;&#xff1a; git config --global user.name "111"git config --global user.email "121…

鴻蒙Flutter實戰:25-混合開發詳解-5-跳轉Flutter頁面

概述 在上一章中&#xff0c;我們介紹了如何初始化 Flutter 引擎&#xff0c;本文重點介紹如何添加并跳轉至 Flutter 頁面。 跳轉原理 跳轉原理如下&#xff1a; 本質上是從一個原生頁面A 跳轉至另一個原生頁面 B&#xff0c;不過區別在于&#xff0c;頁面 B是一個頁面容器…

c語言 寫一個五子棋

c語言 IsWin判贏 display 畫 10 x 10 的棋盤 判斷落子的坐標是否已有棋子 判斷落子坐標范圍是否超出范圍 // 五子棋 #include <stdio.h> #include <stdlib.h>// 畫棋盤 10 x 10的棋盤&#xff0c;len為行數 void display(char map[][10], int len) {system(&q…

格雷希爾快速封堵接頭,解決新能源汽車的氣密性檢測和三電系統的綜合測試

我國的新能源汽車已經遙遙領先&#xff0c;讓其他國家望塵莫及。格雷希爾GripSeal&#xff0c;為新能源汽車制造業提供快速可靠的密封連接器&#xff0c;讓測試速度加倍。以好抓取、易密封為設計理念&#xff0c;實現一秒連接&#xff0c;瞬時密封的高效性能。通過持續的產品設…

人工智能全景解析:從技術原理到未來趨勢的深度探索

人工智能(AI)作為21世紀最具變革性的技術之一&#xff0c;正以前所未有的速度重塑著人類社會。從智能手機中的語音助手到工廠里的智能機器人&#xff0c;從醫療診斷系統到金融風控模型&#xff0c;AI技術已滲透到我們生活和工作的方方面面。本文將全面解析人工智能的發展歷程、…

[密碼學實戰]使用C語言實現TCP服務端(二十九)

[密碼學實戰]使用C語言實現TCP服務端(二十九) 引言 TCP(傳輸控制協議)是互聯網通信中最核心的協議之一,它提供可靠的、面向連接的數據傳輸服務。通過C語言的標準Socket API,開發者可以靈活地實現TCP客戶端和服務端程序。本文將詳細講解TCP通信的原理,并提供完整的代碼…