From 021b27848231041a9a81d08d98c80b233255b166 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sun, 29 Aug 2021 17:56:47 -0400 Subject: [PATCH 1/4] Set default background color to #3f3f3fff This ensures that the background is always opaque. This is a significant change; before, if no background color was specified, images were drawn onto an initially transparent buffer, leaving some of the content behind swaybg visible if the image was alpha transparent or if the 'fit' or 'center' scaling modes were used and the image aspect ratio did not match, or the image was not large enough. As the purpose of a wallpaper/background program is to draw the pixels behind all other interface items, this transparency is neither a required nor a very useful behavior. The new color is close to the result of blending the old default background color (#00000000 = transparent) with the color Sway uses to clear the background ([0.25,0.25,0.25,1.0] = mid gray). Users of other compositors may need to adjust their images or specify the --color flag on the command line. --- main.c | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/main.c b/main.c index fc6725e..453419a 100644 --- a/main.c +++ b/main.c @@ -99,21 +99,22 @@ struct swaybg_output { // Create a wl_buffer with the specified dimensions and content static struct wl_buffer *draw_buffer(const struct swaybg_output *output, cairo_surface_t *surface, uint32_t buffer_width, uint32_t buffer_height) { + uint32_t bg_color = output->config->color ? output->config->color : 0x3f3f3fff; + if (buffer_width == 1 && buffer_height == 1 && output->config->mode == BACKGROUND_MODE_SOLID_COLOR && output->state->single_pixel_buffer_manager) { // create and return single pixel buffer - uint8_t r8 = (output->config->color >> 24) & 0xFF; - uint8_t g8 = (output->config->color >> 16) & 0xFF; - uint8_t b8 = (output->config->color >> 8) & 0xFF; - uint8_t a8 = (output->config->color >> 0) & 0xFF; + uint8_t r8 = (bg_color >> 24) & 0xFF; + uint8_t g8 = (bg_color >> 16) & 0xFF; + uint8_t b8 = (bg_color >> 8) & 0xFF; uint32_t f = 0xFFFFFFFF / 0xFF; // division result is an integer uint32_t r32 = r8 * f; uint32_t g32 = g8 * f; uint32_t b32 = b8 * f; - uint32_t a32 = a8 * f; return wp_single_pixel_buffer_manager_v1_create_u32_rgba_buffer( - output->state->single_pixel_buffer_manager, r32, g32, b32, a32); + output->state->single_pixel_buffer_manager, + r32, g32, b32, 0xFFFFFFFF); } @@ -124,23 +125,12 @@ static struct wl_buffer *draw_buffer(const struct swaybg_output *output, } cairo_t *cairo = buffer.cairo; - cairo_save(cairo); - cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR); + cairo_set_source_u32(cairo, bg_color); cairo_paint(cairo); - cairo_restore(cairo); - if (output->config->mode == BACKGROUND_MODE_SOLID_COLOR) { - cairo_set_source_u32(cairo, output->config->color); - cairo_paint(cairo); - } else { - if (output->config->color) { - cairo_set_source_u32(cairo, output->config->color); - cairo_paint(cairo); - } - if (surface) { - render_background_image(cairo, surface, - output->config->mode, buffer_width, buffer_height); - } + if (surface) { + render_background_image(cairo, surface, + output->config->mode, buffer_width, buffer_height); } // return wl_buffer for caller to use and destroy From fda4ace35b2f28c18d1f0aaaff651edeb2c6403c Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 31 Dec 2022 21:05:09 -0500 Subject: [PATCH 2/4] Only submit opaque (XRGB8888) buffers This lets the compositor avoid needing to clear and blend the pixels behind the background surface made by swaybg. --- main.c | 2 +- pool-buffer.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.c b/main.c index 453419a..89f86eb 100644 --- a/main.c +++ b/main.c @@ -120,7 +120,7 @@ static struct wl_buffer *draw_buffer(const struct swaybg_output *output, struct pool_buffer buffer; if (!create_buffer(&buffer, output->state->shm, - buffer_width, buffer_height, WL_SHM_FORMAT_ARGB8888)) { + buffer_width, buffer_height, WL_SHM_FORMAT_XRGB8888)) { return NULL; } diff --git a/pool-buffer.c b/pool-buffer.c index 9872ec5..4b7b63b 100644 --- a/pool-buffer.c +++ b/pool-buffer.c @@ -59,7 +59,7 @@ bool create_buffer(struct pool_buffer *buf, struct wl_shm *shm, buf->size = size; buf->data = data; buf->surface = cairo_image_surface_create_for_data(data, - CAIRO_FORMAT_ARGB32, width, height, stride); + CAIRO_FORMAT_RGB24, width, height, stride); buf->cairo = cairo_create(buf->surface); return true; } From edcd734d566bd79e3848493e9c4611a874624846 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 7 Jan 2023 23:27:16 -0500 Subject: [PATCH 3/4] Remove unused function --- cairo.c | 16 ---------------- include/cairo_util.h | 4 ---- 2 files changed, 20 deletions(-) diff --git a/cairo.c b/cairo.c index ee7d3ad..7d620d4 100644 --- a/cairo.c +++ b/cairo.c @@ -13,22 +13,6 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { (color >> (0*8) & 0xFF) / 255.0); } -cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel) { - switch (subpixel) { - case WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB: - return CAIRO_SUBPIXEL_ORDER_RGB; - case WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR: - return CAIRO_SUBPIXEL_ORDER_BGR; - case WL_OUTPUT_SUBPIXEL_VERTICAL_RGB: - return CAIRO_SUBPIXEL_ORDER_VRGB; - case WL_OUTPUT_SUBPIXEL_VERTICAL_BGR: - return CAIRO_SUBPIXEL_ORDER_VBGR; - default: - return CAIRO_SUBPIXEL_ORDER_DEFAULT; - } - return CAIRO_SUBPIXEL_ORDER_DEFAULT; -} - #if HAVE_GDK_PIXBUF cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(const GdkPixbuf *gdkbuf) { int chan = gdk_pixbuf_get_n_channels(gdkbuf); diff --git a/include/cairo_util.h b/include/cairo_util.h index fd611b6..651adab 100644 --- a/include/cairo_util.h +++ b/include/cairo_util.h @@ -9,10 +9,6 @@ #endif void cairo_set_source_u32(cairo_t *cairo, uint32_t color); -cairo_subpixel_order_t to_cairo_subpixel_order(enum wl_output_subpixel subpixel); - -cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, - int width, int height); #if HAVE_GDK_PIXBUF From 2230501a2319078722ed0fada85afc55f35411f7 Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Sat, 7 Jan 2023 11:58:51 -0500 Subject: [PATCH 4/4] Optionally submit 10 bit deep buffers This requires that the compositor support either XRGB2101010 or XBGR2101010, and that the background image is a 16-bit PNG. --- background-image.c | 45 +++++++++++++++++++++++++------------------- cairo.c | 22 ++++++++++++++++++++++ include/cairo_util.h | 2 ++ main.c | 40 ++++++++++++++++++++++++++++++++++++++- pool-buffer.c | 15 ++++++++++++++- 5 files changed, 103 insertions(+), 21 deletions(-) diff --git a/background-image.c b/background-image.c index 3eae832..ca43178 100644 --- a/background-image.c +++ b/background-image.c @@ -22,28 +22,35 @@ enum background_mode parse_background_mode(const char *mode) { } cairo_surface_t *load_background_image(const char *path) { - cairo_surface_t *image; -#if HAVE_GDK_PIXBUF - GError *err = NULL; - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); - if (!pixbuf) { - swaybg_log(LOG_ERROR, "Failed to load background image (%s).", - err->message); - return NULL; + cairo_surface_t *image = NULL; + + // Prefer to load PNG images with Cairo, since it can load images with + // higher bit depths at full precision + const char *suffix = strrchr(path, '.'); + if (suffix && (!strcmp(suffix, ".png") || !strcmp(suffix, ".PNG"))) { + image = cairo_image_surface_create_from_png(path); } - // Correct for embedded image orientation; typical images are not - // rotated and will be handled efficiently - GdkPixbuf *oriented = gdk_pixbuf_apply_embedded_orientation(pixbuf); - g_object_unref(pixbuf); - image = gdk_cairo_image_surface_create_from_pixbuf(oriented); - g_object_unref(oriented); -#else - image = cairo_image_surface_create_from_png(path); -#endif // HAVE_GDK_PIXBUF + + // if not a PNG image, try to load with gdk-pixbuf +#if HAVE_GDK_PIXBUF if (!image) { - swaybg_log(LOG_ERROR, "Failed to read background image."); - return NULL; + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err); + if (!pixbuf) { + swaybg_log(LOG_ERROR, "Failed to load background image (%s).", + err->message); + return NULL; + } + + // Correct for embedded image orientation; typical images are not + // rotated and will be handled efficiently + GdkPixbuf *oriented = gdk_pixbuf_apply_embedded_orientation(pixbuf); + g_object_unref(pixbuf); + image = gdk_cairo_image_surface_create_from_pixbuf(oriented); + g_object_unref(oriented); } +#endif // HAVE_GDK_PIXBUF + if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) { swaybg_log(LOG_ERROR, "Failed to read background image: %s." #if !HAVE_GDK_PIXBUF diff --git a/cairo.c b/cairo.c index 7d620d4..0271aec 100644 --- a/cairo.c +++ b/cairo.c @@ -1,3 +1,4 @@ +#include #include #include #include "cairo_util.h" @@ -13,6 +14,27 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { (color >> (0*8) & 0xFF) / 255.0); } +void cairo_rgb30_swap_rb(cairo_surface_t *surface) { + assert(cairo_image_surface_get_format(surface) == CAIRO_FORMAT_RGB30); + + unsigned char *data = cairo_image_surface_get_data(surface); + int w = cairo_image_surface_get_width(surface); + int h = cairo_image_surface_get_height(surface); + int stride = cairo_image_surface_get_stride(surface); + for (int y = 0; y < h; y++) { + uint32_t *row = (uint32_t *)(data + stride * y); + for (int x = 0; x < w; x++) { + uint32_t pix = row[x]; + // swap blue (0:10) and red (20:30) + pix = (pix & 0xc00ffc00) | ((pix & 0x3ff00000) >> 20) | + ((pix & 0x3ff) << 20); + row[x] = pix; + } + } + + cairo_surface_mark_dirty(surface); +} + #if HAVE_GDK_PIXBUF cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf(const GdkPixbuf *gdkbuf) { int chan = gdk_pixbuf_get_n_channels(gdkbuf); diff --git a/include/cairo_util.h b/include/cairo_util.h index 651adab..eb3998d 100644 --- a/include/cairo_util.h +++ b/include/cairo_util.h @@ -10,6 +10,8 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color); +void cairo_rgb30_swap_rb(cairo_surface_t *surface); + #if HAVE_GDK_PIXBUF cairo_surface_t* gdk_cairo_image_surface_create_from_pixbuf( diff --git a/main.c b/main.c index 89f86eb..7baf6a0 100644 --- a/main.c +++ b/main.c @@ -53,6 +53,8 @@ struct swaybg_state { struct wl_list outputs; // struct swaybg_output::link struct wl_list images; // struct swaybg_image::link bool run_display; + bool has_xrgb2101010; + bool has_xbgr2101010; }; struct swaybg_image { @@ -117,10 +119,26 @@ static struct wl_buffer *draw_buffer(const struct swaybg_output *output, r32, g32, b32, 0xFFFFFFFF); } + bool deep_image = false; + if (surface) { + cairo_format_t fmt = cairo_image_surface_get_format(surface); + deep_image = deep_image || fmt == CAIRO_FORMAT_RGB30; +#if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 17, 2) + deep_image = deep_image || fmt == CAIRO_FORMAT_RGB96F; + deep_image = deep_image || fmt == CAIRO_FORMAT_RGBA128F; +#endif + } + + uint32_t format = WL_SHM_FORMAT_XRGB8888; + if (deep_image && output->state->has_xrgb2101010) { + format = WL_SHM_FORMAT_XRGB2101010; + } else if (deep_image && output->state->has_xbgr2101010) { + format = WL_SHM_FORMAT_XBGR2101010; + } struct pool_buffer buffer; if (!create_buffer(&buffer, output->state->shm, - buffer_width, buffer_height, WL_SHM_FORMAT_XRGB8888)) { + buffer_width, buffer_height, format)) { return NULL; } @@ -133,6 +151,10 @@ static struct wl_buffer *draw_buffer(const struct swaybg_output *output, output->config->mode, buffer_width, buffer_height); } + if (format == WL_SHM_FORMAT_XBGR2101010) { + cairo_rgb30_swap_rb(buffer.surface); + } + // return wl_buffer for caller to use and destroy struct wl_buffer *wl_buf = buffer.buffer; buffer.buffer = NULL; @@ -398,6 +420,21 @@ static const struct wl_output_listener output_listener = { .description = output_description, }; + +static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) { + struct swaybg_state *state = data; + if (format == WL_SHM_FORMAT_XBGR2101010) { + state->has_xbgr2101010 = true; + } + if (format == WL_SHM_FORMAT_XRGB2101010) { + state->has_xrgb2101010 = true; + } +} + +static const struct wl_shm_listener shm_listener = { + .format = shm_format, +}; + static void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { struct swaybg_state *state = data; @@ -406,6 +443,7 @@ static void handle_global(void *data, struct wl_registry *registry, wl_registry_bind(registry, name, &wl_compositor_interface, 4); } else if (strcmp(interface, wl_shm_interface.name) == 0) { state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); + wl_shm_add_listener(state->shm, &shm_listener, state); } else if (strcmp(interface, wl_output_interface.name) == 0) { struct swaybg_output *output = calloc(1, sizeof(struct swaybg_output)); output->state = state; diff --git a/pool-buffer.c b/pool-buffer.c index 4b7b63b..6aab4c2 100644 --- a/pool-buffer.c +++ b/pool-buffer.c @@ -36,6 +36,18 @@ static int anonymous_shm_open(void) { return -1; } +static uint32_t cairo_format_from_wayland_shm(uint32_t shm) { + switch (shm) { + case WL_SHM_FORMAT_XRGB8888: + return CAIRO_FORMAT_RGB24; + case WL_SHM_FORMAT_XBGR2101010: + case WL_SHM_FORMAT_XRGB2101010: + return CAIRO_FORMAT_RGB30; + default: + assert(0); + } +} + bool create_buffer(struct pool_buffer *buf, struct wl_shm *shm, int32_t width, int32_t height, uint32_t format) { uint32_t stride = width * 4; @@ -56,10 +68,11 @@ bool create_buffer(struct pool_buffer *buf, struct wl_shm *shm, wl_shm_pool_destroy(pool); close(fd); + cairo_format_t cairo_fmt = cairo_format_from_wayland_shm(format); buf->size = size; buf->data = data; buf->surface = cairo_image_surface_create_for_data(data, - CAIRO_FORMAT_RGB24, width, height, stride); + cairo_fmt, width, height, stride); buf->cairo = cairo_create(buf->surface); return true; }