FFmpeg coverage


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