swaylock/background-image.c
Neil Muller b70d85ec14 Correct for image orientation when loading image
PEG and other image formats may include an EXIF orientation tag
which indicates in what orientation (rotation and mirroring)
the image should be displayed. libgdk-pixbuf does not correct for this
when loading an image, but provides a function to apply the transform
after the fact, which this commit uses.

Pulled from https://github.com/swaywm/swaybg/pull/68
2024-06-14 22:41:03 +02:00

130 lines
4.0 KiB
C

#include <assert.h>
#include "background-image.h"
#include "cairo.h"
#include "log.h"
enum background_mode parse_background_mode(const char *mode) {
if (strcmp(mode, "stretch") == 0) {
return BACKGROUND_MODE_STRETCH;
} else if (strcmp(mode, "fill") == 0) {
return BACKGROUND_MODE_FILL;
} else if (strcmp(mode, "fit") == 0) {
return BACKGROUND_MODE_FIT;
} else if (strcmp(mode, "center") == 0) {
return BACKGROUND_MODE_CENTER;
} else if (strcmp(mode, "tile") == 0) {
return BACKGROUND_MODE_TILE;
} else if (strcmp(mode, "solid_color") == 0) {
return BACKGROUND_MODE_SOLID_COLOR;
}
swaylock_log(LOG_ERROR, "Unsupported background mode: %s", mode);
return BACKGROUND_MODE_INVALID;
}
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) {
swaylock_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
image = cairo_image_surface_create_from_png(path);
#endif // HAVE_GDK_PIXBUF
if (!image) {
swaylock_log(LOG_ERROR, "Failed to read background image.");
return NULL;
}
if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
swaylock_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;
}
return image;
}
void render_background_image(cairo_t *cairo, cairo_surface_t *image,
enum background_mode mode, int buffer_width, int buffer_height) {
double width = cairo_image_surface_get_width(image);
double height = cairo_image_surface_get_height(image);
cairo_save(cairo);
switch (mode) {
case BACKGROUND_MODE_STRETCH:
cairo_scale(cairo,
(double)buffer_width / width,
(double)buffer_height / height);
cairo_set_source_surface(cairo, image, 0, 0);
break;
case BACKGROUND_MODE_FILL: {
double window_ratio = (double)buffer_width / buffer_height;
double bg_ratio = width / height;
if (window_ratio > bg_ratio) {
double scale = (double)buffer_width / width;
cairo_scale(cairo, scale, scale);
cairo_set_source_surface(cairo, image,
0, (double)buffer_height / 2 / scale - height / 2);
} else {
double scale = (double)buffer_height / height;
cairo_scale(cairo, scale, scale);
cairo_set_source_surface(cairo, image,
(double)buffer_width / 2 / scale - width / 2, 0);
}
break;
}
case BACKGROUND_MODE_FIT: {
double window_ratio = (double)buffer_width / buffer_height;
double bg_ratio = width / height;
if (window_ratio > bg_ratio) {
double scale = (double)buffer_height / height;
cairo_scale(cairo, scale, scale);
cairo_set_source_surface(cairo, image,
(double)buffer_width / 2 / scale - width / 2, 0);
} else {
double scale = (double)buffer_width / width;
cairo_scale(cairo, scale, scale);
cairo_set_source_surface(cairo, image,
0, (double)buffer_height / 2 / scale - height / 2);
}
break;
}
case BACKGROUND_MODE_CENTER:
/*
* Align the unscaled image to integer pixel boundaries
* in order to prevent loss of clarity (this only matters
* for odd-sized images).
*/
cairo_set_source_surface(cairo, image,
(int)((double)buffer_width / 2 - width / 2),
(int)((double)buffer_height / 2 - height / 2));
break;
case BACKGROUND_MODE_TILE: {
cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image);
cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
cairo_set_source(cairo, pattern);
break;
}
case BACKGROUND_MODE_SOLID_COLOR:
case BACKGROUND_MODE_INVALID:
assert(0);
break;
}
cairo_paint(cairo);
cairo_restore(cairo);
}