main
44ceea7 ยท 1 month ago 7 commits
  1#define _POSIX_C_SOURCE 200809
  2#define _XOPEN_SOURCE 700
  3#include <omp.h>
  4#include <limits.h>
  5#include <stdlib.h>
  6#include <stdbool.h>
  7#include <dlfcn.h>
  8#include <string.h>
  9#include <errno.h>
 10#include <sys/wait.h>
 11#include <sys/stat.h>
 12#include <sys/types.h>
 13#include <unistd.h>
 14#include <spawn.h>
 15#include <time.h>
 16#include <stdio.h>
 17#include "effects.h"
 18#include "log.h"
 19
 20// glib might or might not have already defined MIN,
 21// depending on whether we have pixbuf or not...
 22#ifndef MIN
 23#define MIN(a, b) ((a) < (b) ? (a) : (b))
 24#endif
 25
 26extern char **environ;
 27
 28static int screen_size_to_pix(struct swaylock_effect_screen_pos size, int screensize, int scale) {
 29	if (size.is_percent) {
 30		return (size.pos / 100.0) * screensize;
 31	} else if (size.pos > 0) {
 32		return size.pos * scale;
 33	} else {
 34		return size.pos;
 35	}
 36}
 37
 38static int screen_pos_to_pix(struct swaylock_effect_screen_pos pos, int screensize, int scale) {
 39	int actual;
 40	if (pos.is_percent) {
 41		actual = (pos.pos / 100.0) * screensize;
 42	} else {
 43		actual = pos.pos * scale;
 44	}
 45
 46	if (actual < 0) {
 47		actual = screensize + actual;
 48	}
 49
 50	return actual;
 51}
 52
 53static const char *effect_name(struct swaylock_effect *effect) {
 54	switch (effect->tag) {
 55	case EFFECT_BLUR: return "blur";
 56	case EFFECT_PIXELATE: return "pixelate";
 57	case EFFECT_SCALE: return "scale";
 58	case EFFECT_GREYSCALE: return "greyscale";
 59	case EFFECT_VIGNETTE: return "vignette";
 60	case EFFECT_COMPOSE: return "compose";
 61	case EFFECT_CUSTOM: return effect->e.custom;
 62	}
 63
 64	abort();
 65}
 66
 67static void screen_pos_pair_to_pix(
 68		struct swaylock_effect_screen_pos posx,
 69		struct swaylock_effect_screen_pos posy,
 70		int objwidth, int objheight,
 71		int screenwidth, int screenheight, int scale, int gravity,
 72		int *outx, int *outy) {
 73	int x = screen_pos_to_pix(posx, screenwidth, scale);
 74	int y = screen_pos_to_pix(posy, screenheight, scale);
 75
 76	// Adjust X
 77	switch (gravity) {
 78	case EFFECT_COMPOSE_GRAV_CENTER:
 79	case EFFECT_COMPOSE_GRAV_N:
 80	case EFFECT_COMPOSE_GRAV_S:
 81		x -= objwidth / 2;
 82		break;
 83	case EFFECT_COMPOSE_GRAV_NW:
 84	case EFFECT_COMPOSE_GRAV_SW:
 85	case EFFECT_COMPOSE_GRAV_W:
 86		break;
 87	case EFFECT_COMPOSE_GRAV_NE:
 88	case EFFECT_COMPOSE_GRAV_SE:
 89	case EFFECT_COMPOSE_GRAV_E:
 90		x -= objwidth;
 91		break;
 92	}
 93
 94	// Adjust Y
 95	switch (gravity) {
 96	case EFFECT_COMPOSE_GRAV_CENTER:
 97	case EFFECT_COMPOSE_GRAV_W:
 98	case EFFECT_COMPOSE_GRAV_E:
 99		y -= objheight / 2;
100		break;
101	case EFFECT_COMPOSE_GRAV_NW:
102	case EFFECT_COMPOSE_GRAV_NE:
103	case EFFECT_COMPOSE_GRAV_N:
104		break;
105	case EFFECT_COMPOSE_GRAV_SW:
106	case EFFECT_COMPOSE_GRAV_SE:
107	case EFFECT_COMPOSE_GRAV_S:
108		y -= objheight;
109		break;
110	}
111
112	*outx = x;
113	*outy = y;
114}
115
116static uint32_t blend_pixels(float alpha, uint32_t srcpix, uint32_t destpix) {
117	uint8_t srcr = (srcpix & 0x00ff0000) >> 16;
118	uint8_t destr = (destpix & 0x00ff0000) >> 16;
119	uint8_t srcg = (srcpix & 0x0000ff00) >> 8;
120	uint8_t destg = (destpix & 0x0000ff00) >> 8;
121	uint8_t srcb = (srcpix & 0x000000ff) >> 0;
122	uint8_t destb = (destpix & 0x000000ff) >> 0;
123
124	return (uint32_t)0 |
125		(uint32_t)255 << 24 |
126		(uint32_t)(srcr + destr * (1 - alpha)) << 16 |
127		(uint32_t)(srcg + destg * (1 - alpha)) << 8 |
128		(uint32_t)(srcb + destb * (1 - alpha)) << 0;
129}
130
131static void blur_h(uint32_t *dest, uint32_t *src, int width, int height,
132		int radius) {
133	const int minradius = radius < width ? radius : width;
134
135#pragma omp parallel for
136	for (int y = 0; y < height; ++y) {
137		uint32_t *srow = src + y * width;
138		uint32_t *drow = dest + y * width;
139
140		// 'range' is float, because floating point division is usually faster
141		// than integer division.
142		int r_acc = 0;
143		int g_acc = 0;
144		int b_acc = 0;
145		float range = minradius;
146
147		// Accumulate the range (0..radius)
148		for (int x = 0; x < minradius; ++x) {
149			r_acc += (srow[x] & 0xff0000) >> 16;
150			g_acc += (srow[x] & 0x00ff00) >> 8;
151			b_acc += (srow[x] & 0x0000ff);
152		}
153
154		// Deal with the main body
155		for (int x = 0; x < width; ++x) {
156			if (x >= minradius) {
157				r_acc -= (srow[x - radius] & 0xff0000) >> 16;
158				g_acc -= (srow[x - radius] & 0x00ff00) >> 8;
159				b_acc -= (srow[x - radius] & 0x0000ff);
160				range -= 1;
161			}
162
163			if (x < width - minradius) {
164				r_acc += (srow[x + radius] & 0xff0000) >> 16;
165				g_acc += (srow[x + radius] & 0x00ff00) >> 8;
166				b_acc += (srow[x + radius] & 0x0000ff);
167				range += 1;
168			}
169
170			drow[x] = 0 |
171				(int)(r_acc / range) << 16 |
172				(int)(g_acc / range) << 8 |
173				(int)(b_acc / range);
174		}
175	}
176}
177
178static void blur_v(uint32_t *dest, uint32_t *src, int width, int height,
179		int radius) {
180	const int minradius = radius < height ? radius : height;
181
182#pragma omp parallel for
183	for (int x = 0; x < width; ++x) {
184		uint32_t *scol = src + x;
185		uint32_t *dcol = dest + x;
186
187		// 'range' is float, because floating point division is usually faster
188		// than integer division.
189		int r_acc = 0;
190		int g_acc = 0;
191		int b_acc = 0;
192		float range = minradius;
193
194		// Accumulate the range (0..radius)
195		for (int y = 0; y < minradius; ++y) {
196			r_acc += (scol[y * width] & 0xff0000) >> 16;
197			g_acc += (scol[y * width] & 0x00ff00) >> 8;
198			b_acc += (scol[y * width] & 0x0000ff);
199		}
200
201		// Deal with the main body
202		for (int y = 0; y < height; ++y) {
203			if (y >= minradius) {
204				r_acc -= (scol[(y - radius) * width] & 0xff0000) >> 16;
205				g_acc -= (scol[(y - radius) * width] & 0x00ff00) >> 8;
206				b_acc -= (scol[(y - radius) * width] & 0x0000ff);
207				range -= 1;
208			}
209
210			if (y < height - minradius) {
211				r_acc += (scol[(y + radius) * width] & 0xff0000) >> 16;
212				g_acc += (scol[(y + radius) * width] & 0x00ff00) >> 8;
213				b_acc += (scol[(y + radius) * width] & 0x0000ff);
214				range += 1;
215			}
216
217			dcol[y * width] = 0 |
218				(int)(r_acc / range) << 16 |
219				(int)(g_acc / range) << 8 |
220				(int)(b_acc / range);
221		}
222	}
223}
224
225static void blur_once(uint32_t *dest, uint32_t *src, uint32_t *scratch,
226		int width, int height, int radius) {
227	blur_h(scratch, src, width, height, radius);
228	blur_v(dest, scratch, width, height, radius);
229}
230
231// This effect_blur function, and the associated blur_* functions,
232// are my own adaptations of code in yvbbrjdr's i3lock-fancy-rapid:
233// https://github.com/yvbbrjdr/i3lock-fancy-rapid
234static void effect_blur(uint32_t *dest, uint32_t *src, int width, int height, int scale,
235		int radius, int times) {
236	uint32_t *origdest = dest;
237
238	uint32_t *scratch = malloc(width * height * sizeof(*scratch));
239	blur_once(dest, src, scratch, width, height, radius * scale);
240	for (int i = 0; i < times - 1; ++i) {
241		uint32_t *tmp = src;
242		src = dest;
243		dest = tmp;
244		blur_once(dest, src, scratch, width, height, radius * scale);
245	}
246	free(scratch);
247
248	// We're flipping between using dest and src;
249	// if the last buffer we used was src, copy that over to dest.
250	if (dest != origdest)
251		memcpy(origdest, dest, width * height * sizeof(*dest));
252}
253
254static void effect_pixelate(uint32_t *data, int width, int height, int scale, int factor) {
255	factor *= scale;
256#pragma omp parallel for
257	for (int y = 0; y < height / factor + 1; ++y) {
258		for (int x = 0; x < width / factor + 1; ++x) {
259			int total_r = 0, total_g = 0, total_b = 0;
260
261			int xstart = x * factor;
262			int ystart = y * factor;
263			int xlim = MIN(xstart + factor, width);
264			int ylim = MIN(ystart + factor, height);
265
266			// Average
267			for (int ry = ystart; ry < ylim; ++ry) {
268				for (int rx = xstart; rx < xlim; ++rx) {
269					int index = ry * width + rx;
270					total_r += (data[index] & 0xff0000) >> 16;
271					total_g += (data[index] & 0x00ff00) >> 8;
272					total_b += (data[index] & 0x0000ff);
273				}
274			}
275
276			int r = total_r / (factor * factor);
277			int g = total_g / (factor * factor);
278			int b = total_b / (factor * factor);
279
280			// Fill pixels
281			for (int ry = ystart; ry < ylim; ++ry) {
282				for (int rx = xstart; rx < xlim; ++rx) {
283					int index = ry * width + rx;
284					data[index] = r << 16 | g << 8 | b;
285				}
286			}
287		}
288	}
289}
290
291static void effect_scale(uint32_t *dest, uint32_t *src, int swidth, int sheight,
292		double scale) {
293	int dwidth = swidth * scale;
294	int dheight = sheight * scale;
295	double fact = 1.0 / scale;
296
297#pragma omp parallel for
298	for (int dy = 0; dy < dheight; ++dy) {
299		int sy = dy * fact;
300		if (sy >= sheight) continue;
301		for (int dx = 0; dx < dwidth; ++dx) {
302			int sx = dx * fact;
303			if (sx >= swidth) continue;
304			dest[dy * dwidth + dx] = src[sy * swidth + sx];
305		}
306	}
307}
308
309static void effect_greyscale(uint32_t *data, int width, int height) {
310#pragma omp parallel for
311	for (int y = 0; y < height; ++y) {
312		for (int x = 0; x < width; ++x) {
313			int index = y * width + x;
314			int r = (data[index] & 0xff0000) >> 16;
315			int g = (data[index] & 0x00ff00) >> 8;
316			int b = (data[index] & 0x0000ff);
317			int luma = 0.2989 * r + 0.5870 * g + 0.1140 * b;
318			if (luma < 0) luma = 0;
319			if (luma > 255) luma = 255;
320			luma &= 0xFF;
321			data[index] = luma << 16 | luma << 8 | luma;
322		}
323	}
324}
325
326static void effect_vignette(uint32_t *data, int width, int height,
327		double base, double factor) {
328	base = fmin(1, fmax(0, base));
329	factor = fmin(1 - base, fmax(0, factor));
330#pragma omp parallel for
331	for (int y = 0; y < height; ++y) {
332		for (int x = 0; x < width; ++x) {
333
334			double xf = (x * 1.0) / width;
335			double yf = (y * 1.0) / height;
336			double vignette_factor = base + factor
337				* 16 * xf * yf * (1.0 - xf) * (1.0 - yf);
338
339			int index = y * width + x;
340			int r = (data[index] & 0xff0000) >> 16;
341			int g = (data[index] & 0x00ff00) >> 8;
342			int b = (data[index] & 0x0000ff);
343
344			r = (int)(r * vignette_factor) & 0xFF;
345			g = (int)(g * vignette_factor) & 0xFF;
346			b = (int)(b * vignette_factor) & 0xFF;
347
348			data[index] = r << 16 | g << 8 | b;
349		}
350	}
351}
352
353static void effect_compose(uint32_t *data, int width, int height, int scale,
354		struct swaylock_effect_screen_pos posx,
355		struct swaylock_effect_screen_pos posy,
356		struct swaylock_effect_screen_pos posw,
357		struct swaylock_effect_screen_pos posh,
358		int gravity, char *imgpath) {
359#if !HAVE_GDK_PIXBUF
360	(void)&blend_pixels;
361	(void)&screen_size_to_pix;
362	(void)&screen_pos_pair_to_pix;
363	swaylock_log(LOG_ERROR, "Compose effect: Compiled without gdk_pixbuf support.\n");
364	return;
365#else
366	int imgw = screen_size_to_pix(posw, width, scale);
367	int imgh = screen_size_to_pix(posh, height, scale);
368	bool preserve_aspect = imgw < 0 || imgh < 0;
369
370	GError *err = NULL;
371	GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file_at_scale(
372			imgpath, imgw, imgh, preserve_aspect, &err);
373	if (!pixbuf) {
374		swaylock_log(LOG_ERROR, "Compose effect: Failed to load image file '%s' (%s).",
375				imgpath, err->message);
376		g_error_free(err);
377		return;
378	}
379
380	cairo_surface_t *image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf);
381	g_object_unref(pixbuf);
382
383	int bufw = cairo_image_surface_get_width(image);
384	int bufh = cairo_image_surface_get_height(image);
385	uint32_t *bufdata = (uint32_t *)cairo_image_surface_get_data(image);
386	int bufstride = cairo_image_surface_get_stride(image) / 4;
387	bool bufalpha = cairo_image_surface_get_format(image) == CAIRO_FORMAT_ARGB32;
388
389	int imgx, imgy;
390	screen_pos_pair_to_pix(
391			posx, posy, bufw, bufh,
392			width, height, scale, gravity,
393			&imgx, &imgy);
394
395#pragma omp parallel for
396	for (int offy = 0; offy < bufh; ++offy) {
397		if (offy + imgy < 0 || offy + imgy > height)
398			continue;
399
400		for (int offx = 0; offx < bufw; ++offx) {
401			if (offx + imgx < 0 || offx + imgx > width)
402				continue;
403
404			size_t idx = (size_t)(offy + imgy) * width + (offx + imgx);
405			size_t bufidx = (size_t)offy * bufstride + (offx);
406
407			if (!bufalpha) {
408				data[idx] = bufdata[bufidx];
409			} else {
410				uint8_t alpha = (bufdata[bufidx] & 0xff000000) >> 24;
411				if (alpha == 255) {
412					data[idx] = bufdata[bufidx];
413				} else if (alpha != 0) {
414					data[idx] = blend_pixels(alpha / 255.0, bufdata[bufidx], data[idx]);
415				}
416			}
417		}
418	}
419
420	cairo_surface_destroy(image);
421#endif
422}
423
424static void effect_custom_run(uint32_t *data, int width, int height, int scale,
425		char *path) {
426	void *dl = dlopen(path, RTLD_LAZY);
427	if (dl == NULL) {
428		swaylock_log(LOG_ERROR, "Custom effect: %s", dlerror());
429		return;
430	}
431
432	void (*effect_func)(uint32_t *data, int width, int height, int scale) =
433		dlsym(dl, "swaylock_effect");
434	if (effect_func != NULL) {
435		effect_func(data, width, height, scale);
436		dlclose(dl);
437		return;
438	}
439
440	uint32_t (*pixel_func)(uint32_t pix, int x, int y, int width, int height) =
441		dlsym(dl, "swaylock_pixel");
442	if (pixel_func != NULL) {
443#pragma omp parallel for
444		for (int y = 0; y < height; ++y) {
445			for (int x = 0; x < width; ++x) {
446				data[y * width + x] =
447					pixel_func(data[y * width + x], x, y, width, height);
448			}
449		}
450
451		dlclose(dl);
452		return;
453	}
454
455	(void)dlsym(dl, "swaylock_effect"); // Change the result of dlerror()
456	swaylock_log(LOG_ERROR, "Custom effect: %s", dlerror());
457}
458
459static bool file_is_outdated(const char *input, const char *output) {
460	struct stat instat, outstat;
461	if (stat(input, &instat) < 0) {
462		return true;
463	}
464
465	if (stat(output, &outstat) < 0) {
466		return true;
467	}
468
469	if (instat.st_mtim.tv_sec > outstat.st_mtim.tv_sec) {
470		return true;
471	}
472
473	if (
474			instat.st_mtim.tv_sec == outstat.st_mtim.tv_sec &&
475			instat.st_mtim.tv_nsec >= outstat.st_mtim.tv_nsec) {
476		return true;
477	}
478
479	return false;
480}
481
482static char *effect_custom_compile(const char *path) {
483	static char *cachepath = NULL;
484	static size_t cachelen;
485	if (!cachepath) {
486		char *xdgdir = getenv("XDG_DATA_HOME");
487		if (xdgdir) {
488			cachepath = malloc(strlen(xdgdir) + strlen("/swaylock") + 1);
489			cachelen = sprintf(cachepath, "%s/swaylock", xdgdir);
490		} else {
491			char *homedir = getenv("HOME");
492			if (homedir == NULL) {
493				swaylock_log(LOG_ERROR,
494						"Can't compile custom effect; neither $HOME nor $XDG_CONFIG_HOME "
495						"is defined.");
496				return NULL;
497			}
498
499			cachepath = malloc(strlen(homedir) + strlen("/.cache/swaylock") + 1);
500			cachelen = sprintf(cachepath, "%s/.cache/swaylock", homedir);
501		}
502
503		if (mkdir(cachepath, 0777) < 0 && errno != EEXIST) {
504			swaylock_log(LOG_ERROR,
505					"Can't compile custom effect; mkdir %s failed: %s\n",
506					cachepath, strerror(errno));
507			free(cachepath);
508			cachepath = NULL;
509			return NULL;
510		}
511	}
512
513	// Find the true, absolute path of the input file
514	char *abspath = realpath(path, NULL);
515	size_t abspathlen = strlen(abspath);
516
517	char *outpath = malloc(cachelen + 1 + abspathlen + 3 + 1);
518	size_t outlen = sprintf(outpath, "%s/%s.so", cachepath, abspath);
519
520	// Sanitize
521	for (char *ch = outpath + cachelen + 1; ch < outpath + cachelen + 1 + abspathlen; ++ch) {
522		if (!(
523				(*ch >= 'a' && *ch <= 'z') ||
524				(*ch >= 'A' && *ch <= 'Z') ||
525				(*ch >= '0' && *ch <= '9') ||
526				(*ch == '.'))) {
527			*ch = '_';
528		}
529	}
530
531	if (!file_is_outdated(path, outpath)) {
532		free(abspath);
533		return outpath;
534	}
535
536	static const char *fmt = "cc -shared -g -O2 -march=native -fopenmp -o '%s' '%s' -lm";
537	char *cmd = malloc(strlen(fmt) + outlen - 2 + abspathlen - 2 + 1);
538	sprintf(cmd, fmt, outpath, abspath);
539	free(abspath);
540	fprintf(stderr, "Compiling custom effect: %s\n", cmd);
541
542	// Finally, compile.
543	int ret = system(cmd);
544	free(cmd);
545	if (ret != 0) {
546		if (ret == -1) {
547			swaylock_log(LOG_ERROR, "Custom effect: system(): %s", strerror(errno));
548			free(outpath);
549			return NULL;
550		} else {
551			swaylock_log(LOG_ERROR, "Custom effect compilation failed\n");
552			free(outpath);
553			return NULL;
554		}
555	}
556
557	return outpath;
558}
559
560static void effect_custom(uint32_t *data, int width, int height, int scale,
561		char *path) {
562	size_t pathlen = strlen(path);
563	if (pathlen > 3 && strcmp(path + pathlen - 3, ".so") == 0) {
564		effect_custom_run(data, width, height, scale, path);
565	} else if (pathlen > 2 && strcmp(path + pathlen - 2, ".c") == 0) {
566		char *compiled = effect_custom_compile(path);
567		if (compiled != NULL) {
568			effect_custom_run(data, width, height, scale, compiled);
569			free(compiled);
570		}
571	} else {
572		swaylock_log(
573			LOG_ERROR, "%s: Unknown file type for custom effect (expected .c or .so)",
574			path);
575	}
576}
577
578static cairo_surface_t *run_effect(cairo_surface_t *surface, int scale,
579		struct swaylock_effect *effect) {
580	switch (effect->tag) {
581	case EFFECT_BLUR: {
582		cairo_surface_t *surf = cairo_image_surface_create(
583				CAIRO_FORMAT_RGB24,
584				cairo_image_surface_get_width(surface),
585				cairo_image_surface_get_height(surface));
586
587		if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) {
588			swaylock_log(LOG_ERROR, "Failed to create surface for blur effect");
589			cairo_surface_destroy(surf);
590			break;
591		}
592
593		effect_blur(
594				(uint32_t *)cairo_image_surface_get_data(surf),
595				(uint32_t *)cairo_image_surface_get_data(surface),
596				cairo_image_surface_get_width(surface),
597				cairo_image_surface_get_height(surface),
598				scale,
599				effect->e.blur.radius, effect->e.blur.times);
600		cairo_surface_flush(surf);
601		cairo_surface_destroy(surface);
602		surface = surf;
603		break;
604	}
605
606	case EFFECT_PIXELATE: {
607		effect_pixelate(
608				(uint32_t *)cairo_image_surface_get_data(surface),
609				cairo_image_surface_get_width(surface),
610				cairo_image_surface_get_height(surface),
611				scale,
612				effect->e.pixelate.factor);
613		cairo_surface_flush(surface);
614		break;
615	}
616
617	case EFFECT_SCALE: {
618		cairo_surface_t *surf = cairo_image_surface_create(
619				CAIRO_FORMAT_RGB24,
620				cairo_image_surface_get_width(surface) * effect->e.scale,
621				cairo_image_surface_get_height(surface) * effect->e.scale);
622
623		if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) {
624			swaylock_log(LOG_ERROR, "Failed to create surface for scale effect");
625			cairo_surface_destroy(surf);
626			break;
627		}
628
629		effect_scale(
630				(uint32_t *)cairo_image_surface_get_data(surf),
631				(uint32_t *)cairo_image_surface_get_data(surface),
632				cairo_image_surface_get_width(surface),
633				cairo_image_surface_get_height(surface),
634				effect->e.scale);
635		cairo_surface_flush(surf);
636		cairo_surface_destroy(surface);
637		surface = surf;
638		break;
639	}
640
641	case EFFECT_GREYSCALE: {
642		effect_greyscale(
643				(uint32_t *)cairo_image_surface_get_data(surface),
644				cairo_image_surface_get_width(surface),
645				cairo_image_surface_get_height(surface));
646		cairo_surface_flush(surface);
647		break;
648	}
649
650	case EFFECT_VIGNETTE: {
651		effect_vignette(
652				(uint32_t *)cairo_image_surface_get_data(surface),
653				cairo_image_surface_get_width(surface),
654				cairo_image_surface_get_height(surface),
655				effect->e.vignette.base,
656				effect->e.vignette.factor);
657		cairo_surface_flush(surface);
658		break;
659	}
660
661	case EFFECT_COMPOSE: {
662		effect_compose(
663				(uint32_t *)cairo_image_surface_get_data(surface),
664				cairo_image_surface_get_width(surface),
665				cairo_image_surface_get_height(surface),
666				scale,
667				effect->e.compose.x, effect->e.compose.y,
668				effect->e.compose.w, effect->e.compose.h,
669				effect->e.compose.gravity, effect->e.compose.imgpath);
670		cairo_surface_flush(surface);
671		break;
672	}
673
674	case EFFECT_CUSTOM: {
675		effect_custom(
676				(uint32_t *)cairo_image_surface_get_data(surface),
677				cairo_image_surface_get_width(surface),
678				cairo_image_surface_get_height(surface),
679				scale,
680				effect->e.custom);
681		cairo_surface_flush(surface);
682		break;
683	} }
684
685	return surface;
686}
687
688static cairo_surface_t *ensure_format(cairo_surface_t *surface) {
689	if (cairo_image_surface_get_format(surface) == CAIRO_FORMAT_RGB24) {
690		return surface;
691	}
692
693	swaylock_log(LOG_DEBUG, "Have to convert surface to CAIRO_FORMAT_RGB24 from %i.",
694			(int)cairo_image_surface_get_format(surface));
695
696	cairo_surface_t *surf = cairo_image_surface_create(
697			CAIRO_FORMAT_RGB24,
698			cairo_image_surface_get_width(surface),
699			cairo_image_surface_get_height(surface));
700	if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) {
701		swaylock_log(LOG_ERROR, "Failed to create surface for scale effect");
702		cairo_surface_destroy(surf);
703		return NULL;
704	}
705
706	memcpy(
707			cairo_image_surface_get_data(surf),
708			cairo_image_surface_get_data(surface),
709			cairo_image_surface_get_stride(surface) * cairo_image_surface_get_height(surface));
710	cairo_surface_destroy(surface);
711	return surf;
712}
713
714cairo_surface_t *swaylock_effects_run(cairo_surface_t *surface, int scale,
715		struct swaylock_effect *effects, int count) {
716	surface = ensure_format(surface);
717	if (surface == NULL) return NULL;
718
719	for (int i = 0; i < count; ++i) {
720		struct swaylock_effect *effect = &effects[i];
721		surface = run_effect(surface, scale, effect);
722	}
723
724	return surface;
725}
726
727#define TIME_MSEC(tv) ((tv).tv_sec * 1000.0 + (tv).tv_nsec / 1000000.0)
728#define TIME_DELTA(first, last) (TIME_MSEC(last) - TIME_MSEC(first))
729
730cairo_surface_t *swaylock_effects_run_timed(cairo_surface_t *surface, int scale,
731		struct swaylock_effect *effects, int count) {
732	struct timespec start_tv;
733	clock_gettime(CLOCK_MONOTONIC, &start_tv);
734
735	surface = ensure_format(surface);
736	if (surface == NULL) return NULL;
737
738	fprintf(stderr, "Running %i effects:\n", count);
739	for (int i = 0; i < count; ++i) {
740		struct timespec effect_start_tv;
741		clock_gettime(CLOCK_MONOTONIC, &effect_start_tv);
742
743		struct swaylock_effect *effect = &effects[i];
744		surface = run_effect(surface, scale, effect);
745
746		struct timespec effect_end_tv;
747		clock_gettime(CLOCK_MONOTONIC, &effect_end_tv);
748		fprintf(stderr, "    %s: %fms\n", effect_name(effect),
749				TIME_DELTA(effect_start_tv, effect_end_tv));
750	}
751
752	struct timespec end_tv;
753	clock_gettime(CLOCK_MONOTONIC, &end_tv);
754	fprintf(stderr, "Effects took %fms.\n", TIME_DELTA(start_tv, end_tv));
755
756	return surface;
757}