Skip to content

Commit

Permalink
Optionally submit 10 bit deep buffers
Browse files Browse the repository at this point in the history
This requires that the compositor support either XRGB2101010 or XBGR2101010,
and that the background image is a 16-bit PNG.
  • Loading branch information
mstoeckl committed Oct 27, 2024
1 parent b987b66 commit 0d44865
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 20 deletions.
45 changes: 27 additions & 18 deletions background-image.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,28 +22,37 @@ 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;

#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;
// 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 (!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);
}
#else // !HAVE_GDK_PIXBUF
image = cairo_image_surface_create_from_png(path);
#endif

if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
swaybg_log(LOG_ERROR, "Failed to read background image: %s."
#if !HAVE_GDK_PIXBUF
Expand Down
22 changes: 22 additions & 0 deletions cairo.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <assert.h>
#include <stdint.h>
#include <cairo.h>
#include "cairo_util.h"
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions include/cairo_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
40 changes: 39 additions & 1 deletion main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down
15 changes: 14 additions & 1 deletion pool-buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand Down

0 comments on commit 0d44865

Please sign in to comment.