You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何不重绘整个Surface擦除图形,实现Win11式动态截图区域选择

解决Cairo动态截图区域选择的闪烁与绘制异常问题

当前实现的功能是在屏幕上绘制半透明遮罩矩形,并从遮罩中抠出不透明的选择区域。目前的做法是循环重绘整个Surface和半透明矩形,但低配电脑上会出现闪烁;如果停止全量重绘,闪烁消失,但会出现绘制异常(由代码中注释的行导致)。需要实现不重绘整个Surface,仅擦除半透明矩形,完成类似Windows 11的动态截图区域选择功能。

原实现代码:

void OcctGtkViewer::buttonclick(int *x, int *y, int *width, int *height)
{
  cairo_surface_t *surface = cairo_xlib_surface_create(xdisplay, xroot,    DefaultVisual(xdisplay, scr), DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
  cairo_t *cr = cairo_create(surface);

  const char *backgroundImagePath = "/tmp/test.png";
  cairo_surface_write_to_png(
   surface,
   backgroundImagePath);

  cairo_surface_t *surfaceTmp =    cairo_image_surface_create_from_png(backgroundImagePath);

 cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
 cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
 cairo_fill(cr);

 cairo_surface_t *surfaceTmpp =  cairo_image_surface_create_from_png(backgroundImagePath);

 printf("select an area\n");
 while (1)
 {
   event.type == MotionNotify && isPressed == true)
   {
     int tmp_x = xevent.xmotion.x_root;
     int tmp_y = xevent.xmotion.y_root;

     cairo_set_source_surface(cr, surfaceTmp, 1, 1);
     // cairo_paint(cr);

     cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD); // The default is  CAIRO_FILL_RULE_WINDING.

     // cairo_set_source_rgba(cr, 0, 0, 0, 0.2);
     // cairo_rectangle(cr, 0, 0, DisplayWidth(xdisplay, scr),  DisplayHeight(xdisplay, scr));

     cairo_rectangle(cr, init_x, init_y, tmp_x - init_x, tmp_y - init_y); // set rectangle
     cairo_fill(cr);
   }
 }
}

核心解决思路

要兼顾无闪烁和绘制正确,关键是只更新上一次选择区域与当前选择区域的差异部分,而非每次重绘全屏。具体步骤如下:

  • 只捕获一次初始屏幕背景,绘制一次全屏半透明遮罩并保存状态,后续不再重复绘制全屏内容
  • 记录上一次的选择区域坐标,每次鼠标移动时,先恢复上一次区域的背景+遮罩状态,再绘制新的选择区域
  • 利用CAIRO_FILL_RULE_EVEN_ODD实现遮罩抠图,但仅针对局部区域操作

修改后的完整代码

void OcctGtkViewer::buttonclick(int *x, int *y, int *width, int *height)
{
    // 创建关联屏幕的Surface
    cairo_surface_t *surface = cairo_xlib_surface_create(xdisplay, xroot, 
        DefaultVisual(xdisplay, scr), DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
    cairo_t *cr = cairo_create(surface);

    const char *backgroundImagePath = "/tmp/test.png";
    // 捕获初始屏幕背景并保存
    cairo_surface_write_to_png(surface, backgroundImagePath);
    cairo_surface_t *bgSurface = cairo_image_surface_create_from_png(backgroundImagePath);
    
    // 创建临时Surface保存全屏半透明遮罩的状态,只绘制一次
    cairo_surface_t *maskSurface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 
        DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
    cairo_t *maskCr = cairo_create(maskSurface);
    cairo_set_source_rgba(maskCr, 0, 0, 0, 0.2);
    cairo_rectangle(maskCr, 0, 0, DisplayWidth(xdisplay, scr), DisplayHeight(xdisplay, scr));
    cairo_fill(maskCr);
    cairo_destroy(maskCr);

    // 记录上一次选择区域的坐标,用于局部恢复
    int last_x = 0, last_y = 0, last_w = 0, last_h = 0;
    bool hasLastRegion = false;

    printf("select an area\n");
    while (1)
    {
        // 修正原代码的语法错误,处理鼠标移动事件
        if (event.type == MotionNotify && isPressed == true)
        {
            int tmp_x = xevent.xmotion.x_root;
            int tmp_y = xevent.xmotion.y_root;
            int curr_w = tmp_x - init_x;
            int curr_h = tmp_y - init_y;

            // 先恢复上一次选择区域的原始状态(背景+遮罩)
            if (hasLastRegion)
            {
                // 绘制背景到上一次区域
                cairo_set_source_surface(cr, bgSurface, 0, 0);
                cairo_rectangle(cr, last_x, last_y, last_w, last_h);
                cairo_fill(cr);
                // 绘制遮罩到上一次区域
                cairo_set_source_surface(cr, maskSurface, 0, 0);
                cairo_rectangle(cr, last_x, last_y, last_w, last_h);
                cairo_fill(cr);
            }

            // 使用奇偶填充规则,绘制当前选择的抠图区域
            cairo_set_fill_rule(cr, CAIRO_FILL_RULE_EVEN_ODD);
            // 先绘制当前区域的遮罩
            cairo_set_source_surface(cr, maskSurface, 0, 0);
            cairo_rectangle(cr, init_x, init_y, curr_w, curr_h);
            // 叠加当前区域的背景,实现抠出透明效果
            cairo_set_source_surface(cr, bgSurface, 0, 0);
            cairo_rectangle(cr, init_x, init_y, curr_w, curr_h);
            cairo_fill(cr);

            // 更新上一次区域的记录
            last_x = init_x;
            last_y = init_y;
            last_w = curr_w;
            last_h = curr_h;
            hasLastRegion = true;

            // 局部刷新,避免全量屏幕刷新
            XSync(xdisplay, False);
        }
        // 补充鼠标松开的退出逻辑
        else if (event.type == ButtonRelease && isPressed == true)
        {
            isPressed = false;
            break;
        }
        // 等待X11事件
        XNextEvent(xdisplay, &event);
    }

    // 清理资源,删除临时文件
    cairo_surface_destroy(bgSurface);
    cairo_surface_destroy(maskSurface);
    cairo_destroy(cr);
    cairo_surface_destroy(surface);
    remove(backgroundImagePath);
}

关键改进点

  1. 消除全量重绘:每次仅更新上一次和当前选择的区域,大幅减少绘制操作,解决低配电脑闪烁问题
  2. 复用遮罩状态:仅绘制一次全屏遮罩并保存,后续直接复用,避免重复绘制全屏
  3. 修复语法问题:补全原代码中缺失的if判断,完善事件循环逻辑
  4. 局部刷新机制:使用XSync只刷新修改的区域,进一步降低性能消耗

内容的提问来源于stack exchange,提问作者XoDefender

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 12:20:40