add transparency

This commit is contained in:
2026-07-18 08:42:01 +05:00
parent 2bbee73425
commit 7d91df3c6b
5 changed files with 83 additions and 16 deletions

View File

@@ -6,6 +6,7 @@
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <X11/extensions/Xcomposite.h>
#include <X11/extensions/Xdamage.h>
#include <errno.h>
#include <fcntl.h>
@@ -52,6 +53,8 @@ struct renderer {
Display *capture_display;
Window capture_window;
Pixmap capture_pixmap;
Damage capture_damage;
int damage_event_base;
XImage *capture_image;
XShmSegmentInfo capture_shm;
bool capture_redirected;
@@ -199,6 +202,10 @@ static bool initialize_x11_capture(struct renderer *renderer)
renderer->capture_display = XOpenDisplay(NULL);
if (!renderer->capture_display || !XShmQueryExtension(renderer->capture_display))
return false;
int damage_error_base = 0;
if (!XDamageQueryExtension(renderer->capture_display, &renderer->damage_event_base,
&damage_error_base))
return false;
/* An opaque WebKit view may render into child/compositor surfaces which are
* absent from the top-level window's named backing pixmap. Xvfb's root
@@ -224,6 +231,10 @@ static bool initialize_x11_capture(struct renderer *renderer)
if (!renderer->capture_pixmap)
return false;
}
renderer->capture_damage = XDamageCreate(renderer->capture_display,
renderer->capture_window, XDamageReportNonEmpty);
if (!renderer->capture_damage)
return false;
XWindowAttributes attributes;
if (!XGetWindowAttributes(renderer->capture_display, renderer->capture_window, &attributes))
return false;
@@ -257,6 +268,8 @@ static bool initialize_x11_capture(struct renderer *renderer)
static void destroy_x11_capture(struct renderer *renderer)
{
if (renderer->capture_damage)
XDamageDestroy(renderer->capture_display, renderer->capture_damage);
if (renderer->capture_attached) {
XShmDetach(renderer->capture_display, &renderer->capture_shm);
XSync(renderer->capture_display, False);
@@ -288,11 +301,28 @@ static void publish_frame(struct renderer *renderer, const uint8_t *pixels, int
for (uint32_t y = 0; y < renderer->config.height; y++)
memcpy(destination + (size_t)y * renderer->config.width * 4,
pixels + (size_t)y * (size_t)stride, (size_t)renderer->config.width * 4);
if (!preserve_alpha) {
for (size_t offset = 3, size = owb_frame_size(renderer->config.width,
renderer->config.height);
offset < size; offset += 4)
destination[offset] = 255;
size_t size = owb_frame_size(renderer->config.width, renderer->config.height);
for (size_t offset = 0; offset < size; offset += 4) {
uint8_t *pixel = destination + offset;
if (!preserve_alpha) {
pixel[3] = 255;
continue;
}
/* XComposite ARGB pixmaps contain premultiplied color channels, while
* OBS treats an asynchronous VIDEO_FORMAT_BGRA frame as straight alpha.
* Passing the pixmap through unchanged multiplies translucent colors a
* second time during scene compositing and leaves black fringes. */
unsigned alpha = pixel[3];
if (alpha == 0) {
pixel[0] = pixel[1] = pixel[2] = 0;
} else if (alpha < 255) {
for (size_t channel = 0; channel < 3; channel++) {
unsigned straight = ((unsigned)pixel[channel] * 255u + alpha / 2u) /
alpha;
pixel[channel] = (uint8_t)(straight > 255u ? 255u : straight);
}
}
}
__atomic_store_n(&renderer->shared->sequence[slot], complete_sequence, __ATOMIC_RELEASE);
uint8_t notification = (uint8_t)slot;
@@ -310,6 +340,18 @@ static void publish_x11_frame(struct renderer *renderer)
renderer->config.transparent && renderer->capture_image->depth == 32);
}
static bool capture_was_damaged(struct renderer *renderer)
{
bool damaged = false;
while (XPending(renderer->capture_display)) {
XEvent event;
XNextEvent(renderer->capture_display, &event);
if (event.type == renderer->damage_event_base + XDamageNotify)
damaged = true;
}
return damaged;
}
static void advance_deadline(struct timespec *deadline, long interval_ns)
{
deadline->tv_nsec += interval_ns;
@@ -325,8 +367,17 @@ static void *capture_loop(void *opaque)
long interval_ns = 1000000000L / (long)renderer->config.fps;
struct timespec deadline;
clock_gettime(CLOCK_MONOTONIC, &deadline);
bool first_frame = true;
while (!atomic_load_explicit(&renderer->stopping, memory_order_acquire)) {
publish_x11_frame(renderer);
bool damaged = capture_was_damaged(renderer);
if (first_frame || damaged) {
/* Clear the accumulated region before capture. Changes arriving while
* XShmGetImage runs then produce a fresh notification for the next tick. */
XDamageSubtract(renderer->capture_display, renderer->capture_damage,
None, None);
publish_x11_frame(renderer);
first_frame = false;
}
advance_deadline(&deadline, interval_ns);
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &deadline, NULL) == EINTR) {
}
@@ -548,6 +599,15 @@ int main(void)
NULL, NULL);
webkit_user_content_manager_add_script(manager, reporter);
webkit_user_script_unref(reporter);
if (renderer.config.transparent) {
static const char transparent_page_css[] =
"html,body{background:transparent!important}";
WebKitUserStyleSheet *transparent_sheet = webkit_user_style_sheet_new(
transparent_page_css, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL);
webkit_user_content_manager_add_style_sheet(manager, transparent_sheet);
webkit_user_style_sheet_unref(transparent_sheet);
}
if (*renderer.config.css) {
WebKitUserStyleSheet *sheet = webkit_user_style_sheet_new(renderer.config.css,
WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES, WEBKIT_USER_STYLE_LEVEL_USER, NULL, NULL);
@@ -600,7 +660,7 @@ int main(void)
while (gtk_events_pending())
gtk_main_iteration();
if (!initialize_x11_capture(&renderer)) {
fprintf(stderr, "Cannot initialize XComposite/XShm capture\n");
fprintf(stderr, "Cannot initialize XComposite/XDamage/XShm capture\n");
return 6;
}