FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_morpho.c
Date: 2024-02-16 17:37:06
Exec Total Coverage
Lines: 0 570 0.0%
Functions: 0 46 0.0%
Branches: 0 301 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2016 ReneBrals
3 * Copyright (c) 2021 Paul B Mahol
4 *
5 * This file is part of FFmpeg.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in all
15 * copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26 #include "libavutil/avassert.h"
27 #include "libavutil/imgutils.h"
28 #include "libavutil/intreadwrite.h"
29 #include "libavutil/opt.h"
30 #include "libavutil/pixdesc.h"
31 #include "avfilter.h"
32 #include "framesync.h"
33 #include "internal.h"
34 #include "video.h"
35
36 enum MorphModes {
37 ERODE,
38 DILATE,
39 OPEN,
40 CLOSE,
41 GRADIENT,
42 TOPHAT,
43 BLACKHAT,
44 NB_MODES
45 };
46
47 typedef struct IPlane {
48 uint8_t **img;
49 int w, h;
50 int range;
51 int depth;
52 int type_size;
53
54 void (*max_out_place)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x);
55 void (*min_out_place)(uint8_t *c, const uint8_t *a, const uint8_t *b, int x);
56 void (*diff_rin_place)(uint8_t *a, const uint8_t *b, int x);
57 void (*max_in_place)(uint8_t *a, const uint8_t *b, int x);
58 void (*min_in_place)(uint8_t *a, const uint8_t *b, int x);
59 void (*diff_in_place)(uint8_t *a, const uint8_t *b, int x);
60 } IPlane;
61
62 typedef struct LUT {
63 /* arr is shifted from base_arr by FFMAX(min_r, 0).
64 * arr != NULL means "lut completely allocated" */
65 uint8_t ***arr;
66 uint8_t ***base_arr;
67 int min_r;
68 int max_r;
69 int I;
70 int X;
71 int pre_pad_x;
72 int type_size;
73 } LUT;
74
75 typedef struct chord {
76 int x;
77 int y;
78 int l;
79 int i;
80 } chord;
81
82 typedef struct chord_set {
83 chord *C;
84 int size;
85 int cap;
86
87 int *R;
88 int Lnum;
89
90 int minX;
91 int maxX;
92 int minY;
93 int maxY;
94 unsigned nb_elements;
95 } chord_set;
96
97 #define MAX_THREADS 64
98
99 typedef struct MorphoContext {
100 const AVClass *class;
101 FFFrameSync fs;
102
103 chord_set SE[4];
104 IPlane SEimg[4];
105 IPlane g[4], f[4], h[4];
106 LUT Ty[MAX_THREADS][2][4];
107
108 int mode;
109 int planes;
110 int structures;
111
112 int planewidth[4];
113 int planeheight[4];
114 int splanewidth[4];
115 int splaneheight[4];
116 int depth;
117 int type_size;
118 int nb_planes;
119
120 int got_structure[4];
121
122 AVFrame *temp;
123
124 int64_t *plane_f, *plane_g;
125 } MorphoContext;
126
127 #define OFFSET(x) offsetof(MorphoContext, x)
128 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_RUNTIME_PARAM
129
130 static const AVOption morpho_options[] = {
131 { "mode", "set morphological transform", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_MODES-1, FLAGS, .unit = "mode" },
132 { "erode", NULL, 0, AV_OPT_TYPE_CONST, {.i64=ERODE}, 0, 0, FLAGS, .unit = "mode" },
133 { "dilate", NULL, 0, AV_OPT_TYPE_CONST, {.i64=DILATE}, 0, 0, FLAGS, .unit = "mode" },
134 { "open", NULL, 0, AV_OPT_TYPE_CONST, {.i64=OPEN}, 0, 0, FLAGS, .unit = "mode" },
135 { "close", NULL, 0, AV_OPT_TYPE_CONST, {.i64=CLOSE}, 0, 0, FLAGS, .unit = "mode" },
136 { "gradient",NULL, 0, AV_OPT_TYPE_CONST, {.i64=GRADIENT},0, 0, FLAGS, .unit = "mode" },
137 { "tophat",NULL, 0, AV_OPT_TYPE_CONST, {.i64=TOPHAT}, 0, 0, FLAGS, .unit = "mode" },
138 { "blackhat",NULL, 0, AV_OPT_TYPE_CONST, {.i64=BLACKHAT},0, 0, FLAGS, .unit = "mode" },
139 { "planes", "set planes to filter", OFFSET(planes), AV_OPT_TYPE_INT, {.i64=7}, 0, 15, FLAGS },
140 { "structure", "when to process structures", OFFSET(structures), AV_OPT_TYPE_INT, {.i64=1}, 0, 1, FLAGS, .unit = "str" },
141 { "first", "process only first structure, ignore rest", 0, AV_OPT_TYPE_CONST, {.i64=0}, 0, 0, FLAGS, .unit = "str" },
142 { "all", "process all structure", 0, AV_OPT_TYPE_CONST, {.i64=1}, 0, 0, FLAGS, .unit = "str" },
143 { NULL }
144 };
145
146 FRAMESYNC_DEFINE_CLASS(morpho, MorphoContext, fs);
147
148 static const enum AVPixelFormat pix_fmts[] = {
149 AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUV444P, AV_PIX_FMT_YUV440P,
150 AV_PIX_FMT_YUVJ444P, AV_PIX_FMT_YUVJ440P,
151 AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUV422P, AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUV420P,
152 AV_PIX_FMT_YUVJ422P, AV_PIX_FMT_YUVJ420P,
153 AV_PIX_FMT_YUVJ411P, AV_PIX_FMT_YUV411P, AV_PIX_FMT_YUV410P,
154 AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, AV_PIX_FMT_GRAY8, AV_PIX_FMT_GRAY9,
155 AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, AV_PIX_FMT_GBRP9,
156 AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA444P9,
157 AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10,
158 AV_PIX_FMT_YUV420P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV440P12,
159 AV_PIX_FMT_YUV420P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV444P14,
160 AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16,
161 AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA444P10,
162 AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA444P12,
163 AV_PIX_FMT_YUVA420P16, AV_PIX_FMT_YUVA422P16, AV_PIX_FMT_YUVA444P16,
164 AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRP14, AV_PIX_FMT_GBRP16,
165 AV_PIX_FMT_GBRAP10, AV_PIX_FMT_GBRAP12, AV_PIX_FMT_GBRAP16,
166 AV_PIX_FMT_GRAY10, AV_PIX_FMT_GRAY12, AV_PIX_FMT_GRAY14, AV_PIX_FMT_GRAY16,
167 AV_PIX_FMT_NONE
168 };
169
170 static void min_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x)
171 {
172 for (int i = 0; i < x; i++)
173 c[i] = FFMIN(b[i], a[i]);
174 }
175
176 static void mininplace_fun(uint8_t *a, const uint8_t *b, int x)
177 {
178 for (int i = 0; i < x; i++)
179 a[i] = FFMIN(a[i], b[i]);
180 }
181
182 static void max_fun(uint8_t *c, const uint8_t *a, const uint8_t *b, int x)
183 {
184 for (int i = 0; i < x; i++)
185 c[i] = FFMAX(a[i], b[i]);
186 }
187
188 static void maxinplace_fun(uint8_t *a, const uint8_t *b, int x)
189 {
190 for (int i = 0; i < x; i++)
191 a[i] = FFMAX(a[i], b[i]);
192 }
193
194 static void diff_fun(uint8_t *a, const uint8_t *b, int x)
195 {
196 for (int i = 0; i < x; i++)
197 a[i] = FFMAX(b[i] - a[i], 0);
198 }
199
200 static void diffinplace_fun(uint8_t *a, const uint8_t *b, int x)
201 {
202 for (int i = 0; i < x; i++)
203 a[i] = FFMAX(a[i] - b[i], 0);
204 }
205
206 static void min16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x)
207 {
208 const uint16_t *a = (const uint16_t *)aa;
209 const uint16_t *b = (const uint16_t *)bb;
210 uint16_t *c = (uint16_t *)cc;
211
212 for (int i = 0; i < x; i++)
213 c[i] = FFMIN(b[i], a[i]);
214 }
215
216 static void mininplace16_fun(uint8_t *aa, const uint8_t *bb, int x)
217 {
218 uint16_t *a = (uint16_t *)aa;
219 const uint16_t *b = (const uint16_t *)bb;
220
221 for (int i = 0; i < x; i++)
222 a[i] = FFMIN(a[i], b[i]);
223 }
224
225 static void diff16_fun(uint8_t *aa, const uint8_t *bb, int x)
226 {
227 const uint16_t *b = (const uint16_t *)bb;
228 uint16_t *a = (uint16_t *)aa;
229
230 for (int i = 0; i < x; i++)
231 a[i] = FFMAX(b[i] - a[i], 0);
232 }
233
234 static void diffinplace16_fun(uint8_t *aa, const uint8_t *bb, int x)
235 {
236 uint16_t *a = (uint16_t *)aa;
237 const uint16_t *b = (const uint16_t *)bb;
238
239 for (int i = 0; i < x; i++)
240 a[i] = FFMAX(a[i] - b[i], 0);
241 }
242
243 static void max16_fun(uint8_t *cc, const uint8_t *aa, const uint8_t *bb, int x)
244 {
245 const uint16_t *a = (const uint16_t *)aa;
246 const uint16_t *b = (const uint16_t *)bb;
247 uint16_t *c = (uint16_t *)cc;
248
249 for (int i = 0; i < x; i++)
250 c[i] = FFMAX(a[i], b[i]);
251 }
252
253 static void maxinplace16_fun(uint8_t *aa, const uint8_t *bb, int x)
254 {
255 uint16_t *a = (uint16_t *)aa;
256 const uint16_t *b = (const uint16_t *)bb;
257
258 for (int i = 0; i < x; i++)
259 a[i] = FFMAX(a[i], b[i]);
260 }
261
262 static int alloc_lut(LUT *Ty, chord_set *SE, int type_size, int mode)
263 {
264 const int min = FFMAX(Ty->min_r, 0);
265 const int max = min + (Ty->max_r - Ty->min_r);
266 int pre_pad_x = 0;
267
268 if (SE->minX < 0)
269 pre_pad_x = 0 - SE->minX;
270 Ty->pre_pad_x = pre_pad_x;
271 Ty->type_size = type_size;
272
273 Ty->base_arr = av_calloc(max + 1, sizeof(*Ty->base_arr));
274 if (!Ty->base_arr)
275 return AVERROR(ENOMEM);
276 for (int r = min; r <= max; r++) {
277 uint8_t **arr = Ty->base_arr[r] = av_calloc(Ty->I, sizeof(uint8_t *));
278 if (!Ty->base_arr[r])
279 return AVERROR(ENOMEM);
280 for (int i = 0; i < Ty->I; i++) {
281 arr[i] = av_calloc(Ty->X + pre_pad_x, type_size);
282 if (!arr[i])
283 return AVERROR(ENOMEM);
284 if (mode == ERODE)
285 memset(arr[i], UINT8_MAX, pre_pad_x * type_size);
286 /* Shifting the X index such that negative indices correspond to
287 * the pre-padding.
288 */
289 arr[i] = &(arr[i][pre_pad_x * type_size]);
290 }
291 }
292
293 Ty->arr = &(Ty->base_arr[min - Ty->min_r]);
294
295 return 0;
296 }
297
298 static void free_lut(LUT *table)
299 {
300 const int min = FFMAX(table->min_r, 0);
301 const int max = min + (table->max_r - table->min_r);
302
303 if (!table->base_arr)
304 return;
305
306 for (int r = min; r <= max; r++) {
307 if (!table->base_arr[r])
308 break;
309 for (int i = 0; i < table->I; i++) {
310 if (!table->base_arr[r][i])
311 break;
312 // The X index was also shifted, for padding purposes.
313 av_free(table->base_arr[r][i] - table->pre_pad_x * table->type_size);
314 }
315 av_freep(&table->base_arr[r]);
316 }
317 av_freep(&table->base_arr);
318 table->arr = NULL;
319 }
320
321 static int alloc_lut_if_necessary(LUT *Ty, IPlane *f, chord_set *SE,
322 int num, enum MorphModes mode)
323 {
324 if (!Ty->arr || Ty->I != SE->Lnum ||
325 Ty->X != f->w ||
326 SE->minX < 0 && -SE->minX > Ty->pre_pad_x ||
327 Ty->min_r != SE->minY ||
328 Ty->max_r != SE->maxY + num - 1) {
329 int ret;
330
331 free_lut(Ty);
332
333 Ty->I = SE->Lnum;
334 Ty->X = f->w;
335 Ty->min_r = SE->minY;
336 Ty->max_r = SE->maxY + num - 1;
337 ret = alloc_lut(Ty, SE, f->type_size, mode);
338 if (ret < 0)
339 return ret;
340 }
341 return 0;
342 }
343
344 static void circular_swap(LUT *Ty)
345 {
346 /*
347 * Swap the pointers to r-indices in a circle. This is useful because
348 * Ty(r,i,x) = Ty-1(r+1,i,x) for r < ymax.
349 */
350 if (Ty->max_r - Ty->min_r > 0) {
351 uint8_t **Ty0 = Ty->arr[Ty->min_r];
352
353 for (int r = Ty->min_r; r < Ty->max_r; r++)
354 Ty->arr[r] = Ty->arr[r + 1];
355
356 Ty->arr[Ty->max_r] = Ty0;
357 }
358 }
359
360 static void compute_min_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y)
361 {
362 if (y + r >= 0 && y + r < f->h) {
363 memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size);
364 } else {
365 memset(Ty->arr[r][0], UINT8_MAX, Ty->X * Ty->type_size);
366 }
367
368 for (int i = 1; i < SE->Lnum; i++) {
369 int d = SE->R[i] - SE->R[i - 1];
370
371 f->min_out_place(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size,
372 Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size,
373 Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size,
374 Ty->X + Ty->pre_pad_x - d);
375 memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size,
376 Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size,
377 d * f->type_size);
378 }
379 }
380
381 static void update_min_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num)
382 {
383 for (int i = 0; i < num; i++)
384 circular_swap(Ty);
385
386 compute_min_row(f, Ty, SE, Ty->max_r - tid, y);
387 }
388
389 static int compute_min_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num)
390 {
391 int ret = alloc_lut_if_necessary(Ty, f, SE, num, ERODE);
392 if (ret < 0)
393 return ret;
394
395 for (int r = Ty->min_r; r <= Ty->max_r; r++)
396 compute_min_row(f, Ty, SE, r, y);
397
398 return 0;
399 }
400
401 static void compute_max_row(IPlane *f, LUT *Ty, chord_set *SE, int r, int y)
402 {
403 if (y + r >= 0 && y + r < f->h) {
404 memcpy(Ty->arr[r][0], f->img[y + r], Ty->X * Ty->type_size);
405 } else {
406 memset(Ty->arr[r][0], 0, Ty->X * Ty->type_size);
407 }
408
409 for (int i = 1; i < SE->Lnum; i++) {
410 int d = SE->R[i] - SE->R[i - 1];
411
412 f->max_out_place(Ty->arr[r][i] - Ty->pre_pad_x * f->type_size,
413 Ty->arr[r][i - 1] - Ty->pre_pad_x * f->type_size,
414 Ty->arr[r][i - 1] + (d - Ty->pre_pad_x) * f->type_size,
415 Ty->X + Ty->pre_pad_x - d);
416 memcpy(Ty->arr[r][i] + (Ty->X - d) * f->type_size,
417 Ty->arr[r][i - 1] + (Ty->X - d) * f->type_size,
418 d * f->type_size);
419 }
420 }
421
422 static void update_max_lut(IPlane *f, LUT *Ty, chord_set *SE, int y, int tid, int num)
423 {
424 for (int i = 0; i < num; i++)
425 circular_swap(Ty);
426
427 compute_max_row(f, Ty, SE, Ty->max_r - tid, y);
428 }
429
430 static int compute_max_lut(LUT *Ty, IPlane *f, chord_set *SE, int y, int num)
431 {
432 int ret = alloc_lut_if_necessary(Ty, f, SE, num, DILATE);
433 if (ret < 0)
434 return ret;
435
436 for (int r = Ty->min_r; r <= Ty->max_r; r++)
437 compute_max_row(f, Ty, SE, r, y);
438
439 return 0;
440 }
441
442 static void line_dilate(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid)
443 {
444 memset(g->img[y], 0, g->w * g->type_size);
445
446 for (int c = 0; c < SE->size; c++) {
447 g->max_in_place(g->img[y],
448 Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size,
449 av_clip(g->w - SE->C[c].x, 0, g->w));
450 }
451 }
452
453 static void line_erode(IPlane *g, LUT *Ty, chord_set *SE, int y, int tid)
454 {
455 memset(g->img[y], UINT8_MAX, g->w * g->type_size);
456
457 for (int c = 0; c < SE->size; c++) {
458 g->min_in_place(g->img[y],
459 Ty->arr[SE->C[c].y + tid][SE->C[c].i] + SE->C[c].x * Ty->type_size,
460 av_clip(g->w - SE->C[c].x, 0, g->w));
461 }
462 }
463
464 static int dilate(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty, int y0, int y1)
465 {
466 int ret = compute_max_lut(Ty, f, SE, y0, 1);
467 if (ret < 0)
468 return ret;
469
470 line_dilate(g, Ty, SE, y0, 0);
471 for (int y = y0 + 1; y < y1; y++) {
472 update_max_lut(f, Ty, SE, y, 0, 1);
473 line_dilate(g, Ty, SE, y, 0);
474 }
475
476 return 0;
477 }
478
479 static int erode(IPlane *g, IPlane *f, chord_set *SE, LUT *Ty, int y0, int y1)
480 {
481 int ret = compute_min_lut(Ty, f, SE, y0, 1);
482 if (ret < 0)
483 return ret;
484
485 line_erode(g, Ty, SE, y0, 0);
486 for (int y = y0 + 1; y < y1; y++) {
487 update_min_lut(f, Ty, SE, y, 0, 1);
488 line_erode(g, Ty, SE, y, 0);
489 }
490
491 return 0;
492 }
493
494 static void difference(IPlane *g, IPlane *f, int y0, int y1)
495 {
496 for (int y = y0; y < y1; y++)
497 f->diff_in_place(g->img[y], f->img[y], f->w);
498 }
499
500 static void difference2(IPlane *g, IPlane *f, int y0, int y1)
501 {
502 for (int y = y0; y < y1; y++)
503 f->diff_rin_place(g->img[y], f->img[y], f->w);
504 }
505
506 static int insert_chord_set(chord_set *chords, chord c)
507 {
508 // Checking if chord fits in dynamic array, resize if not.
509 if (chords->size == chords->cap) {
510 chords->C = av_realloc_f(chords->C, chords->cap * 2, sizeof(chord));
511 if (!chords->C)
512 return AVERROR(ENOMEM);
513 chords->cap *= 2;
514 }
515
516 // Add the chord to the dynamic array.
517 chords->C[chords->size].x = c.x;
518 chords->C[chords->size].y = c.y;
519 chords->C[chords->size++].l = c.l;
520
521 // Update minimum/maximum x/y offsets of the chord set.
522 chords->minX = FFMIN(chords->minX, c.x);
523 chords->maxX = FFMAX(chords->maxX, c.x);
524
525 chords->minY = FFMIN(chords->minY, c.y);
526 chords->maxY = FFMAX(chords->maxY, c.y);
527
528 return 0;
529 }
530
531 static void free_chord_set(chord_set *SE)
532 {
533 av_freep(&SE->C);
534 SE->size = 0;
535 SE->cap = 0;
536
537 av_freep(&SE->R);
538 SE->Lnum = 0;
539 }
540
541 static int init_chordset(chord_set *chords)
542 {
543 chords->nb_elements = 0;
544 chords->size = 0;
545 chords->C = av_calloc(1, sizeof(chord));
546 if (!chords->C)
547 return AVERROR(ENOMEM);
548
549 chords->cap = 1;
550 chords->minX = INT16_MAX;
551 chords->maxX = INT16_MIN;
552 chords->minY = INT16_MAX;
553 chords->maxY = INT16_MIN;
554
555 return 0;
556 }
557
558 static int comp_chord_length(const void *p, const void *q)
559 {
560 chord a, b;
561 a = *((chord *)p);
562 b = *((chord *)q);
563
564 return (a.l > b.l) - (a.l < b.l);
565 }
566
567 static int comp_chord(const void *p, const void *q)
568 {
569 chord a, b;
570 a = *((chord *)p);
571 b = *((chord *)q);
572
573 return (a.y > b.y) - (a.y < b.y);
574 }
575
576 static int build_chord_set(IPlane *SE, chord_set *chords)
577 {
578 const int mid = 1 << (SE->depth - 1);
579 int chord_length_index;
580 int chord_start, val, ret;
581 int centerX, centerY;
582 int r_cap = 1;
583 chord c;
584
585 ret = init_chordset(chords);
586 if (ret < 0)
587 return ret;
588 /*
589 * In erosion/dilation, the center of the IPlane has S.E. offset (0,0).
590 * Otherwise, the resulting IPlane would be shifted to the top-left.
591 */
592 centerX = (SE->w - 1) / 2;
593 centerY = (SE->h - 1) / 2;
594
595 /*
596 * Computing the set of chords C.
597 */
598 for (int y = 0; y < SE->h; y++) {
599 int x;
600
601 chord_start = -1;
602 for (x = 0; x < SE->w; x++) {
603 if (SE->type_size == 1) {
604 chords->nb_elements += (SE->img[y][x] >= mid);
605 //A chord is a run of non-zero pixels.
606 if (SE->img[y][x] >= mid && chord_start == -1) {
607 // Chord starts.
608 chord_start = x;
609 } else if (SE->img[y][x] < mid && chord_start != -1) {
610 // Chord ends before end of line.
611 c.x = chord_start - centerX;
612 c.y = y - centerY;
613 c.l = x - chord_start;
614 ret = insert_chord_set(chords, c);
615 if (ret < 0)
616 return AVERROR(ENOMEM);
617 chord_start = -1;
618 }
619 } else {
620 chords->nb_elements += (AV_RN16(&SE->img[y][x * 2]) >= mid);
621 //A chord is a run of non-zero pixels.
622 if (AV_RN16(&SE->img[y][x * 2]) >= mid && chord_start == -1) {
623 // Chord starts.
624 chord_start = x;
625 } else if (AV_RN16(&SE->img[y][x * 2]) < mid && chord_start != -1) {
626 // Chord ends before end of line.
627 c.x = chord_start - centerX;
628 c.y = y - centerY;
629 c.l = x - chord_start;
630 ret = insert_chord_set(chords, c);
631 if (ret < 0)
632 return AVERROR(ENOMEM);
633 chord_start = -1;
634 }
635 }
636 }
637 if (chord_start != -1) {
638 // Chord ends at end of line.
639 c.x = chord_start - centerX;
640 c.y = y - centerY;
641 c.l = x - chord_start;
642 ret = insert_chord_set(chords, c);
643 if (ret < 0)
644 return AVERROR(ENOMEM);
645 }
646 }
647
648 /*
649 * Computing the array of chord lengths R(i).
650 * This is needed because the lookup table will contain a row for each
651 * length index i.
652 */
653 qsort(chords->C, chords->size, sizeof(chord), comp_chord_length);
654 chords->R = av_calloc(1, sizeof(*chords->R));
655 if (!chords->R)
656 return AVERROR(ENOMEM);
657 chords->Lnum = 0;
658 val = 0;
659 r_cap = 1;
660
661 if (chords->size > 0) {
662 val = 1;
663 if (chords->Lnum >= r_cap) {
664 chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R));
665 if (!chords->R)
666 return AVERROR(ENOMEM);
667 r_cap *= 2;
668 }
669 chords->R[chords->Lnum++] = 1;
670 val = 1;
671 }
672
673 for (int i = 0; i < chords->size; i++) {
674 if (val != chords->C[i].l) {
675 while (2 * val < chords->C[i].l && val != 0) {
676 if (chords->Lnum >= r_cap) {
677 chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R));
678 if (!chords->R)
679 return AVERROR(ENOMEM);
680 r_cap *= 2;
681 }
682
683 chords->R[chords->Lnum++] = 2 * val;
684 val *= 2;
685 }
686 val = chords->C[i].l;
687
688 if (chords->Lnum >= r_cap) {
689 chords->R = av_realloc_f(chords->R, r_cap * 2, sizeof(*chords->R));
690 if (!chords->R)
691 return AVERROR(ENOMEM);
692 r_cap *= 2;
693 }
694 chords->R[chords->Lnum++] = val;
695 }
696 }
697
698 /*
699 * Setting the length indices of chords.
700 * These are needed so that the algorithm can, for each chord,
701 * access the lookup table at the correct length in constant time.
702 */
703 chord_length_index = 0;
704 for (int i = 0; i < chords->size; i++) {
705 while (chords->R[chord_length_index] < chords->C[i].l)
706 chord_length_index++;
707 chords->C[i].i = chord_length_index;
708 }
709
710 /*
711 * Chords are sorted on Y. This way, when a row of the lookup table or IPlane
712 * is cached, the next chord offset has a better chance of being on the
713 * same cache line.
714 */
715 qsort(chords->C, chords->size, sizeof(chord), comp_chord);
716
717 return 0;
718 }
719
720 static void free_iplane(IPlane *imp)
721 {
722 av_freep(&imp->img);
723 }
724
725 static int read_iplane(IPlane *imp, const uint8_t *dst, int dst_linesize,
726 int w, int h, int R, int type_size, int depth)
727 {
728 if (!imp->img)
729 imp->img = av_calloc(h, sizeof(*imp->img));
730 if (!imp->img)
731 return AVERROR(ENOMEM);
732
733 imp->w = w;
734 imp->h = h;
735 imp->range = R;
736 imp->depth = depth;
737 imp->type_size = type_size;
738 imp->max_out_place = type_size == 1 ? max_fun : max16_fun;
739 imp->min_out_place = type_size == 1 ? min_fun : min16_fun;
740 imp->diff_rin_place = type_size == 1 ? diff_fun : diff16_fun;
741 imp->max_in_place = type_size == 1 ? maxinplace_fun : maxinplace16_fun;
742 imp->min_in_place = type_size == 1 ? mininplace_fun : mininplace16_fun;
743 imp->diff_in_place = type_size == 1 ? diffinplace_fun : diffinplace16_fun;
744
745 for (int y = 0; y < h; y++)
746 imp->img[y] = (uint8_t *)dst + y * dst_linesize;
747
748 return 0;
749 }
750
751 static int config_input(AVFilterLink *inlink)
752 {
753 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
754 MorphoContext *s = inlink->dst->priv;
755
756 s->depth = desc->comp[0].depth;
757 s->type_size = (s->depth + 7) / 8;
758 s->nb_planes = desc->nb_components;
759 s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
760 s->planewidth[0] = s->planewidth[3] = inlink->w;
761 s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
762 s->planeheight[0] = s->planeheight[3] = inlink->h;
763
764 return 0;
765 }
766
767 static int config_input_structure(AVFilterLink *inlink)
768 {
769 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
770 AVFilterContext *ctx = inlink->dst;
771 MorphoContext *s = inlink->dst->priv;
772
773 av_assert0(ctx->inputs[0]->format == ctx->inputs[1]->format);
774
775 s->splanewidth[1] = s->splanewidth[2] = AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
776 s->splanewidth[0] = s->splanewidth[3] = inlink->w;
777 s->splaneheight[1] = s->splaneheight[2] = AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
778 s->splaneheight[0] = s->splaneheight[3] = inlink->h;
779
780 return 0;
781 }
782
783 static int activate(AVFilterContext *ctx)
784 {
785 MorphoContext *s = ctx->priv;
786 return ff_framesync_activate(&s->fs);
787 }
788
789 typedef struct ThreadData {
790 AVFrame *in, *out;
791 } ThreadData;
792
793 static int morpho_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
794 {
795 MorphoContext *s = ctx->priv;
796 ThreadData *td = arg;
797 AVFrame *out = td->out;
798 AVFrame *in = td->in;
799 int ret;
800
801 for (int p = 0; p < s->nb_planes; p++) {
802 const int width = s->planewidth[p];
803 const int height = s->planeheight[p];
804 const int y0 = (height * jobnr ) / nb_jobs;
805 const int y1 = (height * (jobnr+1)) / nb_jobs;
806 const int depth = s->depth;
807
808 if (ctx->is_disabled || !(s->planes & (1 << p))) {
809 copy:
810 av_image_copy_plane(out->data[p] + y0 * out->linesize[p],
811 out->linesize[p],
812 in->data[p] + y0 * in->linesize[p],
813 in->linesize[p],
814 width * ((depth + 7) / 8),
815 y1 - y0);
816 continue;
817 }
818
819 if (s->SE[p].minX == INT16_MAX ||
820 s->SE[p].minY == INT16_MAX ||
821 s->SE[p].maxX == INT16_MIN ||
822 s->SE[p].maxY == INT16_MIN)
823 goto copy;
824
825 switch (s->mode) {
826 case ERODE:
827 ret = erode(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1);
828 break;
829 case DILATE:
830 case GRADIENT:
831 ret = dilate(&s->g[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1);
832 break;
833 case OPEN:
834 case TOPHAT:
835 ret = erode(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1);
836 break;
837 case CLOSE:
838 case BLACKHAT:
839 ret = dilate(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][0][p], y0, y1);
840 break;
841 default:
842 av_assert0(0);
843 }
844
845 if (ret < 0)
846 return ret;
847 }
848
849 return 0;
850 }
851
852 static int morpho_sliceX(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
853 {
854 MorphoContext *s = ctx->priv;
855 int ret;
856
857 for (int p = 0; p < s->nb_planes; p++) {
858 const int height = s->planeheight[p];
859 const int y0 = (height * jobnr ) / nb_jobs;
860 const int y1 = (height * (jobnr+1)) / nb_jobs;
861
862 if (ctx->is_disabled || !(s->planes & (1 << p))) {
863 copy:
864 continue;
865 }
866
867 if (s->SE[p].minX == INT16_MAX ||
868 s->SE[p].minY == INT16_MAX ||
869 s->SE[p].maxX == INT16_MIN ||
870 s->SE[p].maxY == INT16_MIN)
871 goto copy;
872
873 switch (s->mode) {
874 case OPEN:
875 ret = dilate(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1);
876 break;
877 case CLOSE:
878 ret = erode(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1);
879 break;
880 case GRADIENT:
881 ret = erode(&s->h[p], &s->f[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1);
882 if (ret < 0)
883 break;
884 difference(&s->g[p], &s->h[p], y0, y1);
885 break;
886 case TOPHAT:
887 ret = dilate(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1);
888 if (ret < 0)
889 break;
890 difference2(&s->g[p], &s->f[p], y0, y1);
891 break;
892 case BLACKHAT:
893 ret = erode(&s->g[p], &s->h[p], &s->SE[p], &s->Ty[jobnr][1][p], y0, y1);
894 if (ret < 0)
895 break;
896 difference(&s->g[p], &s->f[p], y0, y1);
897 break;
898 default:
899 av_assert0(0);
900 }
901
902 if (ret < 0)
903 return ret;
904 }
905
906 return 0;
907 }
908
909 static int do_morpho(FFFrameSync *fs)
910 {
911 AVFilterContext *ctx = fs->parent;
912 AVFilterLink *outlink = ctx->outputs[0];
913 MorphoContext *s = ctx->priv;
914 AVFrame *in = NULL, *structurepic = NULL;
915 ThreadData td;
916 AVFrame *out;
917 int ret;
918
919 ret = ff_framesync_dualinput_get(fs, &in, &structurepic);
920 if (ret < 0)
921 return ret;
922 if (!structurepic)
923 return ff_filter_frame(outlink, in);
924
925 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
926 if (!out) {
927 av_frame_free(&in);
928 return AVERROR(ENOMEM);
929 }
930 av_frame_copy_props(out, in);
931
932 for (int p = 0; p < s->nb_planes; p++) {
933 const uint8_t *ssrc = structurepic->data[p];
934 const int ssrc_linesize = structurepic->linesize[p];
935 const int swidth = s->splanewidth[p];
936 const int sheight = s->splaneheight[p];
937 const uint8_t *src = in->data[p];
938 int src_linesize = in->linesize[p];
939 uint8_t *dst = out->data[p];
940 int dst_linesize = out->linesize[p];
941 const int width = s->planewidth[p];
942 const int height = s->planeheight[p];
943 const int depth = s->depth;
944 int type_size = s->type_size;
945
946 if (!s->got_structure[p] || s->structures) {
947 free_chord_set(&s->SE[p]);
948
949 ret = read_iplane(&s->SEimg[p], ssrc, ssrc_linesize, swidth, sheight, 1, type_size, depth);
950 if (ret < 0)
951 goto fail;
952 ret = build_chord_set(&s->SEimg[p], &s->SE[p]);
953 if (ret < 0)
954 goto fail;
955 s->got_structure[p] = 1;
956 }
957
958 ret = read_iplane(&s->f[p], src, src_linesize, width, height, 1, type_size, depth);
959 if (ret < 0)
960 goto fail;
961
962 ret = read_iplane(&s->g[p], dst, dst_linesize, s->f[p].w, s->f[p].h, s->f[p].range, type_size, depth);
963 if (ret < 0)
964 goto fail;
965
966 switch (s->mode) {
967 case OPEN:
968 case CLOSE:
969 case GRADIENT:
970 case TOPHAT:
971 case BLACKHAT:
972 ret = read_iplane(&s->h[p], s->temp->data[p], s->temp->linesize[p], width, height, 1, type_size, depth);
973 break;
974 }
975
976 if (ret < 0)
977 goto fail;
978 }
979
980 td.in = in; td.out = out;
981 ret = ff_filter_execute(ctx, morpho_slice, &td, NULL,
982 FFMIN3(s->planeheight[1], s->planeheight[2],
983 FFMIN(MAX_THREADS, ff_filter_get_nb_threads(ctx))));
984 if (ret == 0 && (s->mode != ERODE && s->mode != DILATE)) {
985 ff_filter_execute(ctx, morpho_sliceX, NULL, NULL,
986 FFMIN3(s->planeheight[1], s->planeheight[2],
987 FFMIN(MAX_THREADS, ff_filter_get_nb_threads(ctx))));
988 }
989
990 av_frame_free(&in);
991 out->pts = av_rescale_q(s->fs.pts, s->fs.time_base, outlink->time_base);
992 return ff_filter_frame(outlink, out);
993 fail:
994 av_frame_free(&out);
995 av_frame_free(&in);
996 return ret;
997 }
998
999 static int config_output(AVFilterLink *outlink)
1000 {
1001 AVFilterContext *ctx = outlink->src;
1002 MorphoContext *s = ctx->priv;
1003 AVFilterLink *mainlink = ctx->inputs[0];
1004 int ret;
1005
1006 s->fs.on_event = do_morpho;
1007 ret = ff_framesync_init_dualinput(&s->fs, ctx);
1008 if (ret < 0)
1009 return ret;
1010 outlink->w = mainlink->w;
1011 outlink->h = mainlink->h;
1012 outlink->time_base = mainlink->time_base;
1013 outlink->sample_aspect_ratio = mainlink->sample_aspect_ratio;
1014 outlink->frame_rate = mainlink->frame_rate;
1015
1016 if ((ret = ff_framesync_configure(&s->fs)) < 0)
1017 return ret;
1018 outlink->time_base = s->fs.time_base;
1019
1020 s->temp = ff_get_video_buffer(outlink, outlink->w, outlink->h);
1021 if (!s->temp)
1022 return AVERROR(ENOMEM);
1023
1024 s->plane_f = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_f));
1025 s->plane_g = av_calloc(outlink->w * outlink->h, sizeof(*s->plane_g));
1026 if (!s->plane_f || !s->plane_g)
1027 return AVERROR(ENOMEM);
1028
1029 return 0;
1030 }
1031
1032 static av_cold void uninit(AVFilterContext *ctx)
1033 {
1034 MorphoContext *s = ctx->priv;
1035
1036 for (int p = 0; p < 4; p++) {
1037 free_iplane(&s->SEimg[p]);
1038 free_iplane(&s->f[p]);
1039 free_iplane(&s->g[p]);
1040 free_iplane(&s->h[p]);
1041 free_chord_set(&s->SE[p]);
1042 for (int n = 0; n < MAX_THREADS; n++) {
1043 free_lut(&s->Ty[n][0][p]);
1044 free_lut(&s->Ty[n][1][p]);
1045 }
1046 }
1047
1048 ff_framesync_uninit(&s->fs);
1049
1050 av_frame_free(&s->temp);
1051 av_freep(&s->plane_f);
1052 av_freep(&s->plane_g);
1053 }
1054
1055 static const AVFilterPad morpho_inputs[] = {
1056 {
1057 .name = "default",
1058 .type = AVMEDIA_TYPE_VIDEO,
1059 .config_props = config_input,
1060 },
1061 {
1062 .name = "structure",
1063 .type = AVMEDIA_TYPE_VIDEO,
1064 .config_props = config_input_structure,
1065 },
1066 };
1067
1068 static const AVFilterPad morpho_outputs[] = {
1069 {
1070 .name = "default",
1071 .type = AVMEDIA_TYPE_VIDEO,
1072 .config_props = config_output,
1073 },
1074 };
1075
1076 const AVFilter ff_vf_morpho = {
1077 .name = "morpho",
1078 .description = NULL_IF_CONFIG_SMALL("Apply Morphological filter."),
1079 .preinit = morpho_framesync_preinit,
1080 .priv_size = sizeof(MorphoContext),
1081 .priv_class = &morpho_class,
1082 .activate = activate,
1083 .uninit = uninit,
1084 FILTER_INPUTS(morpho_inputs),
1085 FILTER_OUTPUTS(morpho_outputs),
1086 FILTER_PIXFMTS_ARRAY(pix_fmts),
1087 .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL |
1088 AVFILTER_FLAG_SLICE_THREADS,
1089 .process_command = ff_filter_process_command,
1090 };
1091