blind-gauss-blur.c - blind - suckless command-line video editing utility | |
git clone git://git.suckless.org/blind | |
Log | |
Files | |
Refs | |
README | |
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.… | |
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)… | |
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] * (TYP… | |
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)blu… | |
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, &chil… | |
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;… | |
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… | |
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;… | |
174 clr[i1][0] = clr[i1][0] … | |
175 clr[i1][2] = clr[i1][2] … | |
176 /* | |
177 * Explaination: | |
178 * Y is the luma and (… | |
179 * is the chroma (acco… | |
180 * is the white point. | |
181 */\ | |
182 }\ | |
183 }\ | |
184 }\ | |
185 /* Conversion makes no difference if blur is app… | |
186 * parameters: | |
187 * | |
188 * Gaussian blur: | |
189 * | |
190 * ∞ ∞ | |
191 * ⌠ ⌠ V(x,y) −((x−x�… | |
192 * V′ (x₀,y₀) = │ │ ────�… | |
193 * σ ⌡ ⌡ 2πσ² | |
194 * −∞ −∞ | |
195 * | |
196 * With linear transformation, F: | |
197 * | |
198 * ∞ ∞ | |
199 * ⌠ ⌠ F(V(x,y)) −((… | |
200 * V′ (x₀,y₀) = F⁻¹ │ │ ──�… | |
201 * σ ⌡ ⌡ 2πσ² | |
202 * −∞ −∞ | |
203 * | |
204 * ∞ ∞ | |
205 * ⌠ ⌠ ⎛V(x,y) −(… | |
206 * V′ (x₀,y₀) = F⁻¹ │ │ F⎜─… | |
207 * σ ⌡ ⌡ ⎝ 2πσ² … | |
208 * −∞ −∞ | |
209 * | |
210 * ∞ ∞ | |
211 * ⌠ ⌠ V(x,y) �… | |
212 * V′ (x₀,y₀) = (F⁻¹ ∘ F) │ │… | |
213 * σ ⌡ ⌡ 2πσ² | |
214 * −∞ −∞ | |
215 * | |
216 * ∞ ∞ | |
217 * ⌠ ⌠ V(x,y) −((x−x�… | |
218 * V′ (x₀,y₀) = │ │ ────�… | |
219 * σ ⌡ ⌡ 2πσ² | |
220 * −∞ −∞ | |
221 * | |
222 * Just like expected, the colour space should n… | |
223 * result of guassian blur as long as it is line… | |
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 ? c… | |
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 ? … | |
243 i2 = y2start * colour->width + x1,\ | |
244 y2 = y2start; y2 < y2end; (y2++, i2 += colo… | |
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++, i… | |
255 img[i1][0] = (img[i1][0] + img[i… | |
256 img[i1][2] = (img[i1][2] + img[i… | |
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++, i… | |
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++, i… | |
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 sb… | |
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 sbu… | |
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… | |
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_… | |
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, "… | |
378 free(original); | |
379 return 0; | |
380 } |