FFmpeg
Loading...
Searching...
No Matches
filters.c
Go to the documentation of this file.
1/*
2 * Copyright (C) 2026 Niklas Haas
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (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 GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21#include <math.h>
22#include <stdbool.h>
23
25#include <libavutil/avassert.h>
26#include <libavutil/mem.h>
27
28#include "filters.h"
29
30#ifdef _WIN32
31# define j1 _j1
32#endif
33
34/* Maximum (pre-stretching) radius (for tunable filters) */
35#define RADIUS_MAX 10.0
36
37/* Defined only on [0, radius]. */
38typedef double (*SwsFilterKernel)(double x, const double *params);
39
40typedef struct SwsFilterFunction {
41 char name[16];
42 double radius; /* negative means resizable */
44 SwsFilterKernel window; /* optional */
45 double params[SWS_NUM_SCALER_PARAMS]; /* default params */
47
49
50static double scaler_sample(const SwsFilterFunction *f, double x)
51{
52 x = fabs(x);
53 if (x > f->radius)
54 return 0.0;
55
56 double w = f->kernel(x, f->params);
57 if (f->window)
58 w *= f->window(x / f->radius, f->params);
59 return w;
60}
61
63 double radius, double ratio_inv, double stretch_inv,
64 int dst_pos, double *tmp)
65{
66 int *out = &f->weights[dst_pos * f->filter_size];
67 int *pos = &f->offsets[dst_pos];
68
69 /**
70 * Explanation of the 0.5 offsets: Normally, pixel samples are assumed
71 * to be representative of the center of their containing area; e.g. for
72 * a 2x2 image, the samples are located at {0.5, 1.5}^2. However, with
73 * integer indexing, we round sample positions down (0-based indexing).
74 * So the (0, 0) sample is actually located at (0.5, 0.5) and represents
75 * the entire square from (0,0) to (1,1). When normalizing between different
76 * image sizes, we therefore need to add/subtract off these 0.5 offsets.
77 */
78 const double src_pos = (dst_pos + 0.5) * ratio_inv - 0.5 + f->offset;
79 if (f->filter_size == 1) {
80 *pos = fmin(fmax(round(src_pos), 0.0), f->src_size - 1);
82 return;
83 }
84
85 /* First pixel that is actually within the filter envelope */
86 const double start_pos = src_pos - radius;
87 int64_t start_idx = ceil(start_pos);
88 start_idx = FFMAX(start_idx, 0); /* edge clamping */
89 start_idx = FFMIN(start_idx, f->src_size - f->filter_size);
90 const double offset = start_idx - src_pos;
91 *pos = start_idx;
92
93 /**
94 * Generate raw filter weights with maximum precision. Sum the positive
95 * and negative weights separately to avoid catastrophic cancellation. This
96 * summation order should already give the best precision because abs(w)
97 * is monotonically decreasing
98 */
99 const double base = stretch_inv * offset;
100 double wsum_pos = 0.0, wsum_neg = 0.0;
101 for (int i = 0; i < f->filter_size; i++) {
102 tmp[i] = scaler_sample(fun, base + stretch_inv * i);
103 if (tmp[i] >= 0)
104 wsum_pos += tmp[i];
105 else
106 wsum_neg += tmp[i];
107 }
108
109 const double wsum = wsum_pos + wsum_neg;
110 av_assert0(wsum > 0);
111
112 /* Generate correctly rounded filter weights with error diffusion */
113 double error = 0.0;
114 int sum_pos = 0, sum_neg = 0;
115 for (int i = 0; i < f->filter_size; i++) {
116 if (i == f->filter_size - 1) {
117 /* Ensure weights sum to exactly SWS_FILTER_SCALE */
118 out[i] = SWS_FILTER_SCALE - sum_pos - sum_neg;
119 } else {
120 const double w = tmp[i] / wsum + error;
123 }
124 if (out[i] >= 0)
125 sum_pos += out[i];
126 else
127 sum_neg += out[i];
128 }
129
130 if (sum_pos > f->sum_positive)
131 f->sum_positive = sum_pos;
132 if (sum_neg < f->sum_negative)
133 f->sum_negative = sum_neg;
134}
135
136static void sws_filter_free(AVRefStructOpaque opaque, void *obj)
137{
139 av_refstruct_unref(&filter->weights);
140 av_refstruct_unref(&filter->offsets);
141}
142
143static bool validate_params(const SwsFilterFunction *fun, SwsScaler scaler)
144{
145 switch (scaler) {
147 return fun->params[0] >= 0.0; /* sigma */
149 return fun->params[0] >= 1.0 && fun->params[0] <= RADIUS_MAX; /* radius */
151 return fun->params[0] < 3.0; /* B param (division by zero) */
152 default:
153 return true;
154 }
155}
156
157/**
158 * Numerically estimate the last intersection between the function value
159 * and the cutoff domain [-SWS_MAX_REDUCE_CUTOFF, SWS_MAX_REDUCE_CUTOFF].
160 */
161static double est_filter_radius(const SwsFilterFunction *fun)
162{
163 const double bound = fun->radius;
164 const double step = 1e-2;
165
166 double radius = bound;
167 double prev = 0.0, fprev = 1.0; /* f(0) is always 1.0 */
168 double integral = 0.0;
169 for (double x = step; x < bound + step; x += step) {
170 const double fx = scaler_sample(fun, x);
171 integral += (fprev + fx) * step; /* trapezoidal rule (mirrored) */
172 double cutoff = SWS_MAX_REDUCE_CUTOFF * integral;
173 if ((fprev > cutoff && fx <= cutoff) || (fprev < -cutoff && fx >= -cutoff)) {
174 /* estimate crossing with secant method; note that we have to
175 * bias by the cutoff to find the actual cutoff radius */
176 double estimate = fx + (fx > fprev ? cutoff : -cutoff);
177 double root = x - estimate * (x - prev) / (fx - fprev);
178 radius = fmin(root, bound);
179 }
180 prev = x;
181 fprev = fx;
182 }
183
184 return radius;
185}
186
189{
190 SwsScaler scaler = params->scaler;
191 if (scaler >= SWS_SCALE_NB)
192 return AVERROR(EINVAL);
193
194 if (scaler == SWS_SCALE_AUTO)
195 scaler = SWS_SCALE_BICUBIC;
196
197 double virtual_size = params->virtual_size;
198 if (!virtual_size)
199 virtual_size = params->dst_size;
200
201 const double ratio = virtual_size / params->src_size;
202 double stretch = 1.0;
203 if (ratio < 1.0 && scaler != SWS_SCALE_POINT) {
204 /* Widen filter for downscaling (anti-aliasing) */
205 stretch = 1.0 / ratio;
206 }
207
208 if (scaler == SWS_SCALE_AREA) {
209 /**
210 * SWS_SCALE_AREA is a pseudo-filter that is equivalent to bilinear
211 * filtering for upscaling (since bilinear just evenly mixes samples
212 * according to the relative distance), and equivalent to (anti-aliased)
213 * point sampling for downscaling.
214 */
215 scaler = ratio >= 1.0 ? SWS_SCALE_BILINEAR : SWS_SCALE_POINT;
216 }
217
219 if (!fun.kernel)
220 return AVERROR(EINVAL);
221
222 for (int i = 0; i < SWS_NUM_SCALER_PARAMS; i++) {
223 if (params->scaler_params[i] != SWS_PARAM_DEFAULT)
224 fun.params[i] = params->scaler_params[i];
225 }
226
227 if (!validate_params(&fun, scaler)) {
228 av_log(log, AV_LOG_ERROR, "Invalid parameters for scaler %s: {%f, %f}\n",
229 fun.name, fun.params[0], fun.params[1]);
230 return AVERROR(EINVAL);
231 }
232
233 if (fun.radius < 0.0) /* tunable width kernels like lanczos */
234 fun.radius = fun.params[0];
235
236 double radius;
237 switch (scaler) {
238 case SWS_SCALE_POINT:
239 radius = 0.5;
240 break;
242 radius = 1.0 - SWS_MAX_REDUCE_CUTOFF;
243 break;
244 default:
245 /* Numerically estimate radius of nontrivial or parametric kernels */
246 radius = est_filter_radius(&fun);
247 break;
248 }
249 radius *= stretch;
250
251 int filter_size = ceil(radius * 2.0);
252 filter_size = FFMIN(filter_size, params->src_size);
253 av_assert0(filter_size >= 1);
254 if (filter_size > SWS_FILTER_SIZE_MAX)
255 return AVERROR(ENOTSUP);
256
259 if (!filter)
260 return AVERROR(ENOMEM);
261 memcpy(filter->name, fun.name, sizeof(filter->name));
262 filter->src_size = params->src_size;
263 filter->dst_size = params->dst_size;
264 filter->virtual_size = virtual_size;
265 filter->offset = params->offset;
266 filter->filter_size = filter_size;
267 if (filter->filter_size == 1)
268 filter->sum_positive = SWS_FILTER_SCALE;
269
270 av_log(log, AV_LOG_DEBUG, "Generating %s filter with %d taps (radius = %f)\n",
271 filter->name, filter->filter_size, radius);
272
273 filter->num_weights = (size_t) params->dst_size * filter->filter_size;
274 filter->weights = av_refstruct_allocz(filter->num_weights * sizeof(*filter->weights));
275 if (!filter->weights) {
277 return AVERROR(ENOMEM);
278 }
279
280 filter->offsets = av_refstruct_allocz(params->dst_size * sizeof(*filter->offsets));
281 if (!filter->offsets) {
283 return AVERROR(ENOMEM);
284 }
285
286 double *tmp = av_malloc(filter->filter_size * sizeof(*tmp));
287 if (!tmp) {
289 return AVERROR(ENOMEM);
290 }
291
292 const double ratio_inv = 1.0 / ratio, stretch_inv = 1.0 / stretch;
293 for (int i = 0; i < params->dst_size; i++)
294 compute_row(filter, &fun, radius, ratio_inv, stretch_inv, i, tmp);
295 av_free(tmp);
296
297 *out = filter;
298 return 0;
299}
300
301/*
302 * Some of the filter code originally derives (via libplacebo/mpv) from Glumpy:
303 * # Copyright (c) 2009-2016 Nicolas P. Rougier. All rights reserved.
304 * # Distributed under the (new) BSD License.
305 * (https://github.com/glumpy/glumpy/blob/master/glumpy/library/build-spatial-filters.py)
306 *
307 * The math underlying each filter function was written from scratch, with
308 * some algorithms coming from a number of different sources, including:
309 * - https://en.wikipedia.org/wiki/Window_function
310 * - https://en.wikipedia.org/wiki/Jinc
311 * - http://vector-agg.cvs.sourceforge.net/viewvc/vector-agg/agg-2.5/include/agg_image_filters.h
312 * - Vapoursynth plugin fmtconv (WTFPL Licensed), which is based on
313 * dither plugin for avisynth from the same author:
314 * https://github.com/vapoursynth/fmtconv/tree/master/src/fmtc
315 * - Paul Heckbert's "zoom"
316 * - XBMC: ConvolutionKernels.cpp etc.
317 * - https://github.com/AviSynth/jinc-resize (only used to verify the math)
318 */
319
320av_unused static double box(double x, const double *params)
321{
322 return 1.0;
323}
324
325av_unused static double triangle(double x, const double *params)
326{
327 return 1.0 - x;
328}
329
330av_unused static double cosine(double x, const double *params)
331{
332 return cos(x);
333}
334
335av_unused static double hann(double x, const double *params)
336{
337 return 0.5 + 0.5 * cos(M_PI * x);
338}
339
340av_unused static double hamming(double x, const double *params)
341{
342 return 0.54 + 0.46 * cos(M_PI * x);
343}
344
345av_unused static double welch(double x, const double *params)
346{
347 return 1.0 - x * x;
348}
349
350av_unused static double bessel_i0(double x)
351{
352 double s = 1.0;
353 double y = x * x / 4.0;
354 double t = y;
355 int i = 2;
356 while (t > 1e-12) {
357 s += t;
358 t *= y / (i * i);
359 i += 1;
360 }
361 return s;
362}
363
364av_unused static double kaiser(double x, const double *params)
365{
366 double alpha = fmax(params[0], 0.0);
367 double scale = bessel_i0(alpha);
368 return bessel_i0(alpha * sqrt(1.0 - x * x)) / scale;
369}
370
371av_unused static double blackman(double x, const double *params)
372{
373 double a = params[0];
374 double a0 = (1 - a) / 2.0, a1 = 1 / 2.0, a2 = a / 2.0;
375 x *= M_PI;
376 return a0 + a1 * cos(x) + a2 * cos(2 * x);
377}
378
379av_unused static double bohman(double x, const double *params)
380{
381 double pix = M_PI * x;
382 return (1.0 - x) * cos(pix) + sin(pix) / M_PI;
383}
384
385av_unused static double gaussian(double x, const double *params)
386{
387 return exp(-params[0] * x * x);
388}
389
390av_unused static double quadratic(double x, const double *params)
391{
392 if (x < 0.5) {
393 return 1.0 - 4.0/3.0 * (x * x);
394 } else {
395 return 2.0 / 3.0 * (x - 1.5) * (x - 1.5);
396 }
397}
398
399av_unused static double sinc(double x, const double *params)
400{
401 if (x < 1e-8)
402 return 1.0;
403 x *= M_PI;
404 return sin(x) / x;
405}
406
407av_unused static double jinc(double x, const double *params)
408{
409 if (x < 1e-8)
410 return 1.0;
411 x *= M_PI;
412 return 2.0 * j1(x) / x;
413}
414
415av_unused static double sphinx(double x, const double *params)
416{
417 if (x < 1e-8)
418 return 1.0;
419 x *= M_PI;
420 return 3.0 * (sin(x) - x * cos(x)) / (x * x * x);
421}
422
423av_unused static double cubic(double x, const double *params)
424{
425 const double b = params[0], c = params[1];
426 double p0 = 6.0 - 2.0 * b,
427 p2 = -18.0 + 12.0 * b + 6.0 * c,
428 p3 = 12.0 - 9.0 * b - 6.0 * c,
429 q0 = 8.0 * b + 24.0 * c,
430 q1 = -12.0 * b - 48.0 * c,
431 q2 = 6.0 * b + 30.0 * c,
432 q3 = -b - 6.0 * c;
433
434 if (x < 1.0) {
435 return (p0 + x * x * (p2 + x * p3)) / p0;
436 } else {
437 return (q0 + x * (q1 + x * (q2 + x * q3))) / p0;
438 }
439}
440
441static double spline_coeff(double a, double b, double c, double d, double x)
442{
443 if (x <= 1.0) {
444 return ((d * x + c) * x + b) * x + a;
445 } else {
446 return spline_coeff(0.0,
447 b + 2.0 * c + 3.0 * d,
448 c + 3.0 * d,
449 -b - 3.0 * c - 6.0 * d,
450 x - 1.0);
451 }
452}
453
454av_unused static double spline(double x, const double *params)
455{
456 const double p = -2.196152422706632;
457 return spline_coeff(1.0, 0.0, p, -p - 1.0, x);
458}
459
461 [SWS_SCALE_BILINEAR] = { "bilinear", 1.0, triangle },
462 [SWS_SCALE_BICUBIC] = { "bicubic", 2.0, cubic, .params = { 0.0, 0.6 } },
463 [SWS_SCALE_POINT] = { "point", 0.5, box },
464 [SWS_SCALE_GAUSSIAN] = { "gaussian", 4.0, gaussian, .params = { 3.0 } },
465 [SWS_SCALE_SINC] = { "sinc", RADIUS_MAX, sinc },
466 [SWS_SCALE_LANCZOS] = { "lanczos", -1.0, sinc, sinc, .params = { 3.0 } },
467 [SWS_SCALE_SPLINE] = { "spline", RADIUS_MAX, spline },
468 /* SWS_SCALE_AREA is a pseudo-filter, see code above */
469};
SwsAArch64OpImplParams params
Definition ops.c:51
static double bound(const double threshold, const double val)
simple assert() macros that are a bit more flexible than ISO C assert().
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition avassert.h:42
#define i(width, name, range_min, range_max)
Definition cbs_h264.c:63
#define f(width, name)
Definition cbs_vp8.c:236
#define s(width, name)
Definition cbs_vp9.c:198
#define NULL
Definition coverity.c:32
long long int64_t
Definition coverity.c:34
static __device__ float ceil(float a)
static __device__ float fabs(float a)
double fmin(double, double)
double fmax(double, double)
int8_t exp
Definition eval.c:76
static av_unused double kaiser(double x, const double *params)
Definition filters.c:364
static void compute_row(SwsFilterWeights *f, const SwsFilterFunction *fun, double radius, double ratio_inv, double stretch_inv, int dst_pos, double *tmp)
Definition filters.c:62
static av_unused double hamming(double x, const double *params)
Definition filters.c:340
static av_unused double blackman(double x, const double *params)
Definition filters.c:371
static av_unused double bessel_i0(double x)
Definition filters.c:350
static av_unused double bohman(double x, const double *params)
Definition filters.c:379
static double est_filter_radius(const SwsFilterFunction *fun)
Numerically estimate the last intersection between the function value and the cutoff domain [-SWS_MAX...
Definition filters.c:161
static const SwsFilterFunction filter_functions[SWS_SCALE_NB]
Definition filters.c:48
static bool validate_params(const SwsFilterFunction *fun, SwsScaler scaler)
Definition filters.c:143
static av_unused double hann(double x, const double *params)
Definition filters.c:335
static av_unused double gaussian(double x, const double *params)
Definition filters.c:385
static av_unused double sphinx(double x, const double *params)
Definition filters.c:415
static double spline_coeff(double a, double b, double c, double d, double x)
Definition filters.c:441
static double scaler_sample(const SwsFilterFunction *f, double x)
Definition filters.c:50
double(* SwsFilterKernel)(double x, const double *params)
Definition filters.c:38
static av_unused double cubic(double x, const double *params)
Definition filters.c:423
static av_unused double cosine(double x, const double *params)
Definition filters.c:330
int ff_sws_filter_generate(void *log, const SwsFilterParams *params, SwsFilterWeights **out)
Generate a filter kernel for the given parameters.
Definition filters.c:187
static av_unused double sinc(double x, const double *params)
Definition filters.c:399
static av_unused double spline(double x, const double *params)
Definition filters.c:454
static av_unused double welch(double x, const double *params)
Definition filters.c:345
static av_unused double jinc(double x, const double *params)
Definition filters.c:407
static void sws_filter_free(AVRefStructOpaque opaque, void *obj)
Definition filters.c:136
static av_unused double box(double x, const double *params)
Definition filters.c:320
static av_unused double triangle(double x, const double *params)
Definition filters.c:325
static av_unused double quadratic(double x, const double *params)
Definition filters.c:390
#define AVERROR(e)
Definition error.h:45
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition log.h:231
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition log.h:210
#define SWS_MAX_REDUCE_CUTOFF
Filter kernel cut-off value.
Definition swscale.h:447
#define SWS_PARAM_DEFAULT
Definition swscale.h:456
SwsScaler
Definition swscale.h:96
@ SWS_SCALE_SPLINE
unwindowned natural cubic spline
Definition swscale.h:105
@ SWS_SCALE_POINT
nearest neighbor (point sampling)
Definition swscale.h:100
@ SWS_SCALE_NB
not part of the ABI
Definition swscale.h:106
@ SWS_SCALE_LANCZOS
3-tap sinc/sinc
Definition swscale.h:104
@ SWS_SCALE_BILINEAR
bilinear filtering
Definition swscale.h:98
@ SWS_SCALE_GAUSSIAN
2-tap gaussian approximation
Definition swscale.h:102
@ SWS_SCALE_BICUBIC
2-tap cubic BC-spline
Definition swscale.h:99
@ SWS_SCALE_AREA
area averaging
Definition swscale.h:101
@ SWS_SCALE_SINC
unwindowed sinc
Definition swscale.h:103
@ SWS_SCALE_AUTO
Definition swscale.h:97
int a
static const int16_t alpha[]
Definition ilbcdata.h:55
#define b
Definition input.c:43
static void scale(int *out, const int *in, const int w, const int h, const int shift)
Definition intra.c:278
unsigned offset
Definition libaomenc.c:763
Macro definitions for various function/variable attributes.
#define av_unused
Definition attributes.h:164
static av_always_inline av_const double round(double x)
Definition libm.h:446
@ SWS_FILTER_SIZE_MAX
Definition filters.h:41
@ SWS_FILTER_SCALE
14-bit coefficients are picked to fit comfortably within int16_t for efficient SIMD processing (e....
Definition filters.h:40
uint8_t w
Definition llvidencdsp.c:39
#define FFMIN(a, b)
Definition macros.h:49
#define FFMAX(a, b)
Definition macros.h:47
#define M_PI
Definition mathematics.h:67
Memory handling functions.
enum AVPixelFormat pix
Definition ohcodec.c:55
#define av_malloc(s)
Definition ops_static.c:52
void av_refstruct_unref(void *objp)
Decrement the reference count of the underlying object and automatically free the object if there are...
Definition refstruct.c:120
static void * av_refstruct_allocz(size_t size)
Equivalent to av_refstruct_alloc_ext(size, 0, NULL, NULL)
Definition refstruct.h:105
static void * av_refstruct_alloc_ext(size_t size, unsigned flags, void *opaque, void(*free_cb)(AVRefStructOpaque opaque, void *obj))
A wrapper around av_refstruct_alloc_ext_c() for the common case of a non-const qualified opaque.
Definition refstruct.h:94
unsigned int pos
Definition spdifenc.c:431
double params[SWS_NUM_SCALER_PARAMS]
Definition filters.c:45
char name[16]
Definition filters.c:41
SwsFilterKernel kernel
Definition filters.c:43
SwsFilterKernel window
Definition filters.c:44
Represents a computed filter kernel.
Definition filters.h:85
#define SWS_NUM_SCALER_PARAMS
Extra parameters for fine-tuning certain scalers.
Definition swscale.h:243
#define av_free(p)
#define av_log(a,...)
static void error(const char *err)
static uint8_t tmp[40]
Definition aes_ctr.c:52
void(* filter)(uint8_t *src, ptrdiff_t stride, int qscale)
Definition h263dsp.c:29
static FILE * out
Definition movenc.c:55
static const uint8_t q1[256]
Definition twofish.c:100
static const uint8_t q0[256]
Definition twofish.c:81
RefStruct is an API for creating reference-counted objects with minimal overhead.
Definition refstruct.h:58
static float gaussian(float sigma, float x)
#define RADIUS_MAX
Definition vf_sab.c:69
static double a0(void *priv, double x, double y)
Definition vf_xfade.c:2028
static double a2(void *priv, double x, double y)
Definition vf_xfade.c:2030
static double a1(void *priv, double x, double y)
Definition vf_xfade.c:2029
uint8_t base
Definition vp3data.h:128
static double c[64]