diff --git a/background-image.c b/background-image.c index 53d1b63..5cac74b 100644 --- a/background-image.c +++ b/background-image.c @@ -22,17 +22,42 @@ enum background_mode parse_background_mode(const char *mode) { } cairo_surface_t *load_background_image(const char *path) { - cairo_surface_t *image; + 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); + } + + // if not a PNG image, try to load with gdk-pixbuf #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); + if (!image) { + 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; + } + image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + g_object_unref(pixbuf); + } +#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 + "\nSway was compiled without gdk_pixbuf support, so only" + "\nPNG images can be loaded. This is the likely cause." +#endif // !HAVE_GDK_PIXBUF + , cairo_status_to_string(cairo_surface_status(image))); return NULL; } - image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); - g_object_unref(pixbuf); + +#if HAVE_GDK_PIXBUF + #else image = cairo_image_surface_create_from_png(path); #endif // 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 7dc6a96..acc49a4 100644 --- a/main.c +++ b/main.c @@ -45,6 +45,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 { @@ -147,9 +149,26 @@ static void render_frame(struct swaybg_output *output, cairo_surface_t *surface) return; } + 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; } @@ -163,6 +182,10 @@ static void render_frame(struct swaybg_output *output, cairo_surface_t *surface) output->config->mode, buffer_width, buffer_height); } + if (format == WL_SHM_FORMAT_XBGR2101010) { + cairo_rgb30_swap_rb(buffer.surface); + } + wl_surface_set_buffer_scale(output->surface, output->scale); wl_surface_attach(output->surface, buffer.buffer, 0, 0); wl_surface_damage_buffer(output->surface, 0, 0, INT32_MAX, INT32_MAX); @@ -349,6 +372,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; @@ -357,6 +395,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 8017a3f..e3106b9 100644 --- a/pool-buffer.c +++ b/pool-buffer.c @@ -37,6 +37,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; @@ -57,10 +69,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; }