1#define _DEFAULT_SOURCE
2#include <assert.h>
3#include "background-image.h"
4#include "cairo.h"
5#include "log.h"
6#include "swaylock.h"
7
8#ifdef __FreeBSD__
9# include <sys/endian.h>
10#else
11# include <endian.h>
12#endif
13
14// Cairo RGB24 uses 32 bits per pixel, as XRGB, in native endianness.
15// xrgb32_le uses 32 bits per pixel, as XRGB, little endian (BGRX big endian).
16void cairo_rgb24_from_xrgb32_le(unsigned char *buf, int width, int height, int stride) {
17 for (int y = 0; y < height; ++y) {
18 for (int x = 0; x < width; ++x) {
19 unsigned char *pix = buf + y * stride + x * 4;
20 *(uint32_t *)pix = 0 |
21 (uint32_t)pix[2] << 16 |
22 (uint32_t)pix[1] << 8 |
23 (uint32_t)pix[0];
24 }
25 }
26}
27
28// Cairo RGB24 uses 32 bits per pixel, as XRGB, in native endianness.
29// xbgr32_le uses 32 bits per pixel, as XBGR, little endian (RGBX big endian).
30void cairo_rgb24_from_xbgr32_le(unsigned char *buf, int width, int height, int stride) {
31 for (int y = 0; y < height; ++y) {
32 for (int x = 0; x < width; ++x) {
33 unsigned char *pix = buf + y * stride + x * 4;
34 *(uint32_t *)pix = 0 |
35 (uint32_t)pix[0] << 16 |
36 (uint32_t)pix[1] << 8 |
37 (uint32_t)pix[2];
38 }
39 }
40}
41
42void cairo_rgb24_from_xrgb2101010_le(unsigned char *buf, int width, int height, int stride) {
43 for (int y = 0; y < height; ++y) {
44 for (int x = 0; x < width; ++x) {
45 uint32_t *pix = (uint32_t *) (buf + y * stride + x * 4);
46 uint32_t color = le32toh(*pix);
47 *pix = 0 |
48 ((color >> 22) & 0xFF) << 16 |
49 ((color >> 12) & 0xFF) << 8 |
50 ((color >> 2) & 0xFF);
51 }
52 }
53}
54
55void cairo_rgb24_from_xbgr2101010_le(unsigned char *buf, int width, int height, int stride) {
56 for (int y = 0; y < height; ++y) {
57 for (int x = 0; x < width; ++x) {
58 uint32_t *pix = (uint32_t *) (buf + y * stride + x * 4);
59 uint32_t color = le32toh(*pix);
60 *pix = 0 |
61 ((color >> 2) & 0xFF) << 16 |
62 ((color >> 12) & 0xFF) << 8 |
63 ((color >> 22) & 0xFF);
64 }
65 }
66}
67
68void cairo_rgb24_from_rgbx1010102_le(unsigned char *buf, int width, int height, int stride) {
69 for (int y = 0; y < height; ++y) {
70 for (int x = 0; x < width; ++x) {
71 uint32_t *pix = (uint32_t *) (buf + y * stride + x * 4);
72 uint32_t color = le32toh(*pix);
73 *pix = 0 |
74 ((color >> 24) & 0xFF) << 16 |
75 ((color >> 14) & 0xFF) << 8 |
76 ((color >> 4) & 0xFF);
77 }
78 }
79}
80
81void cairo_rgb24_from_bgrx1010102_le(unsigned char *buf, int width, int height, int stride) {
82 for (int y = 0; y < height; ++y) {
83 for (int x = 0; x < width; ++x) {
84 uint32_t *pix = (uint32_t *) (buf + y * stride + x * 4);
85 uint32_t color = le32toh(*pix);
86 *pix = 0 |
87 ((color >> 4) & 0xFF) << 16 |
88 ((color >> 14) & 0xFF) << 8 |
89 ((color >> 24) & 0xFF);
90 }
91 }
92}
93
94// Cairo RGB24 uses 32 bits per pixel, as XRGB, in native endianness.
95// BGR888 uses 24 bits per pixel, as BGR, little endian.
96// 24-bit BGR format, [23:0] B:G:R little endian (From wayland-client-protocol.h)
97void cairo_rgb24_from_bgr888_le(unsigned char *buf, int width, int height, int stride) {
98 for (int y = 0; y < height; ++y) {
99 // Row from back to front to avoid overwriting data.
100 for (int x = width-1; x >= 0; --x) {
101 // 24 bits = 3 bytes, 32 bits = 4 bytes
102 unsigned char *srcpix = buf + y * stride + x * 3;
103 unsigned char *dstpix = buf + y * stride + x * 4;
104
105 *(uint32_t *)dstpix = 0 |
106 (uint32_t)srcpix[0] << 16 |
107 (uint32_t)srcpix[1] << 8 |
108 (uint32_t)srcpix[2];
109 }
110 }
111}
112
113// Swap red and blue values in Cairo RGB24
114void cairo_rgb24_swap_rb(unsigned char *buf, int width, int height, int stride) {
115 for (int y = 0; y < height; ++y) {
116 for (int x = 0; x < width; ++x) {
117 unsigned char *pix = buf + y * stride + x * 4;
118
119 *(uint32_t *)pix = 0 |
120 (uint32_t)pix[0] << 16 |
121 (uint32_t)pix[1] << 8 |
122 (uint32_t)pix[2];
123 }
124 }
125}
126
127enum background_mode parse_background_mode(const char *mode) {
128 if (strcmp(mode, "stretch") == 0) {
129 return BACKGROUND_MODE_STRETCH;
130 } else if (strcmp(mode, "fill") == 0) {
131 return BACKGROUND_MODE_FILL;
132 } else if (strcmp(mode, "fit") == 0) {
133 return BACKGROUND_MODE_FIT;
134 } else if (strcmp(mode, "center") == 0) {
135 return BACKGROUND_MODE_CENTER;
136 } else if (strcmp(mode, "tile") == 0) {
137 return BACKGROUND_MODE_TILE;
138 } else if (strcmp(mode, "solid_color") == 0) {
139 return BACKGROUND_MODE_SOLID_COLOR;
140 }
141 swaylock_log(LOG_ERROR, "Unsupported background mode: %s", mode);
142 return BACKGROUND_MODE_INVALID;
143}
144
145cairo_surface_t *load_background_from_buffer(void *buf, uint32_t format,
146 uint32_t width, uint32_t height, uint32_t stride, enum wl_output_transform transform) {
147 bool rotated =
148 transform == WL_OUTPUT_TRANSFORM_90 ||
149 transform == WL_OUTPUT_TRANSFORM_270 ||
150 transform == WL_OUTPUT_TRANSFORM_FLIPPED_90 ||
151 transform == WL_OUTPUT_TRANSFORM_FLIPPED_270;
152
153 cairo_surface_t *image;
154 if (rotated) {
155 image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, height, width);
156 } else {
157 image = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);
158 }
159 if (image == NULL) {
160 swaylock_log(LOG_ERROR, "Failed to create image..");
161 return NULL;
162 }
163
164 unsigned char *destbuf = cairo_image_surface_get_data(image);
165 size_t destwidth = cairo_image_surface_get_width(image);
166 size_t destheight = cairo_image_surface_get_height(image);
167 size_t deststride = cairo_image_surface_get_stride(image);
168 unsigned char *srcbuf = buf;
169 size_t srcstride = stride;
170 size_t minstride = srcstride < deststride ? srcstride : deststride;
171
172 // Lots of these are mostly-copy-and-pasted, with a lot of boilerplate
173 // for each case.
174 // The only interesting differencess between a lot of these cases are
175 // the definitions of srcx and srcy.
176 // I don't think it's worth adding a macro to make this "cleaner" though,
177 // as that would obfuscate what's actually going on.
178 switch (transform) {
179 case WL_OUTPUT_TRANSFORM_NORMAL:
180 // In most cases, the transform is probably normal. Luckily, it can be
181 // done with just one big memcpy.
182 if (srcstride == deststride) {
183 memcpy(destbuf, srcbuf, destheight * deststride);
184 } else {
185 for (size_t y = 0; y < destheight; ++y) {
186 memcpy(destbuf + y * deststride, srcbuf + y * srcstride, minstride);
187 }
188 }
189 break;
190 case WL_OUTPUT_TRANSFORM_90:
191 for (size_t desty = 0; desty < destheight; ++desty) {
192 size_t srcx = desty;
193 for (size_t destx = 0; destx < destwidth; ++destx) {
194 size_t srcy = destwidth - destx - 1;
195 *((uint32_t *)(destbuf + desty * deststride) + destx) =
196 *((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
197 }
198 }
199 break;
200 case WL_OUTPUT_TRANSFORM_180:
201 for (size_t desty = 0; desty < destheight; ++desty) {
202 size_t srcy = destheight - desty - 1;
203 for (size_t destx = 0; destx < destwidth; ++destx) {
204 size_t srcx = destwidth - destx - 1;
205 *((uint32_t *)(destbuf + desty * deststride) + destx) =
206 *((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
207 }
208 }
209 break;
210 case WL_OUTPUT_TRANSFORM_270:
211 for (size_t desty = 0; desty < destheight; ++desty) {
212 size_t srcx = destheight - desty - 1;
213 for (size_t destx = 0; destx < destwidth; ++destx) {
214 size_t srcy = destx;
215 *((uint32_t *)(destbuf + desty * deststride) + destx) =
216 *((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
217 }
218 }
219 break;
220 case WL_OUTPUT_TRANSFORM_FLIPPED:
221 for (size_t desty = 0; desty < destheight; ++desty) {
222 size_t srcy = desty;
223 for (size_t destx = 0; destx < destwidth; ++destx) {
224 size_t srcx = destwidth - destx - 1;
225 *((uint32_t *)(destbuf + desty * deststride) + destx) =
226 *((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
227 }
228 }
229 break;
230 case WL_OUTPUT_TRANSFORM_FLIPPED_90:
231 for (size_t desty = 0; desty < destheight; ++desty) {
232 size_t srcx = desty;
233 for (size_t destx = 0; destx < destwidth; ++destx) {
234 size_t srcy = destx;
235 *((uint32_t *)(destbuf + desty * deststride) + destx) =
236 *((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
237 }
238 }
239 break;
240 case WL_OUTPUT_TRANSFORM_FLIPPED_180:
241 for (size_t desty = 0; desty < destheight; ++desty) {
242 size_t srcy = destheight - desty - 1;
243 memcpy(destbuf + desty * deststride, srcbuf + srcy * srcstride, minstride);
244 }
245 break;
246 case WL_OUTPUT_TRANSFORM_FLIPPED_270:
247 for (size_t desty = 0; desty < destheight; ++desty) {
248 size_t srcx = destheight - desty - 1;
249 for (size_t destx = 0; destx < destwidth; ++destx) {
250 size_t srcy = destwidth - destx - 1;
251 *((uint32_t *)(destbuf + desty * deststride) + destx) =
252 *((uint32_t *)(srcbuf + srcy * srcstride) + srcx);
253 }
254 }
255 break;
256 }
257
258 switch (format) {
259 case WL_SHM_FORMAT_XBGR8888:
260 case WL_SHM_FORMAT_ABGR8888:
261 cairo_rgb24_from_xbgr32_le(
262 cairo_image_surface_get_data(image),
263 cairo_image_surface_get_width(image),
264 cairo_image_surface_get_height(image),
265 cairo_image_surface_get_stride(image));
266 break;
267 case WL_SHM_FORMAT_XRGB2101010:
268 case WL_SHM_FORMAT_ARGB2101010:
269 cairo_rgb24_from_xrgb2101010_le(
270 cairo_image_surface_get_data(image),
271 cairo_image_surface_get_width(image),
272 cairo_image_surface_get_height(image),
273 cairo_image_surface_get_stride(image));
274 break;
275 case WL_SHM_FORMAT_XBGR2101010:
276 case WL_SHM_FORMAT_ABGR2101010:
277 cairo_rgb24_from_xbgr2101010_le(
278 cairo_image_surface_get_data(image),
279 cairo_image_surface_get_width(image),
280 cairo_image_surface_get_height(image),
281 cairo_image_surface_get_stride(image));
282 break;
283 case WL_SHM_FORMAT_RGBX1010102:
284 case WL_SHM_FORMAT_RGBA1010102:
285 cairo_rgb24_from_rgbx1010102_le(
286 cairo_image_surface_get_data(image),
287 cairo_image_surface_get_width(image),
288 cairo_image_surface_get_height(image),
289 cairo_image_surface_get_stride(image));
290 break;
291 case WL_SHM_FORMAT_BGRX1010102:
292 case WL_SHM_FORMAT_BGRA1010102:
293 cairo_rgb24_from_bgrx1010102_le(
294 cairo_image_surface_get_data(image),
295 cairo_image_surface_get_width(image),
296 cairo_image_surface_get_height(image),
297 cairo_image_surface_get_stride(image));
298 break;
299 case WL_SHM_FORMAT_BGR888:
300 case WL_SHM_FORMAT_RGB888:
301 cairo_rgb24_from_bgr888_le(
302 cairo_image_surface_get_data(image),
303 cairo_image_surface_get_width(image),
304 cairo_image_surface_get_height(image),
305 cairo_image_surface_get_stride(image)
306 );
307 if (format == WL_SHM_FORMAT_RGB888) {
308 cairo_rgb24_swap_rb(
309 cairo_image_surface_get_data(image),
310 cairo_image_surface_get_width(image),
311 cairo_image_surface_get_height(image),
312 cairo_image_surface_get_stride(image)
313 );
314 }
315 break;
316 default:
317 swaylock_log(LOG_ERROR,
318 "Unknown pixel format: %u. Assuming XRGB32. Colors may look wrong.",
319 format);
320 // fallthrough
321 case WL_SHM_FORMAT_XRGB8888:
322 case WL_SHM_FORMAT_ARGB8888: {
323 // If we're little endian, we don't have to do anything
324 int test = 1;
325 bool is_little_endian = *(char *)&test == 1;
326 if (!is_little_endian) {
327 cairo_rgb24_from_xrgb32_le(
328 cairo_image_surface_get_data(image),
329 cairo_image_surface_get_width(image),
330 cairo_image_surface_get_height(image),
331 cairo_image_surface_get_stride(image));
332 }
333 }
334 }
335
336 return image;
337}
338
339cairo_surface_t *load_background_image(const char *path) {
340 cairo_surface_t *image;
341#if HAVE_GDK_PIXBUF
342 GError *err = NULL;
343 GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(path, &err);
344 if (!pixbuf) {
345 swaylock_log(LOG_ERROR, "Failed to load background image (%s).",
346 err->message);
347 return NULL;
348 }
349 image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
350 g_object_unref(pixbuf);
351#else
352 image = cairo_image_surface_create_from_png(path);
353#endif // HAVE_GDK_PIXBUF
354 if (!image) {
355 swaylock_log(LOG_ERROR, "Failed to read background image.");
356 return NULL;
357 }
358 if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS) {
359 swaylock_log(LOG_ERROR, "Failed to read background image: %s."
360#if !HAVE_GDK_PIXBUF
361 "\nSway was compiled without gdk_pixbuf support, so only"
362 "\nPNG images can be loaded. This is the likely cause."
363#endif // !HAVE_GDK_PIXBUF
364 , cairo_status_to_string(cairo_surface_status(image)));
365 return NULL;
366 }
367 return image;
368}
369
370cairo_surface_t *scale_background_image(cairo_surface_t *image,
371 enum background_mode mode, int buffer_width, int buffer_height) {
372 cairo_surface_t *target = cairo_image_surface_create(CAIRO_FORMAT_RGB24, buffer_width, buffer_height);
373 cairo_t *cairo = cairo_create(target);
374 double width = cairo_image_surface_get_width(image);
375 double height = cairo_image_surface_get_height(image);
376
377 switch (mode) {
378 case BACKGROUND_MODE_STRETCH:
379 cairo_scale(cairo,
380 (double)buffer_width / width,
381 (double)buffer_height / height);
382 cairo_set_source_surface(cairo, image, 0, 0);
383 break;
384 case BACKGROUND_MODE_FILL: {
385 double window_ratio = (double)buffer_width / buffer_height;
386 double bg_ratio = width / height;
387
388 if (window_ratio > bg_ratio) {
389 double scale = (double)buffer_width / width;
390 cairo_scale(cairo, scale, scale);
391 cairo_set_source_surface(cairo, image,
392 0, (double)buffer_height / 2 / scale - height / 2);
393 } else {
394 double scale = (double)buffer_height / height;
395 cairo_scale(cairo, scale, scale);
396 cairo_set_source_surface(cairo, image,
397 (double)buffer_width / 2 / scale - width / 2, 0);
398 }
399 break;
400 }
401 case BACKGROUND_MODE_FIT: {
402 double window_ratio = (double)buffer_width / buffer_height;
403 double bg_ratio = width / height;
404
405 if (window_ratio > bg_ratio) {
406 double scale = (double)buffer_height / height;
407 cairo_scale(cairo, scale, scale);
408 cairo_set_source_surface(cairo, image,
409 (double)buffer_width / 2 / scale - width / 2, 0);
410 } else {
411 double scale = (double)buffer_width / width;
412 cairo_scale(cairo, scale, scale);
413 cairo_set_source_surface(cairo, image,
414 0, (double)buffer_height / 2 / scale - height / 2);
415 }
416 break;
417 }
418 case BACKGROUND_MODE_CENTER:
419 /*
420 * Align the unscaled image to integer pixel boundaries
421 * in order to prevent loss of clarity (this only matters
422 * for odd-sized images).
423 */
424 cairo_set_source_surface(cairo, image,
425 (int)((double)buffer_width / 2 - width / 2),
426 (int)((double)buffer_height / 2 - height / 2));
427 break;
428 case BACKGROUND_MODE_TILE: {
429 cairo_pattern_t *pattern = cairo_pattern_create_for_surface(image);
430 cairo_pattern_set_extend(pattern, CAIRO_EXTEND_REPEAT);
431 cairo_set_source(cairo, pattern);
432 break;
433 }
434 case BACKGROUND_MODE_SOLID_COLOR:
435 case BACKGROUND_MODE_INVALID:
436 assert(0);
437 break;
438 }
439
440 cairo_pattern_set_filter(cairo_get_source(cairo), CAIRO_FILTER_BILINEAR);
441 cairo_paint(cairo);
442 cairo_destroy(cairo);
443 return target;
444}
445
446void render_background_image(cairo_t *cairo, cairo_surface_t *image, double alpha) {
447 cairo_save(cairo);
448 cairo_set_source_surface(cairo, image, 0, 0);
449 cairo_paint_with_alpha(cairo, alpha);
450 cairo_restore(cairo);
451}