History

avfilter: add morpho filter

Paul B Mahol committed 5 years ago

Commit b2ec4ed1 parent c2057786 files · +982 −1Browse files

Showing the changes to libavfilter (4 of 6 files). Show all

Unified · Split

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

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t