2 * Copyright (c) 2017 Ming Yang
3 * Copyright (c) 2019 Paul B Mahol
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included in all
13 * copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 #include "libavutil/imgutils.h"
25 #include "libavutil/mem.h"
26 #include "libavutil/opt.h"
27 #include "libavutil/pixdesc.h"
32 typedef struct BilateralContext
{
46 float range_table
[65536];
50 float *map_factor_a
[4];
51 float *map_factor_b
[4];
52 float *slice_factor_a
[4];
53 float *slice_factor_b
[4];
54 float *line_factor_a
[4];
55 float *line_factor_b
[4];
58 #define OFFSET(x) offsetof(BilateralContext, x)
59 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
61 static const AVOption bilateral_options
[] = {
62 { "sigmaS", "set spatial sigma", OFFSET(sigmaS
), AV_OPT_TYPE_FLOAT
, {.dbl
=0.1}, 0.0, 512, FLAGS
},
63 { "sigmaR", "set range sigma", OFFSET(sigmaR
), AV_OPT_TYPE_FLOAT
, {.dbl
=0.1}, 0.0, 1, FLAGS
},
64 { "planes", "set planes to filter", OFFSET(planes
), AV_OPT_TYPE_INT
, {.i64
=1}, 0, 0xF, FLAGS
},
68 AVFILTER_DEFINE_CLASS(bilateral
);
70 static const enum AVPixelFormat pix_fmts
[] = {
71 AV_PIX_FMT_YUVA444P
, AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV440P
,
72 AV_PIX_FMT_YUVJ444P
, AV_PIX_FMT_YUVJ440P
,
73 AV_PIX_FMT_YUVA422P
, AV_PIX_FMT_YUV422P
, AV_PIX_FMT_YUVA420P
, AV_PIX_FMT_YUV420P
,
74 AV_PIX_FMT_YUVJ422P
, AV_PIX_FMT_YUVJ420P
,
75 AV_PIX_FMT_YUVJ411P
, AV_PIX_FMT_YUV411P
, AV_PIX_FMT_YUV410P
,
76 AV_PIX_FMT_YUV420P9
, AV_PIX_FMT_YUV422P9
, AV_PIX_FMT_YUV444P9
,
77 AV_PIX_FMT_YUV420P10
, AV_PIX_FMT_YUV422P10
, AV_PIX_FMT_YUV444P10
,
78 AV_PIX_FMT_YUV420P12
, AV_PIX_FMT_YUV422P12
, AV_PIX_FMT_YUV444P12
, AV_PIX_FMT_YUV440P12
,
79 AV_PIX_FMT_YUV420P14
, AV_PIX_FMT_YUV422P14
, AV_PIX_FMT_YUV444P14
,
80 AV_PIX_FMT_YUV420P16
, AV_PIX_FMT_YUV422P16
, AV_PIX_FMT_YUV444P16
,
81 AV_PIX_FMT_YUVA420P9
, AV_PIX_FMT_YUVA422P9
, AV_PIX_FMT_YUVA444P9
,
82 AV_PIX_FMT_YUVA420P10
, AV_PIX_FMT_YUVA422P10
, AV_PIX_FMT_YUVA444P10
,
83 AV_PIX_FMT_YUVA420P16
, AV_PIX_FMT_YUVA422P16
, AV_PIX_FMT_YUVA444P16
,
84 AV_PIX_FMT_GBRP
, AV_PIX_FMT_GBRP9
, AV_PIX_FMT_GBRP10
,
85 AV_PIX_FMT_GBRP12
, AV_PIX_FMT_GBRP14
, AV_PIX_FMT_GBRP16
,
86 AV_PIX_FMT_GBRAP
, AV_PIX_FMT_GBRAP10
, AV_PIX_FMT_GBRAP12
, AV_PIX_FMT_GBRAP16
,
87 AV_PIX_FMT_GRAY8
, AV_PIX_FMT_GRAY9
, AV_PIX_FMT_GRAY10
, AV_PIX_FMT_GRAY12
, AV_PIX_FMT_GRAY14
, AV_PIX_FMT_GRAY16
,
91 static int config_params(AVFilterContext
*ctx
)
93 BilateralContext
*s
= ctx
->priv
;
94 float inv_sigma_range
;
96 inv_sigma_range
= 1.0f
/ (s
->sigmaR
* ((1 << s
->depth
) - 1));
97 s
->alpha
= expf(-sqrtf(2.f
) / s
->sigmaS
);
99 //compute a lookup table
100 for (int i
= 0; i
< (1 << s
->depth
); i
++)
101 s
->range_table
[i
] = s
->alpha
* expf(-i
* inv_sigma_range
);
106 typedef struct ThreadData
{
110 static int config_input(AVFilterLink
*inlink
)
112 AVFilterContext
*ctx
= inlink
->dst
;
113 BilateralContext
*s
= ctx
->priv
;
114 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
116 s
->depth
= desc
->comp
[0].depth
;
119 s
->planewidth
[1] = s
->planewidth
[2] = AV_CEIL_RSHIFT(inlink
->w
, desc
->log2_chroma_w
);
120 s
->planewidth
[0] = s
->planewidth
[3] = inlink
->w
;
121 s
->planeheight
[1] = s
->planeheight
[2] = AV_CEIL_RSHIFT(inlink
->h
, desc
->log2_chroma_h
);
122 s
->planeheight
[0] = s
->planeheight
[3] = inlink
->h
;
124 s
->nb_planes
= av_pix_fmt_count_planes(inlink
->format
);
125 s
->nb_threads
= ff_filter_get_nb_threads(ctx
);
127 for (int p
= 0; p
< s
->nb_planes
; p
++) {
128 const int w
= s
->planewidth
[p
];
129 const int h
= s
->planeheight
[p
];
131 s
->img_out_f
[p
] = av_calloc(w
* h
, sizeof(float));
132 s
->img_temp
[p
] = av_calloc(w
* h
, sizeof(float));
133 s
->map_factor_a
[p
] = av_calloc(w
* h
, sizeof(float));
134 s
->map_factor_b
[p
] = av_calloc(w
* h
, sizeof(float));
135 s
->slice_factor_a
[p
] = av_calloc(w
, sizeof(float));
136 s
->slice_factor_b
[p
] = av_calloc(w
, sizeof(float));
137 s
->line_factor_a
[p
] = av_calloc(w
, sizeof(float));
138 s
->line_factor_b
[p
] = av_calloc(w
, sizeof(float));
140 if (!s
->img_out_f
[p
] ||
142 !s
->map_factor_a
[p
] ||
143 !s
->map_factor_b
[p
] ||
144 !s
->slice_factor_a
[p
] ||
145 !s
->slice_factor_a
[p
] ||
146 !s
->line_factor_a
[p
] ||
147 !s
->line_factor_a
[p
])
148 return AVERROR(ENOMEM
);
154 #define BILATERAL_H(type, name) \
155 static void bilateralh_##name(BilateralContext *s, AVFrame *out, AVFrame *in, \
156 int jobnr, int nb_jobs, int plane) \
158 const int width = s->planewidth[plane]; \
159 const int height = s->planeheight[plane]; \
160 const int slice_start = (height * jobnr) / nb_jobs; \
161 const int slice_end = (height * (jobnr+1)) / nb_jobs; \
162 const int src_linesize = in->linesize[plane] / sizeof(type); \
163 const type *src = (const type *)in->data[plane]; \
164 float *img_temp = s->img_temp[plane]; \
165 float *map_factor_a = s->map_factor_a[plane]; \
166 const float *const range_table = s->range_table; \
167 const float alpha = s->alpha; \
168 float ypr, ycr, fp, fc; \
169 const float inv_alpha_ = 1.f - alpha; \
171 for (int y = slice_start; y < slice_end; y++) { \
172 float *temp_factor_x, *temp_x = &img_temp[y * width]; \
173 const type *in_x = &src[y * src_linesize]; \
174 const type *texture_x = &src[y * src_linesize]; \
177 *temp_x++ = ypr = *in_x++; \
178 tpr = *texture_x++; \
180 temp_factor_x = &map_factor_a[y * width]; \
181 *temp_factor_x++ = fp = 1; \
183 for (int x = 1; x < width; x++) { \
186 type tcr = *texture_x++; \
187 type dr = abs(tcr - tpr); \
190 alpha_ = range_table[range_dist]; \
191 *temp_x++ = ycr = inv_alpha_*(*in_x++) + alpha_*ypr; \
194 *temp_factor_x++ = fc = inv_alpha_ + alpha_ * fp; \
197 --temp_x; *temp_x = ((*temp_x) + (*--in_x)); \
198 tpr = *--texture_x; \
201 --temp_factor_x; *temp_factor_x = ((*temp_factor_x) + 1); \
204 for (int x = width - 2; x >= 0; x--) { \
205 type tcr = *--texture_x; \
206 type dr = abs(tcr - tpr); \
207 int range_dist = dr; \
208 float alpha_ = range_table[range_dist]; \
210 ycr = inv_alpha_ * (*--in_x) + alpha_ * ypr; \
211 --temp_x; *temp_x = ((*temp_x) + ycr); \
215 fc = inv_alpha_ + alpha_*fp; \
217 *temp_factor_x = ((*temp_factor_x) + fc); \
223 BILATERAL_H(uint8_t, byte
)
224 BILATERAL_H(uint16_t, word
)
226 #define BILATERAL_V(type, name) \
227 static void bilateralv_##name(BilateralContext *s, AVFrame *out, AVFrame *in, \
228 int jobnr, int nb_jobs, int plane) \
230 const int width = s->planewidth[plane]; \
231 const int height = s->planeheight[plane]; \
232 const int slice_start = (width * jobnr) / nb_jobs; \
233 const int slice_end = (width * (jobnr+1)) / nb_jobs; \
234 const int src_linesize = in->linesize[plane] / sizeof(type); \
235 const type *src = (const type *)in->data[plane] + slice_start; \
236 float *img_out_f = s->img_out_f[plane] + slice_start; \
237 float *img_temp = s->img_temp[plane] + slice_start; \
238 float *map_factor_a = s->map_factor_a[plane] + slice_start; \
239 float *map_factor_b = s->map_factor_b[plane] + slice_start; \
240 float *slice_factor_a = s->slice_factor_a[plane] + slice_start; \
241 float *slice_factor_b = s->slice_factor_b[plane] + slice_start; \
242 float *line_factor_a = s->line_factor_a[plane] + slice_start; \
243 float *line_factor_b = s->line_factor_b[plane] + slice_start; \
244 const float *const range_table = s->range_table; \
245 const float alpha = s->alpha; \
246 float *ycy, *ypy, *xcy; \
247 const float inv_alpha_ = 1.f - alpha; \
248 float *ycf, *ypf, *xcf, *in_factor; \
249 const type *tcy, *tpy; \
252 memcpy(img_out_f, img_temp, sizeof(float) * (slice_end - slice_start)); \
254 in_factor = map_factor_a; \
255 memcpy(map_factor_b, in_factor, sizeof(float) * (slice_end - slice_start)); \
256 for (int y = 1; y < height; y++) { \
257 tpy = &src[(y - 1) * src_linesize]; \
258 tcy = &src[y * src_linesize]; \
259 xcy = &img_temp[y * width]; \
260 ypy = &img_out_f[(y - 1) * width]; \
261 ycy = &img_out_f[y * width]; \
263 xcf = &in_factor[y * width]; \
264 ypf = &map_factor_b[(y - 1) * width]; \
265 ycf = &map_factor_b[y * width]; \
266 for (int x = 0; x < slice_end - slice_start; x++) { \
267 type dr = abs((*tcy++) - (*tpy++)); \
268 int range_dist = dr; \
269 float alpha_ = range_table[range_dist]; \
271 *ycy++ = inv_alpha_*(*xcy++) + alpha_*(*ypy++); \
272 *ycf++ = inv_alpha_*(*xcf++) + alpha_*(*ypf++); \
276 ycf = line_factor_a; \
277 ypf = line_factor_b; \
278 memcpy(ypf, &in_factor[h1 * width], sizeof(float) * (slice_end - slice_start)); \
279 for (int x = 0, k = 0; x < slice_end - slice_start; x++) \
280 map_factor_b[h1 * width + x] = (map_factor_b[h1 * width + x] + ypf[k++]); \
282 ycy = slice_factor_a; \
283 ypy = slice_factor_b; \
284 memcpy(ypy, &img_temp[h1 * width], sizeof(float) * (slice_end - slice_start)); \
285 for (int x = 0, k = 0; x < slice_end - slice_start; x++) { \
286 int idx = h1 * width + x; \
287 img_out_f[idx] = (img_out_f[idx] + ypy[k++]) / map_factor_b[h1 * width + x]; \
290 for (int y = h1 - 1; y >= 0; y--) { \
291 float *ycf_, *ypf_, *factor_; \
292 float *ycy_, *ypy_, *out_; \
294 tpy = &src[(y + 1) * src_linesize]; \
295 tcy = &src[y * src_linesize]; \
296 xcy = &img_temp[y * width]; \
299 out_ = &img_out_f[y * width]; \
301 xcf = &in_factor[y * width]; \
304 factor_ = &map_factor_b[y * width]; \
305 for (int x = 0; x < slice_end - slice_start; x++) { \
306 type dr = abs((*tcy++) - (*tpy++)); \
307 int range_dist = dr; \
308 float alpha_ = range_table[range_dist]; \
309 float ycc, fcc = inv_alpha_*(*xcf++) + alpha_*(*ypf_++); \
312 *factor_ = (*factor_ + fcc); \
314 ycc = inv_alpha_*(*xcy++) + alpha_*(*ypy_++); \
316 *out_ = (*out_ + ycc) / (*factor_); \
326 BILATERAL_V(uint8_t, byte
)
327 BILATERAL_V(uint16_t, word
)
329 #define BILATERAL_O(type, name) \
330 static void bilateralo_##name(BilateralContext *s, AVFrame *out, AVFrame *in, \
331 int jobnr, int nb_jobs, int plane) \
333 const int width = s->planewidth[plane]; \
334 const int height = s->planeheight[plane]; \
335 const int slice_start = (height * jobnr) / nb_jobs; \
336 const int slice_end = (height * (jobnr+1)) / nb_jobs; \
337 const int dst_linesize = out->linesize[plane] / sizeof(type); \
339 for (int i = slice_start; i < slice_end; i++) { \
340 type *dst = (type *)out->data[plane] + i * dst_linesize; \
341 const float *const img_out_f = s->img_out_f[plane] + i * width; \
342 for (int j = 0; j < width; j++) \
343 dst[j] = lrintf(img_out_f[j]); \
347 BILATERAL_O(uint8_t, byte
)
348 BILATERAL_O(uint16_t, word
)
350 static int bilateralh_planes(AVFilterContext
*ctx
, void *arg
,
351 int jobnr
, int nb_jobs
)
353 BilateralContext
*s
= ctx
->priv
;
354 ThreadData
*td
= arg
;
355 AVFrame
*out
= td
->out
;
356 AVFrame
*in
= td
->in
;
358 for (int plane
= 0; plane
< s
->nb_planes
; plane
++) {
359 if (!(s
->planes
& (1 << plane
)))
363 bilateralh_byte(s
, out
, in
, jobnr
, nb_jobs
, plane
);
365 bilateralh_word(s
, out
, in
, jobnr
, nb_jobs
, plane
);
371 static int bilateralv_planes(AVFilterContext
*ctx
, void *arg
,
372 int jobnr
, int nb_jobs
)
374 BilateralContext
*s
= ctx
->priv
;
375 ThreadData
*td
= arg
;
376 AVFrame
*out
= td
->out
;
377 AVFrame
*in
= td
->in
;
379 for (int plane
= 0; plane
< s
->nb_planes
; plane
++) {
380 if (!(s
->planes
& (1 << plane
)))
384 bilateralv_byte(s
, out
, in
, jobnr
, nb_jobs
, plane
);
386 bilateralv_word(s
, out
, in
, jobnr
, nb_jobs
, plane
);
392 static int bilateralo_planes(AVFilterContext
*ctx
, void *arg
,
393 int jobnr
, int nb_jobs
)
395 BilateralContext
*s
= ctx
->priv
;
396 ThreadData
*td
= arg
;
397 AVFrame
*out
= td
->out
;
398 AVFrame
*in
= td
->in
;
400 for (int plane
= 0; plane
< s
->nb_planes
; plane
++) {
401 if (!(s
->planes
& (1 << plane
))) {
403 const int height
= s
->planeheight
[plane
];
404 const int slice_start
= (height
* jobnr
) / nb_jobs
;
405 const int slice_end
= (height
* (jobnr
+1)) / nb_jobs
;
406 const int width
= s
->planewidth
[plane
];
407 const int linesize
= in
->linesize
[plane
];
408 const int dst_linesize
= out
->linesize
[plane
];
409 const uint8_t *src
= in
->data
[plane
];
410 uint8_t *dst
= out
->data
[plane
];
412 av_image_copy_plane(dst
+ slice_start
* dst_linesize
,
414 src
+ slice_start
* linesize
,
416 width
* ((s
->depth
+ 7) / 8),
417 slice_end
- slice_start
);
423 bilateralo_byte(s
, out
, in
, jobnr
, nb_jobs
, plane
);
425 bilateralo_word(s
, out
, in
, jobnr
, nb_jobs
, plane
);
431 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
433 AVFilterContext
*ctx
= inlink
->dst
;
434 BilateralContext
*s
= ctx
->priv
;
435 AVFilterLink
*outlink
= ctx
->outputs
[0];
439 if (av_frame_is_writable(in
)) {
442 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
445 return AVERROR(ENOMEM
);
447 av_frame_copy_props(out
, in
);
452 ff_filter_execute(ctx
, bilateralh_planes
, &td
, NULL
, s
->nb_threads
);
453 ff_filter_execute(ctx
, bilateralv_planes
, &td
, NULL
, s
->nb_threads
);
454 ff_filter_execute(ctx
, bilateralo_planes
, &td
, NULL
, s
->nb_threads
);
458 return ff_filter_frame(outlink
, out
);
461 static av_cold
void uninit(AVFilterContext
*ctx
)
463 BilateralContext
*s
= ctx
->priv
;
465 for (int p
= 0; p
< s
->nb_planes
; p
++) {
466 av_freep(&s
->img_out_f
[p
]);
467 av_freep(&s
->img_temp
[p
]);
468 av_freep(&s
->map_factor_a
[p
]);
469 av_freep(&s
->map_factor_b
[p
]);
470 av_freep(&s
->slice_factor_a
[p
]);
471 av_freep(&s
->slice_factor_b
[p
]);
472 av_freep(&s
->line_factor_a
[p
]);
473 av_freep(&s
->line_factor_b
[p
]);
477 static int process_command(AVFilterContext
*ctx
,
484 int ret
= ff_filter_process_command(ctx
, cmd
, arg
, res
, res_len
, flags
);
489 return config_params(ctx
);
492 static const AVFilterPad bilateral_inputs
[] = {
495 .type
= AVMEDIA_TYPE_VIDEO
,
496 .config_props
= config_input
,
497 .filter_frame
= filter_frame
,
501 const FFFilter ff_vf_bilateral
= {
502 .p
.name
= "bilateral",
503 .p
.description
= NULL_IF_CONFIG_SMALL("Apply Bilateral filter."),
504 .p
.priv_class
= &bilateral_class
,
505 .p
.flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
|
506 AVFILTER_FLAG_SLICE_THREADS
,
507 .priv_size
= sizeof(BilateralContext
),
509 FILTER_INPUTS(bilateral_inputs
),
510 FILTER_OUTPUTS(ff_video_default_filterpad
),
511 FILTER_PIXFMTS_ARRAY(pix_fmts
),
512 .process_command
= process_command
,