FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavfilter/vf_sab.c
Date: 2026-09-26 14:26:21
Exec Total Coverage
Lines: 0 116 0.0%
Functions: 0 7 0.0%
Branches: 0 44 0.0%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2002 Michael Niedermayer <michaelni@gmx.at>
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 /**
22 * @file
23 * Shape Adaptive Blur filter, ported from MPlayer libmpcodecs/vf_sab.c
24 */
25
26 #include "libavutil/mem.h"
27 #include "libavutil/opt.h"
28 #include "libavutil/pixdesc.h"
29 #include "libswscale/swscale.h"
30
31 #include "avfilter.h"
32 #include "filters.h"
33 #include "video.h"
34
35 typedef struct FilterParam {
36 float radius;
37 float pre_filter_radius;
38 float strength;
39 float quality;
40 struct SwsContext *pre_filter_context;
41 uint8_t *pre_filter_buf;
42 int pre_filter_linesize;
43 int dist_width;
44 int dist_linesize;
45 int *dist_coeff;
46 #define COLOR_DIFF_COEFF_SIZE 512
47 int color_diff_coeff[COLOR_DIFF_COEFF_SIZE];
48 } FilterParam;
49
50 typedef struct SabContext {
51 const AVClass *class;
52 FilterParam luma;
53 FilterParam chroma;
54 int hsub;
55 int vsub;
56 unsigned int sws_flags;
57 } SabContext;
58
59 static const enum AVPixelFormat pix_fmts[] = {
60 AV_PIX_FMT_YUV420P,
61 AV_PIX_FMT_YUV410P,
62 AV_PIX_FMT_YUV444P,
63 AV_PIX_FMT_YUV422P,
64 AV_PIX_FMT_YUV411P,
65 AV_PIX_FMT_NONE
66 };
67
68 #define RADIUS_MIN 0.1
69 #define RADIUS_MAX 4.0
70
71 #define PRE_FILTER_RADIUS_MIN 0.1
72 #define PRE_FILTER_RADIUS_MAX 2.0
73
74 #define STRENGTH_MIN 0.1
75 #define STRENGTH_MAX 100.0
76
77 #define OFFSET(x) offsetof(SabContext, x)
78 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
79
80 static const AVOption sab_options[] = {
81 { "luma_radius", "set luma radius", OFFSET(luma.radius), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, RADIUS_MIN, RADIUS_MAX, .flags=FLAGS },
82 { "lr" , "set luma radius", OFFSET(luma.radius), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, RADIUS_MIN, RADIUS_MAX, .flags=FLAGS },
83 { "luma_pre_filter_radius", "set luma pre-filter radius", OFFSET(luma.pre_filter_radius), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, PRE_FILTER_RADIUS_MIN, PRE_FILTER_RADIUS_MAX, .flags=FLAGS },
84 { "lpfr", "set luma pre-filter radius", OFFSET(luma.pre_filter_radius), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, PRE_FILTER_RADIUS_MIN, PRE_FILTER_RADIUS_MAX, .flags=FLAGS },
85 { "luma_strength", "set luma strength", OFFSET(luma.strength), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, STRENGTH_MIN, STRENGTH_MAX, .flags=FLAGS },
86 { "ls", "set luma strength", OFFSET(luma.strength), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, STRENGTH_MIN, STRENGTH_MAX, .flags=FLAGS },
87
88 { "chroma_radius", "set chroma radius", OFFSET(chroma.radius), AV_OPT_TYPE_FLOAT, {.dbl=RADIUS_MIN-1}, RADIUS_MIN-1, RADIUS_MAX, .flags=FLAGS },
89 { "cr", "set chroma radius", OFFSET(chroma.radius), AV_OPT_TYPE_FLOAT, {.dbl=RADIUS_MIN-1}, RADIUS_MIN-1, RADIUS_MAX, .flags=FLAGS },
90 { "chroma_pre_filter_radius", "set chroma pre-filter radius", OFFSET(chroma.pre_filter_radius), AV_OPT_TYPE_FLOAT, {.dbl=PRE_FILTER_RADIUS_MIN-1},
91 PRE_FILTER_RADIUS_MIN-1, PRE_FILTER_RADIUS_MAX, .flags=FLAGS },
92 { "cpfr", "set chroma pre-filter radius", OFFSET(chroma.pre_filter_radius), AV_OPT_TYPE_FLOAT, {.dbl=PRE_FILTER_RADIUS_MIN-1},
93 PRE_FILTER_RADIUS_MIN-1, PRE_FILTER_RADIUS_MAX, .flags=FLAGS },
94 { "chroma_strength", "set chroma strength", OFFSET(chroma.strength), AV_OPT_TYPE_FLOAT, {.dbl=STRENGTH_MIN-1}, STRENGTH_MIN-1, STRENGTH_MAX, .flags=FLAGS },
95 { "cs", "set chroma strength", OFFSET(chroma.strength), AV_OPT_TYPE_FLOAT, {.dbl=STRENGTH_MIN-1}, STRENGTH_MIN-1, STRENGTH_MAX, .flags=FLAGS },
96
97 { NULL }
98 };
99
100 AVFILTER_DEFINE_CLASS(sab);
101
102 ✗ static av_cold int init(AVFilterContext *ctx)
103 {
104 ✗ SabContext *s = ctx->priv;
105
106 /* make chroma default to luma values, if not explicitly set */
107 ✗ if (s->chroma.radius < RADIUS_MIN)
108 ✗ s->chroma.radius = s->luma.radius;
109 ✗ if (s->chroma.pre_filter_radius < PRE_FILTER_RADIUS_MIN)
110 ✗ s->chroma.pre_filter_radius = s->luma.pre_filter_radius;
111 ✗ if (s->chroma.strength < STRENGTH_MIN)
112 ✗ s->chroma.strength = s->luma.strength;
113
114 ✗ s->luma.quality = s->chroma.quality = 3.0;
115 ✗ s->sws_flags = SWS_POINT;
116
117 ✗ av_log(ctx, AV_LOG_VERBOSE,
118 "luma_radius:%f luma_pre_filter_radius::%f luma_strength:%f "
119 "chroma_radius:%f chroma_pre_filter_radius:%f chroma_strength:%f\n",
120 ✗ s->luma .radius, s->luma .pre_filter_radius, s->luma .strength,
121 ✗ s->chroma.radius, s->chroma.pre_filter_radius, s->chroma.strength);
122 ✗ return 0;
123 }
124
125 ✗ static void close_filter_param(FilterParam *f)
126 {
127 ✗ if (f->pre_filter_context) {
128 ✗ sws_freeContext(f->pre_filter_context);
129 ✗ f->pre_filter_context = NULL;
130 }
131 ✗ av_freep(&f->pre_filter_buf);
132 ✗ av_freep(&f->dist_coeff);
133 ✗ }
134
135 ✗ static av_cold void uninit(AVFilterContext *ctx)
136 {
137 ✗ SabContext *s = ctx->priv;
138
139 ✗ close_filter_param(&s->luma);
140 ✗ close_filter_param(&s->chroma);
141 ✗ }
142
143 ✗ static int open_filter_param(FilterParam *f, int width, int height, unsigned int sws_flags)
144 {
145 SwsVector *vec;
146 SwsFilter sws_f;
147 int i, x, y;
148 ✗ int linesize = FFALIGN(width, 8);
149
150 ✗ f->pre_filter_buf = av_malloc(linesize * height);
151 ✗ if (!f->pre_filter_buf)
152 ✗ return AVERROR(ENOMEM);
153
154 ✗ f->pre_filter_linesize = linesize;
155 ✗ vec = sws_getGaussianVec(f->pre_filter_radius, f->quality);
156 ✗ sws_f.lumH = sws_f.lumV = vec;
157 ✗ sws_f.chrH = sws_f.chrV = NULL;
158 ✗ f->pre_filter_context = sws_getContext(width, height, AV_PIX_FMT_GRAY8,
159 width, height, AV_PIX_FMT_GRAY8,
160 sws_flags, &sws_f, NULL, NULL);
161 ✗ sws_freeVec(vec);
162
163 ✗ vec = sws_getGaussianVec(f->strength, 5.0);
164 ✗ for (i = 0; i < COLOR_DIFF_COEFF_SIZE; i++) {
165 double d;
166 ✗ int index = i-COLOR_DIFF_COEFF_SIZE/2 + vec->length/2;
167
168 ✗ if (index < 0 || index >= vec->length) d = 0.0;
169 ✗ else d = vec->coeff[index];
170
171 ✗ f->color_diff_coeff[i] = (int)(d/vec->coeff[vec->length/2]*(1<<12) + 0.5);
172 }
173 ✗ sws_freeVec(vec);
174
175 ✗ vec = sws_getGaussianVec(f->radius, f->quality);
176 ✗ f->dist_width = vec->length;
177 ✗ f->dist_linesize = FFALIGN(vec->length, 8);
178 ✗ f->dist_coeff = av_malloc_array(f->dist_width, f->dist_linesize * sizeof(*f->dist_coeff));
179 ✗ if (!f->dist_coeff) {
180 ✗ sws_freeVec(vec);
181 ✗ return AVERROR(ENOMEM);
182 }
183
184 ✗ for (y = 0; y < vec->length; y++) {
185 ✗ for (x = 0; x < vec->length; x++) {
186 ✗ double d = vec->coeff[x] * vec->coeff[y];
187 ✗ f->dist_coeff[x + y*f->dist_linesize] = (int)(d*(1<<10) + 0.5);
188 }
189 }
190 ✗ sws_freeVec(vec);
191
192 ✗ return 0;
193 }
194
195 ✗ static int config_props(AVFilterLink *inlink)
196 {
197 ✗ SabContext *s = inlink->dst->priv;
198 ✗ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
199 int ret;
200
201 ✗ s->hsub = desc->log2_chroma_w;
202 ✗ s->vsub = desc->log2_chroma_h;
203
204 ✗ close_filter_param(&s->luma);
205 ✗ ret = open_filter_param(&s->luma, inlink->w, inlink->h, s->sws_flags);
206 ✗ if (ret < 0)
207 ✗ return ret;
208
209 ✗ close_filter_param(&s->chroma);
210 ✗ ret = open_filter_param(&s->chroma,
211 ✗ AV_CEIL_RSHIFT(inlink->w, s->hsub),
212 ✗ AV_CEIL_RSHIFT(inlink->h, s->vsub), s->sws_flags);
213 ✗ return ret;
214 }
215
216 #define NB_PLANES 4
217
218 ✗ static void blur(uint8_t *dst, const int dst_linesize,
219 const uint8_t *src, const int src_linesize,
220 const int w, const int h, FilterParam *fp)
221 {
222 int x, y;
223 ✗ FilterParam f = *fp;
224 ✗ const int radius = f.dist_width/2;
225
226 ✗ const uint8_t * const src2[NB_PLANES] = { src };
227 ✗ int src2_linesize[NB_PLANES] = { src_linesize };
228 ✗ uint8_t *dst2[NB_PLANES] = { f.pre_filter_buf };
229 ✗ int dst2_linesize[NB_PLANES] = { f.pre_filter_linesize };
230
231 ✗ sws_scale(f.pre_filter_context, src2, src2_linesize, 0, h, dst2, dst2_linesize);
232
233 #define UPDATE_FACTOR do { \
234 int factor; \
235 factor = f.color_diff_coeff[COLOR_DIFF_COEFF_SIZE/2 + pre_val - \
236 f.pre_filter_buf[ix + iy*f.pre_filter_linesize]] * f.dist_coeff[dx + dy*f.dist_linesize]; \
237 sum += src[ix + iy*src_linesize] * factor; \
238 div += factor; \
239 } while (0)
240
241 ✗ for (y = 0; y < h; y++) {
242 ✗ for (x = 0; x < w; x++) {
243 ✗ int sum = 0;
244 ✗ int div = 0;
245 int dy;
246 ✗ const int pre_val = f.pre_filter_buf[x + y*f.pre_filter_linesize];
247 ✗ if (x >= radius && x < w - radius) {
248 ✗ for (dy = 0; dy < radius*2 + 1; dy++) {
249 int dx;
250 ✗ int iy = y+dy - radius;
251 ✗ iy = avpriv_mirror(iy, h-1);
252
253 ✗ for (dx = 0; dx < radius*2 + 1; dx++) {
254 ✗ const int ix = x+dx - radius;
255 ✗ UPDATE_FACTOR;
256 }
257 }
258 } else {
259 ✗ for (dy = 0; dy < radius*2+1; dy++) {
260 int dx;
261 ✗ int iy = y+dy - radius;
262 ✗ iy = avpriv_mirror(iy, h-1);
263
264 ✗ for (dx = 0; dx < radius*2 + 1; dx++) {
265 ✗ int ix = x+dx - radius;
266 ✗ ix = avpriv_mirror(ix, w-1);
267 ✗ UPDATE_FACTOR;
268 }
269 }
270 }
271 ✗ dst[x + y*dst_linesize] = (sum + div/2) / div;
272 }
273 }
274 ✗ }
275
276 ✗ static int filter_frame(AVFilterLink *inlink, AVFrame *inpic)
277 {
278 ✗ SabContext *s = inlink->dst->priv;
279 ✗ AVFilterLink *outlink = inlink->dst->outputs[0];
280 AVFrame *outpic;
281
282 ✗ outpic = ff_get_video_buffer(outlink, outlink->w, outlink->h);
283 ✗ if (!outpic) {
284 ✗ av_frame_free(&inpic);
285 ✗ return AVERROR(ENOMEM);
286 }
287 ✗ av_frame_copy_props(outpic, inpic);
288
289 ✗ blur(outpic->data[0], outpic->linesize[0], inpic->data[0], inpic->linesize[0],
290 inlink->w, inlink->h, &s->luma);
291 ✗ if (inpic->data[2]) {
292 ✗ int cw = AV_CEIL_RSHIFT(inlink->w, s->hsub);
293 ✗ int ch = AV_CEIL_RSHIFT(inlink->h, s->vsub);
294 ✗ blur(outpic->data[1], outpic->linesize[1], inpic->data[1], inpic->linesize[1], cw, ch, &s->chroma);
295 ✗ blur(outpic->data[2], outpic->linesize[2], inpic->data[2], inpic->linesize[2], cw, ch, &s->chroma);
296 }
297
298 ✗ av_frame_free(&inpic);
299 ✗ return ff_filter_frame(outlink, outpic);
300 }
301
302 static const AVFilterPad sab_inputs[] = {
303 {
304 .name = "default",
305 .type = AVMEDIA_TYPE_VIDEO,
306 .filter_frame = filter_frame,
307 .config_props = config_props,
308 },
309 };
310
311 const FFFilter ff_vf_sab = {
312 .p.name = "sab",
313 .p.description = NULL_IF_CONFIG_SMALL("Apply shape adaptive blur."),
314 .p.priv_class = &sab_class,
315 .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
316 .priv_size = sizeof(SabContext),
317 .init = init,
318 .uninit = uninit,
319 FILTER_INPUTS(sab_inputs),
320 FILTER_OUTPUTS(ff_video_default_filterpad),
321 FILTER_PIXFMTS_ARRAY(pix_fmts),
322 };
323