2 * Copyright (c) 2016 ReneBrals
3 * Copyright (c) 2021 Paul B Mahol
5 * This file is part of FFmpeg.
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:
14 * The above copyright notice and this permission notice shall be included in all
15 * copies or substantial portions of the Software.
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
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"
34 #include "framesync.h"
48 typedef struct IPlane
{
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
);
64 /* arr is shifted from base_arr by FFMAX(min_r, 0).
65 * arr != NULL means "lut completely allocated" */
76 typedef struct chord
{
83 typedef struct chord_set
{
98 #define MAX_THREADS 64
100 typedef struct MorphoContext
{
101 const AVClass
*class;
106 IPlane g
[4], f
[4], h
[4];
107 LUT Ty
[MAX_THREADS
][2][4];
121 int got_structure
[4];
125 int64_t *plane_f
, *plane_g
;
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
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" },
147 FRAMESYNC_DEFINE_CLASS(morpho
, MorphoContext
, fs
);
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
,
171 static void min_fun(uint8_t *c
, const uint8_t *a
, const uint8_t *b
, int x
)
173 for (int i
= 0; i
< x
; i
++)
174 c
[i
] = FFMIN(b
[i
], a
[i
]);
177 static void mininplace_fun(uint8_t *a
, const uint8_t *b
, int x
)
179 for (int i
= 0; i
< x
; i
++)
180 a
[i
] = FFMIN(a
[i
], b
[i
]);
183 static void max_fun(uint8_t *c
, const uint8_t *a
, const uint8_t *b
, int x
)
185 for (int i
= 0; i
< x
; i
++)
186 c
[i
] = FFMAX(a
[i
], b
[i
]);
189 static void maxinplace_fun(uint8_t *a
, const uint8_t *b
, int x
)
191 for (int i
= 0; i
< x
; i
++)
192 a
[i
] = FFMAX(a
[i
], b
[i
]);
195 static void diff_fun(uint8_t *a
, const uint8_t *b
, int x
)
197 for (int i
= 0; i
< x
; i
++)
198 a
[i
] = FFMAX(b
[i
] - a
[i
], 0);
201 static void diffinplace_fun(uint8_t *a
, const uint8_t *b
, int x
)
203 for (int i
= 0; i
< x
; i
++)
204 a
[i
] = FFMAX(a
[i
] - b
[i
], 0);
207 static void min16_fun(uint8_t *cc
, const uint8_t *aa
, const uint8_t *bb
, int x
)
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
;
213 for (int i
= 0; i
< x
; i
++)
214 c
[i
] = FFMIN(b
[i
], a
[i
]);
217 static void mininplace16_fun(uint8_t *aa
, const uint8_t *bb
, int x
)
219 uint16_t *a
= (uint16_t *)aa
;
220 const uint16_t *b
= (const uint16_t *)bb
;
222 for (int i
= 0; i
< x
; i
++)
223 a
[i
] = FFMIN(a
[i
], b
[i
]);
226 static void diff16_fun(uint8_t *aa
, const uint8_t *bb
, int x
)
228 const uint16_t *b
= (const uint16_t *)bb
;
229 uint16_t *a
= (uint16_t *)aa
;
231 for (int i
= 0; i
< x
; i
++)
232 a
[i
] = FFMAX(b
[i
] - a
[i
], 0);
235 static void diffinplace16_fun(uint8_t *aa
, const uint8_t *bb
, int x
)
237 uint16_t *a
= (uint16_t *)aa
;
238 const uint16_t *b
= (const uint16_t *)bb
;
240 for (int i
= 0; i
< x
; i
++)
241 a
[i
] = FFMAX(a
[i
] - b
[i
], 0);
244 static void max16_fun(uint8_t *cc
, const uint8_t *aa
, const uint8_t *bb
, int x
)
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
;
250 for (int i
= 0; i
< x
; i
++)
251 c
[i
] = FFMAX(a
[i
], b
[i
]);
254 static void maxinplace16_fun(uint8_t *aa
, const uint8_t *bb
, int x
)
256 uint16_t *a
= (uint16_t *)aa
;
257 const uint16_t *b
= (const uint16_t *)bb
;
259 for (int i
= 0; i
< x
; i
++)
260 a
[i
] = FFMAX(a
[i
], b
[i
]);
263 static int alloc_lut(LUT
*Ty
, chord_set
*SE
, int type_size
, int mode
)
265 const int min
= FFMAX(Ty
->min_r
, 0);
266 const int max
= min
+ (Ty
->max_r
- Ty
->min_r
);
270 pre_pad_x
= 0 - SE
->minX
;
271 Ty
->pre_pad_x
= pre_pad_x
;
272 Ty
->type_size
= type_size
;
274 Ty
->base_arr
= av_calloc(max
+ 1, sizeof(*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
);
284 return AVERROR(ENOMEM
);
286 memset(arr
[i
], UINT8_MAX
, pre_pad_x
* type_size
);
287 /* Shifting the X index such that negative indices correspond to
290 arr
[i
] = &(arr
[i
][pre_pad_x
* type_size
]);
294 Ty
->arr
= &(Ty
->base_arr
[min
- Ty
->min_r
]);
299 static void free_lut(LUT
*table
)
301 const int min
= FFMAX(table
->min_r
, 0);
302 const int max
= min
+ (table
->max_r
- table
->min_r
);
304 if (!table
->base_arr
)
307 for (int r
= min
; r
<= max
; r
++) {
308 if (!table
->base_arr
[r
])
310 for (int i
= 0; i
< table
->I
; i
++) {
311 if (!table
->base_arr
[r
][i
])
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
);
316 av_freep(&table
->base_arr
[r
]);
318 av_freep(&table
->base_arr
);
322 static int alloc_lut_if_necessary(LUT
*Ty
, IPlane
*f
, chord_set
*SE
,
323 int num
, enum MorphModes mode
)
325 if (!Ty
->arr
|| Ty
->I
!= SE
->Lnum
||
327 SE
->minX
< 0 && -SE
->minX
> Ty
->pre_pad_x
||
328 Ty
->min_r
!= SE
->minY
||
329 Ty
->max_r
!= SE
->maxY
+ num
- 1) {
336 Ty
->min_r
= SE
->minY
;
337 Ty
->max_r
= SE
->maxY
+ num
- 1;
338 ret
= alloc_lut(Ty
, SE
, f
->type_size
, mode
);
345 static void circular_swap(LUT
*Ty
)
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.
351 if (Ty
->max_r
- Ty
->min_r
> 0) {
352 uint8_t **Ty0
= Ty
->arr
[Ty
->min_r
];
354 for (int r
= Ty
->min_r
; r
< Ty
->max_r
; r
++)
355 Ty
->arr
[r
] = Ty
->arr
[r
+ 1];
357 Ty
->arr
[Ty
->max_r
] = Ty0
;
361 static void compute_min_row(IPlane
*f
, LUT
*Ty
, chord_set
*SE
, int r
, int y
)
363 if (y
+ r
>= 0 && y
+ r
< f
->h
) {
364 memcpy(Ty
->arr
[r
][0], f
->img
[y
+ r
], Ty
->X
* Ty
->type_size
);
366 memset(Ty
->arr
[r
][0], UINT8_MAX
, Ty
->X
* Ty
->type_size
);
369 for (int i
= 1; i
< SE
->Lnum
; i
++) {
370 int d
= SE
->R
[i
] - SE
->R
[i
- 1];
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
,
382 static void update_min_lut(IPlane
*f
, LUT
*Ty
, chord_set
*SE
, int y
, int tid
, int num
)
384 for (int i
= 0; i
< num
; i
++)
387 compute_min_row(f
, Ty
, SE
, Ty
->max_r
- tid
, y
);
390 static int compute_min_lut(LUT
*Ty
, IPlane
*f
, chord_set
*SE
, int y
, int num
)
392 int ret
= alloc_lut_if_necessary(Ty
, f
, SE
, num
, ERODE
);
396 for (int r
= Ty
->min_r
; r
<= Ty
->max_r
; r
++)
397 compute_min_row(f
, Ty
, SE
, r
, y
);
402 static void compute_max_row(IPlane
*f
, LUT
*Ty
, chord_set
*SE
, int r
, int y
)
404 if (y
+ r
>= 0 && y
+ r
< f
->h
) {
405 memcpy(Ty
->arr
[r
][0], f
->img
[y
+ r
], Ty
->X
* Ty
->type_size
);
407 memset(Ty
->arr
[r
][0], 0, Ty
->X
* Ty
->type_size
);
410 for (int i
= 1; i
< SE
->Lnum
; i
++) {
411 int d
= SE
->R
[i
] - SE
->R
[i
- 1];
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
,
423 static void update_max_lut(IPlane
*f
, LUT
*Ty
, chord_set
*SE
, int y
, int tid
, int num
)
425 for (int i
= 0; i
< num
; i
++)
428 compute_max_row(f
, Ty
, SE
, Ty
->max_r
- tid
, y
);
431 static int compute_max_lut(LUT
*Ty
, IPlane
*f
, chord_set
*SE
, int y
, int num
)
433 int ret
= alloc_lut_if_necessary(Ty
, f
, SE
, num
, DILATE
);
437 for (int r
= Ty
->min_r
; r
<= Ty
->max_r
; r
++)
438 compute_max_row(f
, Ty
, SE
, r
, y
);
443 static void line_dilate(IPlane
*g
, LUT
*Ty
, chord_set
*SE
, int y
, int tid
)
445 memset(g
->img
[y
], 0, g
->w
* g
->type_size
);
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
));
454 static void line_erode(IPlane
*g
, LUT
*Ty
, chord_set
*SE
, int y
, int tid
)
456 memset(g
->img
[y
], UINT8_MAX
, g
->w
* g
->type_size
);
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
));
465 static int dilate(IPlane
*g
, IPlane
*f
, chord_set
*SE
, LUT
*Ty
, int y0
, int y1
)
467 int ret
= compute_max_lut(Ty
, f
, SE
, y0
, 1);
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);
480 static int erode(IPlane
*g
, IPlane
*f
, chord_set
*SE
, LUT
*Ty
, int y0
, int y1
)
482 int ret
= compute_min_lut(Ty
, f
, SE
, y0
, 1);
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);
495 static void difference(IPlane
*g
, IPlane
*f
, int y0
, int y1
)
497 for (int y
= y0
; y
< y1
; y
++)
498 f
->diff_in_place(g
->img
[y
], f
->img
[y
], f
->w
);
501 static void difference2(IPlane
*g
, IPlane
*f
, int y0
, int y1
)
503 for (int y
= y0
; y
< y1
; y
++)
504 f
->diff_rin_place(g
->img
[y
], f
->img
[y
], f
->w
);
507 static int insert_chord_set(chord_set
*chords
, chord c
)
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
));
513 return AVERROR(ENOMEM
);
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
;
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
);
526 chords
->minY
= FFMIN(chords
->minY
, c
.y
);
527 chords
->maxY
= FFMAX(chords
->maxY
, c
.y
);
532 static void free_chord_set(chord_set
*SE
)
542 static int init_chordset(chord_set
*chords
)
544 chords
->nb_elements
= 0;
546 chords
->C
= av_calloc(1, sizeof(chord
));
548 return AVERROR(ENOMEM
);
551 chords
->minX
= INT16_MAX
;
552 chords
->maxX
= INT16_MIN
;
553 chords
->minY
= INT16_MAX
;
554 chords
->maxY
= INT16_MIN
;
559 static int comp_chord_length(const void *p
, const void *q
)
565 return (a
.l
> b
.l
) - (a
.l
< b
.l
);
568 static int comp_chord(const void *p
, const void *q
)
574 return (a
.y
> b
.y
) - (a
.y
< b
.y
);
577 static int build_chord_set(IPlane
*SE
, chord_set
*chords
)
579 const int mid
= 1 << (SE
->depth
- 1);
580 int chord_length_index
;
581 int chord_start
, val
, ret
;
582 int centerX
, centerY
;
586 ret
= init_chordset(chords
);
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.
593 centerX
= (SE
->w
- 1) / 2;
594 centerY
= (SE
->h
- 1) / 2;
597 * Computing the set of chords C.
599 for (int y
= 0; y
< SE
->h
; y
++) {
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) {
610 } else if (SE
->img
[y
][x
] < mid
&& chord_start
!= -1) {
611 // Chord ends before end of line.
612 c
.x
= chord_start
- centerX
;
614 c
.l
= x
- chord_start
;
615 ret
= insert_chord_set(chords
, c
);
617 return AVERROR(ENOMEM
);
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) {
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
;
630 c
.l
= x
- chord_start
;
631 ret
= insert_chord_set(chords
, c
);
633 return AVERROR(ENOMEM
);
638 if (chord_start
!= -1) {
639 // Chord ends at end of line.
640 c
.x
= chord_start
- centerX
;
642 c
.l
= x
- chord_start
;
643 ret
= insert_chord_set(chords
, c
);
645 return AVERROR(ENOMEM
);
650 * Computing the array of chord lengths R(i).
651 * This is needed because the lookup table will contain a row for each
654 qsort(chords
->C
, chords
->size
, sizeof(chord
), comp_chord_length
);
655 chords
->R
= av_calloc(1, sizeof(*chords
->R
));
657 return AVERROR(ENOMEM
);
662 if (chords
->size
> 0) {
664 if (chords
->Lnum
>= r_cap
) {
665 chords
->R
= av_realloc_f(chords
->R
, r_cap
* 2, sizeof(*chords
->R
));
667 return AVERROR(ENOMEM
);
670 chords
->R
[chords
->Lnum
++] = 1;
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
));
680 return AVERROR(ENOMEM
);
684 chords
->R
[chords
->Lnum
++] = 2 * val
;
687 val
= chords
->C
[i
].l
;
689 if (chords
->Lnum
>= r_cap
) {
690 chords
->R
= av_realloc_f(chords
->R
, r_cap
* 2, sizeof(*chords
->R
));
692 return AVERROR(ENOMEM
);
695 chords
->R
[chords
->Lnum
++] = val
;
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.
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
;
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
716 qsort(chords
->C
, chords
->size
, sizeof(chord
), comp_chord
);
721 static void free_iplane(IPlane
*imp
)
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
)
730 imp
->img
= av_calloc(h
, sizeof(*imp
->img
));
732 return AVERROR(ENOMEM
);
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
;
746 for (int y
= 0; y
< h
; y
++)
747 imp
->img
[y
] = (uint8_t *)dst
+ y
* dst_linesize
;
752 static int config_input(AVFilterLink
*inlink
)
754 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
755 MorphoContext
*s
= inlink
->dst
->priv
;
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
;
768 static int config_input_structure(AVFilterLink
*inlink
)
770 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
771 AVFilterContext
*ctx
= inlink
->dst
;
772 MorphoContext
*s
= inlink
->dst
->priv
;
774 av_assert0(ctx
->inputs
[0]->format
== ctx
->inputs
[1]->format
);
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
;
784 static int activate(AVFilterContext
*ctx
)
786 MorphoContext
*s
= ctx
->priv
;
787 return ff_framesync_activate(&s
->fs
);
790 typedef struct ThreadData
{
794 static int morpho_slice(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
796 MorphoContext
*s
= ctx
->priv
;
797 ThreadData
*td
= arg
;
798 AVFrame
*out
= td
->out
;
799 AVFrame
*in
= td
->in
;
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
= (height
* jobnr
) / nb_jobs
;
806 const int y1
= (height
* (jobnr
+1)) / nb_jobs
;
807 const int depth
= s
->depth
;
809 if (ctx
->is_disabled
|| !(s
->planes
& (1 << p
))) {
811 av_image_copy_plane(out
->data
[p
] + y0
* out
->linesize
[p
],
813 in
->data
[p
] + y0
* in
->linesize
[p
],
815 width
* ((depth
+ 7) / 8),
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
)
828 ret
= erode(&s
->g
[p
], &s
->f
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][0][p
], y0
, y1
);
832 ret
= dilate(&s
->g
[p
], &s
->f
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][0][p
], y0
, y1
);
836 ret
= erode(&s
->h
[p
], &s
->f
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][0][p
], y0
, y1
);
840 ret
= dilate(&s
->h
[p
], &s
->f
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][0][p
], y0
, y1
);
853 static int morpho_sliceX(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
855 MorphoContext
*s
= ctx
->priv
;
858 for (int p
= 0; p
< s
->nb_planes
; p
++) {
859 const int height
= s
->planeheight
[p
];
860 const int y0
= (height
* jobnr
) / nb_jobs
;
861 const int y1
= (height
* (jobnr
+1)) / nb_jobs
;
863 if (ctx
->is_disabled
|| !(s
->planes
& (1 << p
))) {
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
)
876 ret
= dilate(&s
->g
[p
], &s
->h
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][1][p
], y0
, y1
);
879 ret
= erode(&s
->g
[p
], &s
->h
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][1][p
], y0
, y1
);
882 ret
= erode(&s
->h
[p
], &s
->f
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][1][p
], y0
, y1
);
885 difference(&s
->g
[p
], &s
->h
[p
], y0
, y1
);
888 ret
= dilate(&s
->g
[p
], &s
->h
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][1][p
], y0
, y1
);
891 difference2(&s
->g
[p
], &s
->f
[p
], y0
, y1
);
894 ret
= erode(&s
->g
[p
], &s
->h
[p
], &s
->SE
[p
], &s
->Ty
[jobnr
][1][p
], y0
, y1
);
897 difference(&s
->g
[p
], &s
->f
[p
], y0
, y1
);
910 static int do_morpho(FFFrameSync
*fs
)
912 AVFilterContext
*ctx
= fs
->parent
;
913 AVFilterLink
*outlink
= ctx
->outputs
[0];
914 MorphoContext
*s
= ctx
->priv
;
915 AVFrame
*in
= NULL
, *structurepic
= NULL
;
920 ret
= ff_framesync_dualinput_get(fs
, &in
, &structurepic
);
924 return ff_filter_frame(outlink
, in
);
926 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
929 return AVERROR(ENOMEM
);
931 av_frame_copy_props(out
, in
);
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
;
947 if (!s
->got_structure
[p
] || s
->structures
) {
948 free_chord_set(&s
->SE
[p
]);
950 ret
= read_iplane(&s
->SEimg
[p
], ssrc
, ssrc_linesize
, swidth
, sheight
, 1, type_size
, depth
);
953 ret
= build_chord_set(&s
->SEimg
[p
], &s
->SE
[p
]);
956 s
->got_structure
[p
] = 1;
959 ret
= read_iplane(&s
->f
[p
], src
, src_linesize
, width
, height
, 1, type_size
, depth
);
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
);
973 ret
= read_iplane(&s
->h
[p
], s
->temp
->data
[p
], s
->temp
->linesize
[p
], width
, height
, 1, type_size
, depth
);
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
))));
992 out
->pts
= av_rescale_q(s
->fs
.pts
, s
->fs
.time_base
, outlink
->time_base
);
993 return ff_filter_frame(outlink
, out
);
1000 static int config_output(AVFilterLink
*outlink
)
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
);
1009 s
->fs
.on_event
= do_morpho
;
1010 ret
= ff_framesync_init_dualinput(&s
->fs
, ctx
);
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
;
1019 if ((ret
= ff_framesync_configure(&s
->fs
)) < 0)
1021 outlink
->time_base
= s
->fs
.time_base
;
1023 s
->temp
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
1025 return AVERROR(ENOMEM
);
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
);
1035 static av_cold
void uninit(AVFilterContext
*ctx
)
1037 MorphoContext
*s
= ctx
->priv
;
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
]);
1051 ff_framesync_uninit(&s
->fs
);
1053 av_frame_free(&s
->temp
);
1054 av_freep(&s
->plane_f
);
1055 av_freep(&s
->plane_g
);
1058 static const AVFilterPad morpho_inputs
[] = {
1061 .type
= AVMEDIA_TYPE_VIDEO
,
1062 .config_props
= config_input
,
1065 .name
= "structure",
1066 .type
= AVMEDIA_TYPE_VIDEO
,
1067 .config_props
= config_input_structure
,
1071 static const AVFilterPad morpho_outputs
[] = {
1074 .type
= AVMEDIA_TYPE_VIDEO
,
1075 .config_props
= config_output
,
1079 const FFFilter ff_vf_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
,
1089 FILTER_INPUTS(morpho_inputs
),
1090 FILTER_OUTPUTS(morpho_outputs
),
1091 FILTER_PIXFMTS_ARRAY(pix_fmts
),
1092 .process_command
= ff_filter_process_command
,