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
344OBJS-$(CONFIG_MINTERPOLATE_FILTER) += vf_minterpolate.o motion_estimation.o344OBJS-$(CONFIG_MINTERPOLATE_FILTER) += vf_minterpolate.o motion_estimation.o
345OBJS-$(CONFIG_MIX_FILTER) += vf_mix.o framesync.o345OBJS-$(CONFIG_MIX_FILTER) += vf_mix.o framesync.o
346OBJS-$(CONFIG_MONOCHROME_FILTER) += vf_monochrome.o346OBJS-$(CONFIG_MONOCHROME_FILTER) += vf_monochrome.o
347OBJS-$(CONFIG_MORPHO_FILTER) += vf_morpho.o
347OBJS-$(CONFIG_MPDECIMATE_FILTER) += vf_mpdecimate.o348OBJS-$(CONFIG_MPDECIMATE_FILTER) += vf_mpdecimate.o
348OBJS-$(CONFIG_NEGATE_FILTER) += vf_lut.o349OBJS-$(CONFIG_NEGATE_FILTER) += vf_lut.o
349OBJS-$(CONFIG_NLMEANS_FILTER) += vf_nlmeans.o350OBJS-$(CONFIG_NLMEANS_FILTER) += vf_nlmeans.o
libavfilter/allfilters.c+1−0
@@ -329,6 +329,7 @@ extern const AVFilter ff_vf_midequalizer; · 328 unmodified lines
329extern const AVFilter ff_vf_minterpolate;329extern const AVFilter ff_vf_minterpolate;
330extern const AVFilter ff_vf_mix;330extern const AVFilter ff_vf_mix;
331extern const AVFilter ff_vf_monochrome;331extern const AVFilter ff_vf_monochrome;
332extern const AVFilter ff_vf_morpho;
332extern const AVFilter ff_vf_mpdecimate;333extern const AVFilter ff_vf_mpdecimate;
333extern const AVFilter ff_vf_msad;334extern const AVFilter ff_vf_msad;
334extern const AVFilter ff_vf_negate;335extern const AVFilter ff_vf_negate;
libavfilter/version.h+1−1
@@ -30,7 +30,7 @@ · 29 unmodified lines
30#include "libavutil/version.h"30#include "libavutil/version.h"
31 31
32#define LIBAVFILTER_VERSION_MAJOR 832#define LIBAVFILTER_VERSION_MAJOR 8
33#define LIBAVFILTER_VERSION_MINOR 933#define LIBAVFILTER_VERSION_MINOR 10
34#define LIBAVFILTER_VERSION_MICRO 10034#define LIBAVFILTER_VERSION_MICRO 100
35 35
36 36
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
37enum MorphModes {
38 ERODE,
39 DILATE,
40 OPEN,
41 CLOSE,
42 NB_MODES
43};
44
45typedef 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
58typedef 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
68typedef struct chord {
69 int x;
70 int y;
71 int l;
72 int i;
73} chord;
74
75typedef 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
90typedef 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
121static 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
134FRAMESYNC_DEFINE_CLASS(morpho, MorphoContext, fs);
135
136static 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
163static 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
169static 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
175static 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
181static 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
187static 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
197static 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
206static 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
216static 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
225static 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
260static 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
280static 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
296static 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
317static 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
325static 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
350static 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
371static 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
379static 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
404static 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
415static 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
426static 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
441static 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
456static 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
481static 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
491static 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
508static 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
517static 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
526static 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
670static void free_iplane(IPlane *imp)
671{
672 av_freep(&imp->img);
673}
674
675static 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
699static 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
715static 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
731static int activate(AVFilterContext *ctx)
732{
733 MorphoContext *s = ctx->priv;
734 return ff_framesync_activate(&s->fs);
735}
736
737static 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))) {
774copy:
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);
846fail:
847 av_frame_free(&in);
848 return ret;
849}
850
851static 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
884static 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
905static 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
918static const AVFilterPad morpho_outputs[] = {
919 {
920 .name = "default",
921 .type = AVMEDIA_TYPE_VIDEO,
922 .config_props = config_output,
923 },
924};
925
926const 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