Skip to content

Commit

Permalink
Render SVG files at output resolution
Browse files Browse the repository at this point in the history
This adds an optional dependency on librsvg to directly render the
SVG images, instead of using gdk-pixbuf to draw them to an
intermediate image.
  • Loading branch information
mstoeckl committed Oct 26, 2024
1 parent d7ec784 commit 1e585f8
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 10 deletions.
92 changes: 92 additions & 0 deletions background-image.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,95 @@ void render_background_image(cairo_t *cairo, cairo_surface_t *image,
cairo_paint(cairo);
cairo_restore(cairo);
}

#if HAVE_RSVG
void render_background_image_svg(cairo_t *cairo, RsvgHandle *svg,
enum background_mode mode, int buffer_width, int buffer_height) {
gdouble nat_width, nat_height;
if (!rsvg_handle_get_intrinsic_size_in_pixels(svg, &nat_width, &nat_height)) {
nat_width = buffer_width;
nat_height = buffer_height;
}
RsvgRectangle viewport;
GError *error = NULL;;
switch (mode) {
case BACKGROUND_MODE_STRETCH:
viewport.x = 0;
viewport.y = 0;
viewport.width = nat_width;
viewport.height = nat_height;
cairo_scale(cairo, buffer_width / nat_width, buffer_height / nat_height);
rsvg_handle_render_document(svg, cairo, &viewport, &error);
break;
case BACKGROUND_MODE_FILL: {
double window_ratio = (double)buffer_width / buffer_height;
double bg_ratio = nat_width / nat_height;

if (window_ratio > bg_ratio) {
double scale = (double)buffer_width / nat_width;

viewport.x = 0;
viewport.y = (double)buffer_height / 2 - scale * nat_height / 2;
viewport.width = buffer_width;
viewport.height = scale * nat_height;
} else {
double scale = (double)buffer_height / nat_height;

viewport.x = (double)buffer_width / 2 - scale *nat_width / 2;
viewport.y = 0;
viewport.width = scale * nat_width;
viewport.height = buffer_height;
}

rsvg_handle_render_document(svg, cairo, &viewport, &error);
break;
}
case BACKGROUND_MODE_FIT: {
double window_ratio = (double)buffer_width / buffer_height;
double bg_ratio = nat_width / nat_height;

if (window_ratio < bg_ratio) {
double scale = (double)buffer_width / nat_width;

viewport.x = 0;
viewport.y = (double)buffer_height / 2 - scale * nat_height / 2;
viewport.width = buffer_width;
viewport.height = scale * nat_height;
} else {
double scale = (double)buffer_height / nat_height;

viewport.x = (double)buffer_width / 2 - scale * nat_width / 2;
viewport.y = 0;
viewport.width = scale * nat_width;
viewport.height = buffer_height;
}
rsvg_handle_render_document(svg, cairo, &viewport, &error);
break;
}
case BACKGROUND_MODE_CENTER: {
viewport.x = (double)buffer_width / 2 - nat_width / 2;
viewport.y = (double)buffer_height / 2 - nat_height / 2;
viewport.width = nat_width;
viewport.height = nat_height;
rsvg_handle_render_document(svg, cairo, &viewport, &error);
break;
}
case BACKGROUND_MODE_TILE: {
for (int x = 0; x * nat_width < buffer_width; x++) {
for (int y = 0; y * nat_height < buffer_height; y++) {
viewport.x = nat_width * x;
viewport.y = nat_height * y;
viewport.width = nat_width;
viewport.height = nat_height;
rsvg_handle_render_document(svg, cairo, &viewport, &error);
}
}
break;
}
case BACKGROUND_MODE_SOLID_COLOR:
case BACKGROUND_MODE_INVALID:
assert(0);
break;
}
}
#endif
9 changes: 9 additions & 0 deletions include/background-image.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#ifndef _SWAY_BACKGROUND_IMAGE_H
#define _SWAY_BACKGROUND_IMAGE_H
#include "cairo_util.h"
#if HAVE_RSVG
#include "librsvg/rsvg.h"
#else
typedef struct _RsvgHandle RsvgHandle;
#endif

enum background_mode {
BACKGROUND_MODE_STRETCH,
Expand All @@ -16,5 +21,9 @@ enum background_mode parse_background_mode(const char *mode);
cairo_surface_t *load_background_image(const char *path);
void render_background_image(cairo_t *cairo, cairo_surface_t *image,
enum background_mode mode, int buffer_width, int buffer_height);
#if HAVE_RSVG
void render_background_image_svg(cairo_t *cairo, RsvgHandle *svg,
enum background_mode mode, int buffer_width, int buffer_height);
#endif

#endif
54 changes: 44 additions & 10 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <string.h>
#include <strings.h>
#include <wayland-client.h>
#if HAVE_RSVG
#include <librsvg/rsvg.h>
#endif
#include "background-image.h"
#include "cairo_util.h"
#include "log.h"
Expand Down Expand Up @@ -98,7 +101,8 @@ 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) {
cairo_surface_t *surface, RsvgHandle *svg,
uint32_t buffer_width, uint32_t buffer_height) {
uint32_t bg_color = output->config->color ? output->config->color : 0x000000ff;

if (buffer_width == 1 && buffer_height == 1 &&
Expand Down Expand Up @@ -132,6 +136,12 @@ static struct wl_buffer *draw_buffer(const struct swaybg_output *output,
render_background_image(cairo, surface,
output->config->mode, buffer_width, buffer_height);
}
#if HAVE_RSVG
if (svg) {
render_background_image_svg(cairo, svg, output->config->mode,
buffer_width, buffer_height);
}
#endif

// return wl_buffer for caller to use and destroy
struct wl_buffer *wl_buf = buffer.buffer;
Expand Down Expand Up @@ -161,15 +171,16 @@ static void get_buffer_size(const struct swaybg_output *output,
}
}

static void render_frame(struct swaybg_output *output, cairo_surface_t *surface) {
static void render_frame(struct swaybg_output *output, cairo_surface_t *surface,
RsvgHandle *svg) {
uint32_t buffer_width, buffer_height;
get_buffer_size(output, &buffer_width, &buffer_height);

// Attach a new buffer if the desired size has changed
struct wl_buffer *buf = NULL;
if (buffer_width != output->buffer_width ||
buffer_height != output->buffer_height) {
buf = draw_buffer(output, surface,
buf = draw_buffer(output, surface, svg,
buffer_width, buffer_height);
if (!buf) {
return;
Expand Down Expand Up @@ -660,28 +671,51 @@ int main(int argc, char **argv) {
continue;
}

cairo_surface_t *surface = load_background_image(image->path);
if (!surface) {
swaybg_log(LOG_ERROR, "Failed to load image: %s", image->path);
continue;
cairo_surface_t *surface = NULL;
RsvgHandle *svg = NULL;
#if HAVE_RSVG
const char *suffix = strrchr(image->path, '.');
if (suffix && (!strcmp(suffix, ".svg") || !strcmp(suffix, ".SVG"))) {
GError *error = NULL;
svg = rsvg_handle_new_from_file(image->path, &error);
if (!svg) {
swaybg_log(LOG_ERROR, "Failed to load image: %s, %s", image->path, error->message);
continue;
}
rsvg_handle_set_dpi(svg, 96.0);
#else
if (false) {
#endif
} else {
svg = NULL;
surface = load_background_image(image->path);
if (!surface) {
swaybg_log(LOG_ERROR, "Failed to load image: %s", image->path);
continue;
}
}

wl_list_for_each(output, &state.outputs, link) {
if (output->dirty && output->config->image == image) {
output->dirty = false;
render_frame(output, surface);
render_frame(output, surface, svg);
}
}

image->load_required = false;
cairo_surface_destroy(surface);
if (surface) {
cairo_surface_destroy(surface);
}
if (svg) {
g_object_unref(svg);
}
}

// Redraw outputs without associated image
wl_list_for_each(output, &state.outputs, link) {
if (output->dirty) {
output->dirty = false;
render_frame(output, NULL);
render_frame(output, NULL, NULL);
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ wayland_protos = dependency('wayland-protocols', version: '>=1.31')
wayland_scanner = dependency('wayland-scanner', version: '>=1.14.91', native: true)
cairo = dependency('cairo')
gdk_pixbuf = dependency('gdk-pixbuf-2.0', required: get_option('gdk-pixbuf'))
rsvg = dependency('librsvg-2.0', required: get_option('rsvg'))

git = find_program('git', required: false, native: true)
scdoc = find_program('scdoc', required: get_option('man-pages'), native: true)
Expand All @@ -54,6 +55,7 @@ endif
add_project_arguments([
'-DSWAYBG_VERSION=@0@'.format(version),
'-DHAVE_GDK_PIXBUF=@0@'.format(gdk_pixbuf.found().to_int()),
'-DHAVE_RSVG=@0@'.format(rsvg.found().to_int()),
], language: 'c')

wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
Expand Down Expand Up @@ -103,6 +105,7 @@ executable(
rt,
gdk_pixbuf,
wayland_client,
rsvg,
],
install: true
)
Expand Down
1 change: 1 addition & 0 deletions meson_options.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
option('gdk-pixbuf', type: 'feature', value: 'auto', description: 'Enable support for more image formats')
option('rsvg', type: 'feature', value: 'auto', description: 'Enable support for directly rendering SVG images')
option('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages')

0 comments on commit 1e585f8

Please sign in to comment.