blind-gauss-blur.c - blind - suckless command-line video editing utility
(HTM) git clone git://git.suckless.org/blind
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
blind-gauss-blur.c (10542B)
---
1 /* See LICENSE file for copyright and license details. */
2 #include "common.h"
3
4 USAGE("[-j jobs] [-s spread | -s 'auto'] [-acghvy] sd-stream")
5
6 static int chroma = 0;
7 static int noalpha = 0;
8 static int glow = 0;
9 static int vertical = 0;
10 static int horizontal = 0;
11 static int measure_y_only = 0;
12 static int auto_spread = 0;
13 static size_t jobs = 1;
14 static size_t spread = 0;
15 static void *original = NULL;
16
17 /*
18 * This is not a regular simple gaussian blur implementation.
19 * This implementation is able to apply different levels of
20 * blur on different pixels. It's therefore written a bit
21 * oddly. Instead of going through each pixel and calculate
22 * the new value for each pixel, it goes through each pixel
23 * and smears it out to the other pixels.
24 */
25
26 #define BLUR_PIXEL_PROLOGUE(TYPE, DIR)\
27 if (sig[i1][3] == 0)\
28 goto no_blur_##DIR;\
29 if (chroma || measure_y_only) {\
30 k[0] = sig[i1][1] * sig[i1][3];\
31 if (auto_spread)\
32 spread = k[0] > 0 ? (size_t)(k[0] * 3 + (TYPE)0.5) : 0;\
33 blur[2] = blur[0] = k[0] > 0;\
34 c[0] = k[0] *= k[0] * 2, c[0] = sqrt(c[0] * (TYPE)M_PI);\
35 k[0] = 1 / -k[0], c[0] = 1 / c[0];\
36 if (chroma) {\
37 k[2] = k[0];\
38 c[2] = c[0];\
39 c[1] = k[1] = 0;\
40 blur[1] = 0;\
41 } else {\
42 k[2] = k[1] = k[0];\
43 c[2] = c[1] = c[0];\
44 blur[1] = blur[0];\
45 }\
46 } else {\
47 if (auto_spread)\
48 spread = 0;\
49 for (i = 0; i < 3; i++) {\
50 k[i] = sig[i1][i] * sig[i1][3];\
51 if (auto_spread && k[i] > 0 && spread < (size_t)(k[i] * 3 + (TYPE)0.5))\
52 spread = (size_t)(k[i] * 3 + (TYPE)0.5);\
53 blur[i] = k[i] > 0;\
54 c[i] = k[i] *= k[i] * 2, c[i] = sqrt(c[i] * (TYPE)M_PI);\
55 k[i] = 1 / -k[i], c[i] = 1 / c[i];\
56 }\
57 }\
58 if (blur[0] + blur[1] + blur[2] == 0)\
59 goto no_blur_##DIR;\
60 if (auto_spread && spread < 1)\
61 spread = 1;
62
63 #define BLUR_PIXEL(TYPE, START, LOOP, DISTANCE)\
64 if (k[0] == k[1] && k[1] == k[2]) {\
65 START;\
66 for (LOOP) {\
67 d = (TYPE)(DISTANCE);\
68 d *= d;\
69 m = c[0] * exp(d * k[0]);\
70 img[i2][0] += clr[i1][0] * m;\
71 img[i2][1] += clr[i1][1] * m;\
72 img[i2][2] += clr[i1][2] * m;\
73 img[i2][3] += clr[i1][3] * m;\
74 }\
75 } else {\
76 blurred = 0;\
77 for (i = 0; i < 3; i++) {\
78 if (blur[i])\
79 blurred += 1;\
80 else\
81 img[i1][i] += clr[i1][i];\
82 }\
83 for (i = 0; i < 3; i++) {\
84 if (!blur[i])\
85 continue;\
86 START;\
87 for (LOOP) {\
88 d = (TYPE)(DISTANCE);\
89 d *= d;\
90 m = c[i] * exp(d * k[i]);\
91 img[i2][i] += clr[i1][i] * m;\
92 img[i2][3] += clr[i1][3] * m / (TYPE)blurred;\
93 }\
94 }\
95 }
96
97 #define BLUR_PIXEL_EPILOGUE(DIR)\
98 continue;\
99 no_blur_##DIR:\
100 img[i1][0] = clr[i1][0];\
101 img[i1][1] = clr[i1][1];\
102 img[i1][2] = clr[i1][2];\
103 img[i1][3] = clr[i1][3];
104
105 #define BLUR(TYPE, DIR, SETSTART, SETEND, START, LOOP, DISTANCE)\
106 do {\
107 memset(img, 0, colour->frame_size);\
108 start = 0, end = colour->height;\
109 is_master = efork_jobs(&start, &end, jobs, &children);\
110 i1 = start * colour->width;\
111 for (y1 = start; y1 < end; y1++) {\
112 for (x1 = 0; x1 < colour->width; x1++, i1++) {\
113 BLUR_PIXEL_PROLOGUE(TYPE, DIR);\
114 if (spread) {\
115 SETSTART;\
116 SETEND;\
117 }\
118 BLUR_PIXEL(TYPE, START, LOOP, DISTANCE);\
119 BLUR_PIXEL_EPILOGUE(DIR);\
120 }\
121 }\
122 ejoin_jobs(is_master, children);\
123 } while (0)
124
125 #define PROCESS(TYPE)\
126 do {\
127 typedef TYPE pixel_t[4];\
128 \
129 pixel_t *restrict clr = (pixel_t *)cbuf;\
130 pixel_t *restrict sig = (pixel_t *)sbuf;\
131 pixel_t *restrict orig = original;\
132 pixel_t *img = (pixel_t *)output;\
133 pixel_t c, k;\
134 size_t x1, y1, i1, x2, y2, i2;\
135 TYPE d, m;\
136 int i, blurred, blur[3] = {0, 0, 0};\
137 size_t start, end, x2start, x2end, y2start, y2end;\
138 int is_master;\
139 pid_t *children;\
140 \
141 y2start = x2start = 0;\
142 x2end = colour->width;\
143 y2end = colour->height;\
144 \
145 if (glow)\
146 memcpy(orig, clr, colour->frame_size);\
147 if (chroma || !noalpha) {\
148 start = 0, end = colour->height;\
149 is_master = efork_jobs(&start, &end, jobs, &children);\
150 \
151 /* premultiply alpha channel */\
152 if (!noalpha) {\
153 i1 = start * colour->width;\
154 for (y1 = start; y1 < end; y1++) {\
155 for (x1 = 0; x1 < colour->width; x1++, i1++) {\
156 clr[i1][0] *= clr[i1][3];\
157 clr[i1][1] *= clr[i1][3];\
158 clr[i1][2] *= clr[i1][3];\
159 }\
160 }\
161 }\
162 \
163 /* store original image */\
164 if (glow) {\
165 i1 = start * colour->width;\
166 memcpy(orig + i1, clr + i1, (end - start) * colour->width * sizeof(*clr));\
167 }\
168 \
169 /* convert colour model */\
170 if (chroma) {\
171 i1 = start * colour->width;\
172 for (y1 = start; y1 < end; y1++) {\
173 for (x1 = 0; x1 < colour->width; x1++, i1++) {\
174 clr[i1][0] = clr[i1][0] / (TYPE)D65_XYZ_X - clr[i1][1];\
175 clr[i1][2] = clr[i1][2] / (TYPE)D65_XYZ_Z - clr[i1][1];\
176 /*
177 * Explaination:
178 * Y is the luma and ((X / Xn - Y / Yn), (Z / Zn - Y / Yn))
179 * is the chroma (according to CIELAB), where (Xn, Yn, Zn)
180 * is the white point.
181 */\
182 }\
183 }\
184 }\
185 /* Conversion makes no difference if blur is applied to all
186 * parameters:
187 *
188 * Gaussian blur:
189 *
190 * ∞ ∞
191 * ⌠ ⌠ V(x,y) −((x−x₀)² + (y−y₀)²)/(2σ²)
192 * V′ (x₀,y₀) = │ │ ────── e dxdy
193 * σ ⌡ ⌡ 2πσ²
194 * −∞ −∞
195 *
196 * With linear transformation, F:
197 *
198 * ∞ ∞
199 * ⌠ ⌠ F(V(x,y)) −((x−x₀)² + (y−y₀)²)/(2σ²)
200 * V′ (x₀,y₀) = F⁻¹ │ │ ───────── e dxdy
201 * σ ⌡ ⌡ 2πσ²
202 * −∞ −∞
203 *
204 * ∞ ∞
205 * ⌠ ⌠ ⎛V(x,y) −((x−x₀)² + (y−y₀)²)/(2σ²)⎞
206 * V′ (x₀,y₀) = F⁻¹ │ │ F⎜────── e ⎟ dxdy
207 * σ ⌡ ⌡ ⎝ 2πσ² ⎠
208 * −∞ −∞
209 *
210 * ∞ ∞
211 * ⌠ ⌠ V(x,y) −((x−x₀)² + (y−y₀)²)/(2σ²)
212 * V′ (x₀,y₀) = (F⁻¹ ∘ F) │ │ ────── e dxdy
213 * σ ⌡ ⌡ 2πσ²
214 * −∞ −∞
215 *
216 * ∞ ∞
217 * ⌠ ⌠ V(x,y) −((x−x₀)² + (y−y₀)²)/(2σ²)
218 * V′ (x₀,y₀) = │ │ ────── e dxdy
219 * σ ⌡ ⌡ 2πσ²
220 * −∞ −∞
221 *
222 * Just like expected, the colour space should not affect the
223 * result of guassian blur as long as it is linear.
224 */\
225 \
226 ejoin_jobs(is_master, children);\
227 }\
228 \
229 /* blur */\
230 if (horizontal)\
231 BLUR(TYPE, horizontal,\
232 x2start = spread > x1 ? 0 : x1 - spread,\
233 x2end = spread + 1 > colour->width - x1 ? colour->width : x1 + spread + 1,\
234 i2 = y1 * colour->width + x2start,\
235 x2 = x2start; x2 < x2end; (x2++, i2++),\
236 (ssize_t)x1 - (ssize_t)x2);\
237 if (horizontal && vertical)\
238 memcpy(clr, img, colour->frame_size);\
239 if (vertical)\
240 BLUR(TYPE, vertical,\
241 y2start = spread > y1 ? 0 : y1 - spread,\
242 y2end = spread + 1 > colour->height - y1 ? colour->height : y1 + spread + 1,\
243 i2 = y2start * colour->width + x1,\
244 y2 = y2start; y2 < y2end; (y2++, i2 += colour->width),\
245 (ssize_t)y1 - (ssize_t)y2);\
246 \
247 start = 0, end = colour->height;\
248 is_master = efork_jobs(&start, &end, jobs, &children);\
249 \
250 /* convert back to CIE XYZ */\
251 if (chroma) {\
252 i1 = start * colour->width;\
253 for (y1 = start; y1 < end; y1++) {\
254 for (x1 = 0; x1 < colour->width; x1++, i1++) {\
255 img[i1][0] = (img[i1][0] + img[i1][1]) * (TYPE)D65_XYZ_X;\
256 img[i1][2] = (img[i1][2] + img[i1][1]) * (TYPE)D65_XYZ_Z;\
257 }\
258 }\
259 }\
260 \
261 /* apply glow */\
262 if (glow) {\
263 i1 = start * colour->width;\
264 for (y1 = start; y1 < end; y1++) {\
265 for (x1 = 0; x1 < colour->width; x1++, i1++) {\
266 img[i1][0] += orig[i1][0];\
267 img[i1][1] += orig[i1][1];\
268 img[i1][2] += orig[i1][2];\
269 img[i1][3] += orig[i1][3];\
270 }\
271 }\
272 }\
273 \
274 /* unpremultiply alpha channel */\
275 i1 = start * colour->width;\
276 for (y1 = start; y1 < end; y1++) {\
277 for (x1 = 0; x1 < colour->width; x1++, i1++) {\
278 if (img[i1][3] != 0)\
279 continue;\
280 img[i1][0] /= img[i1][3];\
281 img[i1][1] /= img[i1][3];\
282 img[i1][2] /= img[i1][3];\
283 }\
284 }\
285 \
286 /* ensure the video if opaque if -a was used */\
287 if (noalpha) {\
288 i1 = start * colour->width;\
289 for (y1 = start; y1 < end; y1++)\
290 for (x1 = 0; x1 < colour->width; x1++, i1++)\
291 img[i1][3] = 1;\
292 }\
293 \
294 ejoin_jobs(is_master, children);\
295 \
296 (void) sigma;\
297 } while (0)
298
299 static void
300 process_lf(char *restrict output, char *restrict cbuf, char *restrict sbuf,
301 struct stream *colour, struct stream *sigma)
302 {
303 PROCESS(double);
304 }
305
306 static void
307 process_f(char *restrict output, char *restrict cbuf, char *restrict sbuf,
308 struct stream *colour, struct stream *sigma)
309 {
310 PROCESS(float);
311 }
312
313 int
314 main(int argc, char *argv[])
315 {
316 struct stream colour, sigma;
317 char *arg;
318 void (*process)(char *restrict output, char *restrict cbuf, char *restrict sbuf,
319 struct stream *colour, struct stream *sigma);
320
321 ARGBEGIN {
322 case 'a':
323 noalpha = 1;
324 break;
325 case 'c':
326 chroma = 1;
327 break;
328 case 'g':
329 glow = 1;
330 break;
331 case 'h':
332 horizontal = 1;
333 break;
334 case 'v':
335 vertical = 1;
336 break;
337 case 'y':
338 measure_y_only = 1;
339 break;
340 case 'j':
341 jobs = etozu_flag('j', UARGF(), 1, SHRT_MAX);
342 break;
343 case 's':
344 arg = UARGF();
345 if (!strcmp(arg, "auto"))
346 auto_spread = 1;
347 else
348 spread = etozu_flag('s', arg, 1, SIZE_MAX);
349 break;
350 default:
351 usage();
352 } ARGEND;
353
354 if (argc != 1)
355 usage();
356
357 if (!vertical && !horizontal)
358 vertical = horizontal = 1;
359
360 eopen_stream(&colour, NULL);
361 eopen_stream(&sigma, argv[0]);
362
363 SELECT_PROCESS_FUNCTION(&colour);
364 CHECK_CHANS(&colour, == 3, == (measure_y_only ? 1 : colour.luma_chan));
365 CHECK_N_CHAN(&colour, 4, 4);
366
367 echeck_compat(&colour, &sigma);
368
369 if (jobs > colour.height)
370 jobs = colour.height;
371
372 if (glow)
373 original = emalloc(colour.frame_size);
374
375 fprint_stream_head(stdout, &colour);
376 efflush(stdout, "<stdout>");
377 process_each_frame_two_streams(&colour, &sigma, STDOUT_FILENO, "<stdout>", process);
378 free(original);
379 return 0;
380 }