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 ee7d3ad..0271aec 100644 --- a/cairo.c +++ b/cairo.c @@ -1,3 +1,4 @@ +#include #include #include #include "cairo_util.h" @@ -13,20 +14,25 @@ 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; +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; + } } - return CAIRO_SUBPIXEL_ORDER_DEFAULT; + + cairo_surface_mark_dirty(surface); } #if HAVE_GDK_PIXBUF diff --git a/include/cairo_util.h b/include/cairo_util.h index fd611b6..eb3998d 100644 --- a/include/cairo_util.h +++ b/include/cairo_util.h @@ -9,10 +9,8 @@ #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); +void cairo_rgb30_swap_rb(cairo_surface_t *surface); #if HAVE_GDK_PIXBUF diff --git a/main.c b/main.c index fc6725e..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 { @@ -99,48 +101,58 @@ 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); } + 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_ARGB8888)) { + buffer_width, buffer_height, format)) { return NULL; } 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); + } + + if (format == WL_SHM_FORMAT_XBGR2101010) { + cairo_rgb30_swap_rb(buffer.surface); } // return wl_buffer for caller to use and destroy @@ -408,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; @@ -416,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 9872ec5..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_ARGB32, width, height, stride); + cairo_fmt, width, height, stride); buf->cairo = cairo_create(buf->surface); return true; }