Cairo在将渲染输出到Xlib表面时,透明区域会显示垃圾像素吗?

编程语言 2026-07-09

我正在尝试使用Cairo在一个窗口中渲染一张基本图像。我先把窗口设好,然后像往常一样把图像绘制到窗口上:

#include <threads.h>
#include <time.h>

#include <X11/Xlib.h>

#include <cairo.h>
#include <cairo-xlib.h>

constexpr int start_width = 192;
constexpr int start_height = 192;

Display *_Atomic display = nullptr;
_Atomic bool have_been_killed = false;

constexpr double button_size = 192;
constexpr double button_thickness = 8;
constexpr cairo_rectangle_t button_extents = {.x = 0, .y = 0, .width = button_size, .height = button_size};

cairo_surface_t *drawButton() {
    cairo_surface_t *surface = cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &button_extents);
    cairo_status_t status = cairo_surface_status(surface);
    if (status != CAIRO_STATUS_SUCCESS) {
        return surface;
    }
    cairo_t *ctx = cairo_create(surface);
    status = cairo_status(ctx);
    if (status != CAIRO_STATUS_SUCCESS) {
        goto done;
    }
    cairo_set_source_rgba(ctx, 0.306, 0.518, 0.518, 0.506);
    constexpr double lt = button_thickness / 2;
    constexpr double rt = button_size - button_thickness / 2;
    constexpr double tp = lt;
    constexpr double bm = rt;
    constexpr double mx = button_size / 2;
    constexpr double my = mx;
    cairo_set_line_width(ctx, button_thickness);
    cairo_move_to(ctx, lt, my);
    cairo_curve_to(ctx, lt, tp, lt, tp, mx, tp);
    cairo_curve_to(ctx, rt, tp, rt, tp, rt, my);
    cairo_curve_to(ctx, rt, bm, rt, bm, mx, bm);
    cairo_curve_to(ctx, lt, bm, lt, bm, lt, my);
    cairo_close_path(ctx);
    cairo_stroke(ctx);
done:
    cairo_destroy(ctx);
    return surface;
}

int guiThread(void *data);

int main() {
    display = XOpenDisplay(nullptr);
    if (!display) {
        return 1;
    }
    const Window root_window = XDefaultRootWindow(display);
    if (!root_window) {
        return 1;
    }

    Window main_window = XCreateWindow(
        display, root_window,
        100, 100, start_width, start_height, 0,
        CopyFromParent, CopyFromParent, CopyFromParent,
        0, nullptr
    );
    XWindowAttributes main_window_attributes;
    if (!XGetWindowAttributes(display, main_window, &main_window_attributes)) {
        return 1;
    }

    Atom WM_DELETE_WINDOW = XInternAtom(display, "WM_DELETE_WINDOW", true);
    if(!XSetWMProtocols(display, main_window, &WM_DELETE_WINDOW, 1)) {
        return 1;
    }

    cairo_surface_t *surface = cairo_xlib_surface_create(display, main_window, main_window_attributes.visual, start_width, start_height);
    cairo_status_t status = cairo_surface_status(surface);
    if (status != CAIRO_STATUS_SUCCESS) {
        return 1;
    }
    thrd_t gui_thread;
    if (thrd_create(&gui_thread, guiThread, surface) != thrd_success) {
        return 1;
    }

    XMapWindow(display, main_window);
    XFlush(display);
    while (!have_been_killed) {
        XEvent event;
        XNextEvent(display, &event);
        switch (event.type) {
            default:
                if (event.xany.window != main_window) {
                    continue;
                }
            case KeymapNotify:
            case GenericEvent:
        }
        switch (event.type) {
            case ClientMessage: {
                XClientMessageEvent e = event.xclient;
                if ((Atom)e.data.l[0] == WM_DELETE_WINDOW) {
                    have_been_killed = true;
                }
                break;
            }
            default:
        }
    }
    thrd_join(gui_thread, nullptr);
    XCloseDisplay(display);
    return 0;
}

int guiThread(void *data) {
    cairo_surface_t *surface = data;
    cairo_surface_t *button = drawButton();
    cairo_t *ctx = cairo_create(surface);
    while (!have_been_killed) {
        cairo_set_source_rgb(ctx, 0, 0, 0);
        cairo_paint(ctx);
        cairo_set_source_surface(ctx, button, 0, 0);
        cairo_paint(ctx);

        cairo_surface_flush(surface);
        XFlush(display);
        thrd_sleep(&(struct timespec){.tv_nsec = 10'000'000}, nullptr);
    }
    cairo_destroy(ctx);
}

然而,在透明区域里,本应显示透明的地方,却绘制出垃圾像素(癫痫警告):

https://i.imgur.com/WKCd1Kg.mp4

此外,尽管垃圾像素通常来自读取不应该读取的内存,但使用 -fsanitize=address 编译并不会暴露任何问题。若把绘制目标改成像SVG表面这样的表面而不是窗口,它也不会这样;若改用图像表面而不是记录表面来绘制,也不会这样。

我希望这不会因为展示这些垃圾像素而暴露我的身份信息。为什么会这样?我应该如何解决?

解决方案

原来你并没有覆盖所有像素。你很可能需要这样做:

  // in your button drawing code   
  cairo_t *b = cairo_create(btn);
  cairo_set_source_rgb(b, 0, 0, 0);
  cairo_rectangle(b, 0, 0, WIDTH, HEIGHT);  // create the background!
  cairo_fill(b);                            // fill it!
  cairo_set_source_rgba(b, 0.306, 0.518, 0.518, 0.506);
  // continue drawing button

  // in your x11 paint code
  /* paint */
  cairo_set_source_surface(ctx, btn, 0, 0);
  cairo_paint(ctx);
  // That's it

我还建议在Expose事件到来时再进行绘制,而不是每隔X 毫秒(那会浪费资源)。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章