1 #include <assert.h>
2 #include "background-image.h"
3 #include "cairo.h"
4 #include "log.h"
5 #include "swaylock.h"
6 
parse_background_mode(const char * mode)7 enum background_mode parse_background_mode(const char *mode) {
8 	if (strcmp(mode, "stretch") == 0) {
9 		return BACKGROUND_MODE_STRETCH;
10 	} else if (strcmp(mode, "fill") == 0) {
11 		return BACKGROUND_MODE_FILL;
12 	} else if (strcmp(mode, "fit") == 0) {
13 		return BACKGROUND_MODE_FIT;
14 	} else if (strcmp(mode, "center") == 0) {
15 		return BACKGROUND_MODE_CENTER;
16 	} else if (strcmp(mode, "tile") == 0) {
17 		return BACKGROUND_MODE_TILE;
18 	} else if (strcmp(mode, "solid_color") == 0) {
19 		return BACKGROUND_MODE_SOLID_COLOR;
20 	}
21 	swaylock_log(LOG_ERROR, "Unsupported background mode: %s", mode);
22 	return BACKGROUND_MODE_INVALID;
23 }
24 
load_background_from_buffer(void * buf,uint32_t format,uint32_t width,uint32_t height,uint32_t stride,enum wl_output_transform transform)25 cairo_surface_t *load_background_from_buffer(void *buf, uint32_t format,
26 		uint32_t width, uint32_t height, uint32_t stride, enum wl_output_transform transform) {
27 	bool rotated =
28 		transform == WL_OUTPUT_TRANSFORM_90 ||
29 		transform == WL_OUTPUT_TRANSFORM_270 ||
30 		transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 ||
31 		transform == WL_OUTPUT_TRANSFORM_FLIPPED_270;
32 
33 	cairo_surface_t *image;
34 	if (rotated) {
35 		image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, height, width);
36 	} else {
37 		image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
38 	}
39 	if (image == NULL) {
40 		swaylock_log(LOG_ERROR, "Failed to create image..");
41 		return NULL;
42 	}
43 
44 	unsigned char *destbuf = cairo_image_surface_get_data(image);
45 	size_t destwidth = cairo_image_surface_get_width(image);
46 	size_t destheight = cairo_image_surface_get_height(image);
47 	size_t deststride = cairo_image_surface_get_stride(image);
48 	unsigned char *srcbuf = buf;
49 	size_t srcstride = stride;
50 	size_t minstride = srcstride < deststride ? srcstride : deststride;
51 
52 	// Lots of these are mostly-copy-and-pasted, with a lot of boilerplate
53 	// for each case.
54 	// The only interesting differencess between a lot of these cases are
55 	// the definitions of srcx and srcy.
56 	// I don't think it's worth adding a macro to make this "cleaner" though,
57 	// as that would obfuscate what's actually going on.
58 	switch (transform) {
59 	case WL_OUTPUT_TRANSFORM_NORMAL:
60 		// In most cases, the transform is probably normal. Luckily, it can be
61 		// done with just one big memcpy.
62 		if (srcstride == deststride) {
63 			memcpy(destbuf, srcbuf, destheight * deststride);
64 		} else {
65 			for (size_t y = 0; y < destheight; ++y) {
66 				memcpy(destbuf + y * deststride, srcbuf + y * srcstride, minstride);
67 			}
68 		}
69 		break;
70 	case WL_OUTPUT_TRANSFORM_90:
71 		for (size_t desty = 0; desty < destheight; ++desty) {
72 			size_t srcx = desty;
73 			for (size_t destx = 0; destx < destwidth; ++destx) {
74 				size_t srcy = destwidth - destx - 1;
75 				*((uint32_t *)(destbuf + desty * deststride) + destx) =
76 					*((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
77 			}
78 		}
79 		break;
80 	case WL_OUTPUT_TRANSFORM_180:
81 		for (size_t desty = 0; desty < destheight; ++desty) {
82 			size_t srcy = destheight - desty - 1;
83 			for (size_t destx = 0; destx < destwidth; ++destx) {
84 				size_t srcx = destwidth - destx - 1;
85 				*((uint32_t *)(destbuf + desty * deststride) + destx) =
86 					*((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
87 			}
88 		}
89 		break;
90 	case WL_OUTPUT_TRANSFORM_270:
91 		for (size_t desty = 0; desty < destheight; ++desty) {
92 			size_t srcx = destheight - desty - 1;
93 			for (size_t destx = 0; destx < destwidth; ++destx) {
94 				size_t srcy = destx;
95 				*((uint32_t *)(destbuf + desty * deststride) + destx) =
96 					*((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
97 			}
98 		}
99 		break;
100 	case WL_OUTPUT_TRANSFORM_FLIPPED:
101 		for (size_t desty = 0; desty < destheight; ++desty) {
102 			size_t srcy = desty;
103 			for (size_t destx = 0; destx < destwidth; ++destx) {
104 				size_t srcx = destwidth - destx - 1;
105 				*((uint32_t *)(destbuf + desty * deststride) + destx) =
106 					*((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
107 			}
108 		}
109 		break;
110 	case WL_OUTPUT_TRANSFORM_FLIPPED_90:
111 		for (size_t desty = 0; desty < destheight; ++desty) {
112 			size_t srcx = desty;
113 			for (size_t destx = 0; destx < destwidth; ++destx) {
114 				size_t srcy = destx;
115 				*((uint32_t *)(destbuf + desty * deststride) + destx) =
116 					*((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
117 			}
118 		}
119 		break;
120 	case WL_OUTPUT_TRANSFORM_FLIPPED_180:
121 		for (size_t desty = 0; desty < destheight; ++desty) {
122 			size_t srcy = destheight - desty - 1;
123 			memcpy(destbuf + desty * deststride, srcbuf + srcy * srcstride, minstride);
124 		}
125 		break;
126 	case WL_OUTPUT_TRANSFORM_FLIPPED_270:
127 		for (size_t desty = 0; desty < destheight; ++desty) {
128 			size_t srcx = destheight - desty - 1;
129 			for (size_t destx = 0; destx < destwidth; ++destx) {
130 				size_t srcy = destwidth - destx - 1;
131 				*((uint32_t *)(destbuf + desty * deststride) + destx) =
132 					*((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
133 			}
134 		}
135 		break;
136 	}
137 
138 	return image;
139 }
140 
load_background_image(const char * path)141 cairo_surface_t *load_background_image(const char *path) {
142 	cairo_surface_t *image;
143 #if HAVE_GDK_PIXBUF
144 	GError *err = NULL;
145 	GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err);
146 	if (!pixbuf) {
147 		swaylock_log(LOG_ERROR, "Failed to load background image (%s).",
148 				err->message);
149 		return NULL;
150 	}
151 	image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
152 	g_object_unref(pixbuf);
153 #else
154 	image = cairo_image_surface_create_from_png(path);
155 #endif // HAVE_GDK_PIXBUF
156 	if (!image) {
157 		swaylock_log(LOG_ERROR, "Failed to read background image.");
158 		return NULL;
159 	}
160 	if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
161 		swaylock_log(LOG_ERROR, "Failed to read background image: %s."
162 #if !HAVE_GDK_PIXBUF
163 				"\nSway was compiled without gdk_pixbuf support, so only"
164 				"\nPNG images can be loaded. This is the likely cause."
165 #endif // !HAVE_GDK_PIXBUF
166 				, cairo_status_to_string(cairo_surface_status(image)));
167 		return NULL;
168 	}
169 	return image;
170 }
171 
render_background_image(cairo_t * cairo,cairo_surface_t * image,enum background_mode mode,int buffer_width,int buffer_height)172 void render_background_image(cairo_t *cairo, cairo_surface_t *image,
173 		enum background_mode mode, int buffer_width, int buffer_height) {
174 	double width = cairo_image_surface_get_width(image);
175 	double height = cairo_image_surface_get_height(image);
176 
177 	cairo_save(cairo);
178 	switch (mode) {
179 	case BACKGROUND_MODE_STRETCH:
180 		cairo_scale(cairo,
181 				(double)buffer_width / width,
182 				(double)buffer_height / height);
183 		cairo_set_source_surface(cairo, image, 0, 0);
184 		break;
185 	case BACKGROUND_MODE_FILL: {
186 		double window_ratio = (double)buffer_width / buffer_height;
187 		double bg_ratio = width / height;
188 
189 		if (window_ratio > bg_ratio) {
190 			double scale = (double)buffer_width / width;
191 			cairo_scale(cairo, scale, scale);
192 			cairo_set_source_surface(cairo, image,
193 					0, (double)buffer_height / 2 / scale - height / 2);
194 		} else {
195 			double scale = (double)buffer_height / height;
196 			cairo_scale(cairo, scale, scale);
197 			cairo_set_source_surface(cairo, image,
198 					(double)buffer_width / 2 / scale - width / 2, 0);
199 		}
200 		break;
201 	}
202 	case BACKGROUND_MODE_FIT: {
203 		double window_ratio = (double)buffer_width / buffer_height;
204 		double bg_ratio = width / height;
205 
206 		if (window_ratio > bg_ratio) {
207 			double scale = (double)buffer_height / height;
208 			cairo_scale(cairo, scale, scale);
209 			cairo_set_source_surface(cairo, image,
210 					(double)buffer_width / 2 / scale - width / 2, 0);
211 		} else {
212 			double scale = (double)buffer_width / width;
213 			cairo_scale(cairo, scale, scale);
214 			cairo_set_source_surface(cairo, image,
215 					0, (double)buffer_height / 2 / scale - height / 2);
216 		}
217 		break;
218 	}
219 	case BACKGROUND_MODE_CENTER:
220 		cairo_set_source_surface(cairo, image,
221 				(double)buffer_width / 2 - width / 2,
222 				(double)buffer_height / 2 - height / 2);
223 		break;
224 	case BACKGROUND_MODE_TILE: {
225 		cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image);
226 		cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
227 		cairo_set_source(cairo, pattern);
228 		break;
229 	}
230 	case BACKGROUND_MODE_SOLID_COLOR:
231 	case BACKGROUND_MODE_INVALID:
232 		assert(0);
233 		break;
234 	}
235 	cairo_paint(cairo);
236 	cairo_restore(cairo);
237 }
238