main
44ceea7 ยท 1 month ago 7 commits
  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}