PR #22402 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22402
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22402.patch

The old practice of using `sws_flags` to control the scaler by setting a single 
flag is awkward and hard to extend; in particular, also preventing any separate 
selection of the scaler between the luma/chroma planes (expect for the BICUBLIN 
hack).

This series replaces these flags by a proper enum, enabling independent 
selection of scalers, and paving the way for the new scaling code that will be 
introduced shortly.


>From e87e8ae04faf408e899eb9190f8b7335bc0b87a1 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 19 Feb 2026 19:34:14 +0100
Subject: [PATCH 1/6] swscale: fix SWS_SPLINE documentation

This was incorrectly inferred to be a Keys spline when the documentation
was first added; but it's actually an "unwindowed" (in theory) natural
cubic spline with C2 continuity everywhere, which is a completely different
thing.

(SWS_BICUBIC is closer to being a Keys spline)

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/swscale.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libswscale/swscale.h b/libswscale/swscale.h
index 1cfd4068f9..1ee24c22ad 100644
--- a/libswscale/swscale.h
+++ b/libswscale/swscale.h
@@ -107,7 +107,7 @@ typedef enum SwsFlags {
     SWS_GAUSS         = 1 <<  7, ///< gaussian approximation
     SWS_SINC          = 1 <<  8, ///< unwindowed sinc
     SWS_LANCZOS       = 1 <<  9, ///< 3-tap sinc/sinc
-    SWS_SPLINE        = 1 << 10, ///< cubic Keys spline
+    SWS_SPLINE        = 1 << 10, ///< unwindowed natural cubic spline
 
     /**
      * Return an error on underspecified conversions. Without this flag,
-- 
2.52.0


>From 6b718ef1366eb08b6d036f489b1ebd3066f362b1 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Feb 2026 16:44:01 +0100
Subject: [PATCH 2/6] swscale: don't hard code number of scaler params

In case we ever need to increase this number in the future.
I won't bother bumping the ABI version for this new #define, since it doesn't
affect ABI, and I'm about to bump the ABI version in a following commit.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/graph.c   |  4 ++--
 libswscale/swscale.h |  3 ++-
 libswscale/utils.c   | 16 +++++++---------
 3 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index 81b8ef35d7..dd49c4cfb0 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -516,8 +516,8 @@ static int add_legacy_sws_pass(SwsGraph *graph, const 
SwsFormat *src,
     legacy_chr_pos(graph, &sws->dst_h_chr_pos, ctx->dst_h_chr_pos, &warned);
     legacy_chr_pos(graph, &sws->dst_v_chr_pos, ctx->dst_v_chr_pos, &warned);
 
-    sws->scaler_params[0] = ctx->scaler_params[0];
-    sws->scaler_params[1] = ctx->scaler_params[1];
+    for (int i = 0; i < SWS_NUM_SCALER_PARAMS; i++)
+        sws->scaler_params[i] = ctx->scaler_params[i];
 
     ret = sws_init_context(sws, NULL, NULL);
     if (ret < 0) {
diff --git a/libswscale/swscale.h b/libswscale/swscale.h
index 1ee24c22ad..043b4c15ef 100644
--- a/libswscale/swscale.h
+++ b/libswscale/swscale.h
@@ -204,7 +204,8 @@ typedef struct SwsContext {
     /**
      * Extra parameters for fine-tuning certain scalers.
      */
-    double scaler_params[2];
+#define SWS_NUM_SCALER_PARAMS 2
+    double scaler_params[SWS_NUM_SCALER_PARAMS];
 
     /**
      * How many threads to use for processing, or 0 for automatic selection.
diff --git a/libswscale/utils.c b/libswscale/utils.c
index 8a3462c4a3..a5d620bde3 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -87,10 +87,8 @@ static SwsContext *alloc_set_opts(int srcW, int srcH, enum 
AVPixelFormat srcForm
     sws->src_format = srcFormat;
     sws->dst_format = dstFormat;
 
-    if (param) {
-        sws->scaler_params[0] = param[0];
-        sws->scaler_params[1] = param[1];
-    }
+    for (int i = 0; param && i < SWS_NUM_SCALER_PARAMS; i++)
+        sws->scaler_params[i] = param[i];
 
     return sws;
 }
@@ -200,7 +198,7 @@ static av_cold int initFilter(int16_t **outFilter, int32_t 
**filterPos,
                               int dstW, int filterAlign, int one,
                               int flags, int cpu_flags,
                               SwsVector *srcFilter, SwsVector *dstFilter,
-                              double param[2], int srcPos, int dstPos)
+                              double param[SWS_NUM_SCALER_PARAMS], int srcPos, 
int dstPos)
 {
     int i;
     int filterSize;
@@ -2357,8 +2355,8 @@ SwsContext *sws_getCachedContext(SwsContext *prev, int 
srcW,
                  prev->dst_h            == dstH      &&
                  prev->dst_format       == dstFormat &&
                  prev->flags            == flags     &&
-                 prev->scaler_params[0] == param[0]  &&
-                 prev->scaler_params[1] == param[1])) {
+                 !memcmp(prev->scaler_params, param,
+                         sizeof(prev->scaler_params)))) {
         return prev;
     }
 
@@ -2379,8 +2377,8 @@ SwsContext *sws_getCachedContext(SwsContext *prev, int 
srcW,
     sws->dst_h            = dstH;
     sws->dst_format       = dstFormat;
     sws->flags            = flags;
-    sws->scaler_params[0] = param[0];
-    sws->scaler_params[1] = param[1];
+    for (int i = 0; i < SWS_NUM_SCALER_PARAMS; i++)
+        sws->scaler_params[i] = param[i];
 
     if (sws_init_context(sws, srcFilter, dstFilter) < 0)
         sws_free_context(&sws);
-- 
2.52.0


>From 02ed982e4fe471270f938daf5aac553ffa0404fb Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Feb 2026 18:11:59 +0100
Subject: [PATCH 3/6] swscale/utils: separate luma and chroma scaler selection

Pre-requisite for adding support for configuring these independently.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/utils.c | 61 ++++++++++++++++++++++++++--------------------
 1 file changed, 34 insertions(+), 27 deletions(-)

diff --git a/libswscale/utils.c b/libswscale/utils.c
index a5d620bde3..c828393345 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -196,7 +196,7 @@ static const ScaleAlgorithm scale_algorithms[] = {
 static av_cold int initFilter(int16_t **outFilter, int32_t **filterPos,
                               int *outFilterSize, int xInc, int srcW,
                               int dstW, int filterAlign, int one,
-                              int flags, int cpu_flags,
+                              int scaler, int flags, int cpu_flags,
                               SwsVector *srcFilter, SwsVector *dstFilter,
                               double param[SWS_NUM_SCALER_PARAMS], int srcPos, 
int dstPos)
 {
@@ -225,7 +225,7 @@ static av_cold int initFilter(int16_t **outFilter, int32_t 
**filterPos,
             filter[i * filterSize] = fone;
             (*filterPos)[i]        = i;
         }
-    } else if (flags & SWS_POINT) { // lame looking point sampling mode
+    } else if (scaler == SWS_POINT) { // lame looking point sampling mode
         int i;
         int64_t xDstInSrc;
         filterSize = 1;
@@ -240,8 +240,8 @@ static av_cold int initFilter(int16_t **outFilter, int32_t 
**filterPos,
             filter[i]       = fone;
             xDstInSrc      += xInc;
         }
-    } else if ((xInc <= (1 << 16) && (flags & SWS_AREA)) ||
-               (flags & SWS_FAST_BILINEAR)) { // bilinear upscale
+    } else if ((xInc <= (1 << 16) && (scaler == SWS_AREA)) ||
+               (scaler == SWS_FAST_BILINEAR)) { // bilinear upscale
         int i;
         int64_t xDstInSrc;
         filterSize = 2;
@@ -269,12 +269,12 @@ static av_cold int initFilter(int16_t **outFilter, 
int32_t **filterPos,
         int sizeFactor = -1;
 
         for (i = 0; i < FF_ARRAY_ELEMS(scale_algorithms); i++) {
-            if (flags & scale_algorithms[i].flag && 
scale_algorithms[i].size_factor > 0) {
+            if (scaler == scale_algorithms[i].flag && 
scale_algorithms[i].size_factor > 0) {
                 sizeFactor = scale_algorithms[i].size_factor;
                 break;
             }
         }
-        if (flags & SWS_LANCZOS)
+        if (scaler == SWS_LANCZOS)
             sizeFactor = param[0] != SWS_PARAM_DEFAULT ? ceil(2 * param[0]) : 
6;
         av_assert0(sizeFactor > 0);
 
@@ -308,7 +308,7 @@ static av_cold int initFilter(int16_t **outFilter, int32_t 
**filterPos,
                     d = d * dstW / srcW;
                 floatd = d * (1.0 / (1 << 30));
 
-                if (flags & SWS_BICUBIC) {
+                if (scaler == SWS_BICUBIC) {
                     int64_t B = (param[0] != SWS_PARAM_DEFAULT ? param[0] :   
0) * (1 << 24);
                     int64_t C = (param[1] != SWS_PARAM_DEFAULT ? param[1] : 
0.6) * (1 << 24);
 
@@ -329,7 +329,7 @@ static av_cold int initFilter(int16_t **outFilter, int32_t 
**filterPos,
                                       (8 * B + 24 * C) * (1 << 30);
                     }
                     coeff /= (1LL<<54)/fone;
-                } else if (flags & SWS_X) {
+                } else if (scaler == SWS_X) {
                     double A = param[0] != SWS_PARAM_DEFAULT ? param[0] : 1.0;
                     double c;
 
@@ -342,7 +342,7 @@ static av_cold int initFilter(int16_t **outFilter, int32_t 
**filterPos,
                     else
                         c = pow(c, A);
                     coeff = (c * 0.5 + 0.5) * fone;
-                } else if (flags & SWS_AREA) {
+                } else if (scaler == SWS_AREA) {
                     int64_t d2 = d - (1 << 29);
                     if (d2 * xInc < -(1LL << (29 + 16)))
                         coeff = 1.0 * (1LL << (30 + 16));
@@ -351,23 +351,23 @@ static av_cold int initFilter(int16_t **outFilter, 
int32_t **filterPos,
                     else
                         coeff = 0.0;
                     coeff *= fone >> (30 + 16);
-                } else if (flags & SWS_GAUSS) {
+                } else if (scaler == SWS_GAUSS) {
                     double p = param[0] != SWS_PARAM_DEFAULT ? param[0] : 3.0;
                     coeff = exp2(-p * floatd * floatd) * fone;
-                } else if (flags & SWS_SINC) {
+                } else if (scaler == SWS_SINC) {
                     coeff = (d ? sin(floatd * M_PI) / (floatd * M_PI) : 1.0) * 
fone;
-                } else if (flags & SWS_LANCZOS) {
+                } else if (scaler == SWS_LANCZOS) {
                     double p = param[0] != SWS_PARAM_DEFAULT ? param[0] : 3.0;
                     coeff = (d ? sin(floatd * M_PI) * sin(floatd * M_PI / p) /
                              (floatd * floatd * M_PI * M_PI / p) : 1.0) * fone;
                     if (floatd > p)
                         coeff = 0;
-                } else if (flags & SWS_BILINEAR) {
+                } else if (scaler == SWS_BILINEAR) {
                     coeff = (1 << 30) - d;
                     if (coeff < 0)
                         coeff = 0;
                     coeff *= fone >> 30;
-                } else if (flags & SWS_SPLINE) {
+                } else if (scaler == SWS_SPLINE) {
                     double p = -2.196152422706632;
                     coeff = getSplineCoeff(1.0, 0.0, p, -p - 1.0, floatd) * 
fone;
                 } else {
@@ -1191,17 +1191,30 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
     /* provide a default scaler if not set by caller */
     if (!i) {
         if (dstW < srcW && dstH < srcH)
-            flags |= SWS_BICUBIC;
+            i = SWS_BICUBIC;
         else if (dstW > srcW && dstH > srcH)
-            flags |= SWS_BICUBIC;
+            i = SWS_BICUBIC;
         else
-            flags |= SWS_BICUBIC;
+            i = SWS_BICUBIC;
+        flags |= i;
         sws->flags = flags;
     } else if (i & (i - 1)) {
         av_log(c, AV_LOG_ERROR,
                "Exactly one scaler algorithm must be chosen, got %X\n", i);
         return AVERROR(EINVAL);
     }
+
+    if (i == SWS_FAST_BILINEAR) {
+        if (srcW < 8 || dstW <= 8) {
+            i = SWS_BILINEAR;
+            flags ^= SWS_FAST_BILINEAR | i;
+            sws->flags = flags;
+        }
+    }
+
+    int lum_scaler = i == SWS_BICUBLIN ? SWS_BICUBIC  : i;
+    int chr_scaler = i == SWS_BICUBLIN ? SWS_BILINEAR : i;
+
     /* sanity check */
     if (srcW < 1 || srcH < 1 || dstW < 1 || dstH < 1) {
         /* FIXME check if these are enough and try to lower them after
@@ -1210,12 +1223,6 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
                srcW, srcH, dstW, dstH);
         return AVERROR(EINVAL);
     }
-    if (flags & SWS_FAST_BILINEAR) {
-        if (srcW < 8 || dstW <= 8) {
-            flags ^= SWS_FAST_BILINEAR | SWS_BILINEAR;
-            sws->flags = flags;
-        }
-    }
 
     if (!dstFilter)
         dstFilter = &dummyFilter;
@@ -1680,7 +1687,7 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
             if ((ret = initFilter(&c->hLumFilter, &c->hLumFilterPos,
                            &c->hLumFilterSize, c->lumXInc,
                            srcW, dstW, filterAlign, 1 << 14,
-                           (flags & SWS_BICUBLIN) ? (flags | SWS_BICUBIC) : 
flags,
+                           lum_scaler, flags,
                            cpu_flags, srcFilter->lumH, dstFilter->lumH,
                            sws->scaler_params,
                            get_local_pos(c, 0, 0, 0),
@@ -1691,7 +1698,7 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
             if ((ret = initFilter(&c->hChrFilter, &c->hChrFilterPos,
                            &c->hChrFilterSize, c->chrXInc,
                            c->chrSrcW, c->chrDstW, filterAlign, 1 << 14,
-                           (flags & SWS_BICUBLIN) ? (flags | SWS_BILINEAR) : 
flags,
+                           chr_scaler, flags,
                            cpu_flags, srcFilter->chrH, dstFilter->chrH,
                            sws->scaler_params,
                            get_local_pos(c, c->chrSrcHSubSample, 
sws->src_h_chr_pos, 0),
@@ -1710,7 +1717,7 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
 
         if ((ret = initFilter(&c->vLumFilter, &c->vLumFilterPos, 
&c->vLumFilterSize,
                        c->lumYInc, srcH, dstH, filterAlign, (1 << 12),
-                       (flags & SWS_BICUBLIN) ? (flags | SWS_BICUBIC) : flags,
+                       lum_scaler, flags,
                        cpu_flags, srcFilter->lumV, dstFilter->lumV,
                        sws->scaler_params,
                        get_local_pos(c, 0, 0, 1),
@@ -1719,7 +1726,7 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
         if ((ret = initFilter(&c->vChrFilter, &c->vChrFilterPos, 
&c->vChrFilterSize,
                        c->chrYInc, c->chrSrcH, c->chrDstH,
                        filterAlign, (1 << 12),
-                       (flags & SWS_BICUBLIN) ? (flags | SWS_BILINEAR) : flags,
+                       chr_scaler, flags,
                        cpu_flags, srcFilter->chrV, dstFilter->chrV,
                        sws->scaler_params,
                        get_local_pos(c, c->chrSrcVSubSample, 
sws->src_v_chr_pos, 1),
-- 
2.52.0


>From f6e9cd5961a72175dbeaf3513a2ccf0966a0724e Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Feb 2026 17:43:41 +0100
Subject: [PATCH 4/6] swscale: add enum SwsScaler, SwsContext.scaler to replace
 legacy flags

Another step towards a cleaner API, with a cleaner separation of purposes.
Also avoids wasting a whopping one third of the flag space on what really
shouldn't have been a flag to begin with.

I pre-emptively decided to separate the scaler selection between "scaler"
and "scaler_sub", the latter defining what's used for things like 4:2:0
subsampling.

This allows us to get rid of the awkwardly defined SWS_BICUBLIN flag, in favor
of that just being the natural consequence of using a different scaler_sub.

Lastly, I also decided to pre-emptively axe the poorly defined and
questionable SWS_X scaler, which I doubt ever saw much use. The old flag
is still available as a deprecated flag, anyhow.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 doc/APIchanges       |  3 +++
 libswscale/graph.c   |  4 ++++
 libswscale/options.c | 13 +++++++++++++
 libswscale/swscale.h | 30 ++++++++++++++++++++++++++++++
 libswscale/utils.c   | 21 +++++++++++++++++++--
 libswscale/version.h |  2 +-
 6 files changed, 70 insertions(+), 3 deletions(-)

diff --git a/doc/APIchanges b/doc/APIchanges
index 88005bb28e..1057644b8b 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
 
 API changes, most recent first:
 
+2026-02-xx - xxxxxxxxxx - lsws 9.5.100 - swscale.h
+  Add enum SwsScaler, and SwsContext.scaler/scaler_sub.
+
 2026-02-xx - xxxxxxxxxx - lsws 9.4.100 - swscale.h
   Add sws_test_hw_format().
 
diff --git a/libswscale/graph.c b/libswscale/graph.c
index dd49c4cfb0..27d0dafe56 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -494,6 +494,8 @@ static int add_legacy_sws_pass(SwsGraph *graph, const 
SwsFormat *src,
     sws->dither      = ctx->dither;
     sws->alpha_blend = ctx->alpha_blend;
     sws->gamma_flag  = ctx->gamma_flag;
+    sws->scaler      = ctx->scaler;
+    sws->scaler_sub  = ctx->scaler_sub;
 
     sws->src_w       = src->width;
     sws->src_h       = src->height;
@@ -843,6 +845,8 @@ static int opts_equal(const SwsContext *c1, const 
SwsContext *c2)
            c1->dst_h_chr_pos == c2->dst_h_chr_pos &&
            c1->dst_v_chr_pos == c2->dst_v_chr_pos &&
            c1->intent        == c2->intent        &&
+           c1->scaler        == c2->scaler        &&
+           c1->scaler_sub    == c2->scaler_sub    &&
            !memcmp(c1->scaler_params, c2->scaler_params, 
sizeof(c1->scaler_params));
 
 }
diff --git a/libswscale/options.c b/libswscale/options.c
index 06e51dcfe9..004988488c 100644
--- a/libswscale/options.c
+++ b/libswscale/options.c
@@ -52,6 +52,19 @@ static const AVOption swscale_options[] = {
         { "error_diffusion", "error diffusion dither",        0,  
AV_OPT_TYPE_CONST, { .i64 = SWS_ERROR_DIFFUSION}, .flags = VE, .unit = 
"sws_flags" },
         { "unstable",        "allow experimental new code",   0,  
AV_OPT_TYPE_CONST, { .i64 = SWS_UNSTABLE       }, .flags = VE, .unit = 
"sws_flags" },
 
+    { "scaler",          "set scaling algorithm",         OFFSET(scaler),      
 AV_OPT_TYPE_INT,    { .i64 = SWS_SCALE_AUTO     }, .flags = VE, .unit = 
"sws_scaler", .max = SWS_SCALE_NB - 1 },
+    { "scaler_sub",      "set subsampling algorithm",     OFFSET(scaler_sub),  
 AV_OPT_TYPE_INT,    { .i64 = SWS_SCALE_AUTO     }, .flags = VE, .unit = 
"sws_scaler", .max = SWS_SCALE_NB - 1 },
+        { "auto",        "automatic selection",           0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_AUTO     }, .flags = VE, .unit = 
"sws_scaler" },
+        { "bilinear",    "bilinear filtering",            0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_BILINEAR }, .flags = VE, .unit = 
"sws_scaler" },
+        { "bicubic",     "2-tap cubic B-spline",          0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_BICUBIC  }, .flags = VE, .unit = 
"sws_scaler" },
+        { "point",       "point sampling",                0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_POINT    }, .flags = VE, .unit = 
"sws_scaler" },
+        { "neighbor",    "nearest neighbor",              0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_POINT    }, .flags = VE, .unit = 
"sws_scaler" },
+        { "area",        "area averaging",                0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_AREA     }, .flags = VE, .unit = 
"sws_scaler" },
+        { "gaussian",    "2-tap gaussian approximation",  0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_GAUSSIAN }, .flags = VE, .unit = 
"sws_scaler" },
+        { "sinc",        "unwindowed sinc",               0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_SINC     }, .flags = VE, .unit = 
"sws_scaler" },
+        { "lanczos",     "3-tap sinc/sinc",               0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_LANCZOS  }, .flags = VE, .unit = 
"sws_scaler" },
+        { "spline",      "2-tap cubic BC spline",         0,                   
 AV_OPT_TYPE_CONST,  { .i64 = SWS_SCALE_SPLINE   }, .flags = VE, .unit = 
"sws_scaler" },
+
     { "param0",          "scaler param 0", OFFSET(scaler_params[0]), 
AV_OPT_TYPE_DOUBLE, { .dbl = SWS_PARAM_DEFAULT  }, INT_MIN, INT_MAX, VE },
     { "param1",          "scaler param 1", OFFSET(scaler_params[1]), 
AV_OPT_TYPE_DOUBLE, { .dbl = SWS_PARAM_DEFAULT  }, INT_MIN, INT_MAX, VE },
 
diff --git a/libswscale/swscale.h b/libswscale/swscale.h
index 043b4c15ef..14c7d4cc57 100644
--- a/libswscale/swscale.h
+++ b/libswscale/swscale.h
@@ -93,6 +93,20 @@ typedef enum SwsAlphaBlend {
     SWS_ALPHA_BLEND_MAX_ENUM = 0x7FFFFFFF, /* force size to 32 bits, not a 
valid blend mode */
 } SwsAlphaBlend;
 
+typedef enum SwsScaler {
+    SWS_SCALE_AUTO = 0,
+    SWS_SCALE_BILINEAR, ///< bilinear filtering
+    SWS_SCALE_BICUBIC,  ///< 2-tap cubic BC-spline
+    SWS_SCALE_POINT,    ///< nearest neighbor (point sampling)
+    SWS_SCALE_AREA,     ///< area averaging
+    SWS_SCALE_GAUSSIAN, ///< 2-tap gaussian approximation
+    SWS_SCALE_SINC,     ///< unwindowed sinc
+    SWS_SCALE_LANCZOS,  ///< 3-tap sinc/sinc
+    SWS_SCALE_SPLINE,   ///< unwindowned natural cubic spline
+    SWS_SCALE_NB,       ///< not part of the ABI
+    SWS_SCALE_MAX_ENUM = 0x7FFFFFFF, ///< force size to 32 bits, not a valid 
filter type
+} SwsScaler;
+
 typedef enum SwsFlags {
     /**
      * Scaler selection options. Only one may be active at a time.
@@ -249,6 +263,22 @@ typedef struct SwsContext {
      */
     int intent;
 
+    /**
+     * Scaling filter. If set to something other than SWS_SCALE_AUTO, this will
+     * override the filter implied by `SwsContext.flags`.
+     *
+     * Note: Does not affect the legacy (stateful) API.
+     */
+    SwsScaler scaler;
+
+    /**
+     * Scaler used specifically for up/downsampling subsampled (chroma) planes.
+     * If set to something other than SWS_SCALE_AUTO, this will override the
+     * filter implied by `SwsContext.scaler`. Otherwise, the same filter
+     * will be used for both main scaling and chroma subsampling.
+     */
+    SwsScaler scaler_sub;
+
     /* Remember to add new fields to graph.c:opts_equal() */
 } SwsContext;
 
diff --git a/libswscale/utils.c b/libswscale/utils.c
index c828393345..c28c8e0f02 100644
--- a/libswscale/utils.c
+++ b/libswscale/utils.c
@@ -1117,6 +1117,22 @@ static enum AVPixelFormat alphaless_fmt(enum 
AVPixelFormat fmt)
     }
 }
 
+static int scaler_flag(SwsScaler scaler, int fallback)
+{
+    switch (scaler) {
+    case SWS_SCALE_BILINEAR: return SWS_BILINEAR; break;
+    case SWS_SCALE_BICUBIC:  return SWS_BICUBIC;  break;
+    case SWS_SCALE_POINT:    return SWS_POINT;    break;
+    case SWS_SCALE_AREA:     return SWS_AREA;     break;
+    case SWS_SCALE_GAUSSIAN: return SWS_GAUSS;    break;
+    case SWS_SCALE_SINC:     return SWS_SINC;     break;
+    case SWS_SCALE_LANCZOS:  return SWS_LANCZOS;  break;
+    case SWS_SCALE_SPLINE:   return SWS_SPLINE;   break;
+    default:
+        return fallback;
+    }
+}
+
 av_cold int ff_sws_init_single_context(SwsContext *sws, SwsFilter *srcFilter,
                                        SwsFilter *dstFilter)
 {
@@ -1212,8 +1228,9 @@ av_cold int ff_sws_init_single_context(SwsContext *sws, 
SwsFilter *srcFilter,
         }
     }
 
-    int lum_scaler = i == SWS_BICUBLIN ? SWS_BICUBIC  : i;
-    int chr_scaler = i == SWS_BICUBLIN ? SWS_BILINEAR : i;
+    SwsScaler scaler_sub = sws->scaler_sub ? sws->scaler_sub : sws->scaler;
+    int lum_scaler = scaler_flag(sws->scaler, i == SWS_BICUBLIN ? SWS_BICUBIC  
: i);
+    int chr_scaler = scaler_flag(scaler_sub,  i == SWS_BICUBLIN ? SWS_BILINEAR 
: i);
 
     /* sanity check */
     if (srcW < 1 || srcH < 1 || dstW < 1 || dstH < 1) {
diff --git a/libswscale/version.h b/libswscale/version.h
index 12412bd538..c13db31c43 100644
--- a/libswscale/version.h
+++ b/libswscale/version.h
@@ -28,7 +28,7 @@
 
 #include "version_major.h"
 
-#define LIBSWSCALE_VERSION_MINOR   4
+#define LIBSWSCALE_VERSION_MINOR   5
 #define LIBSWSCALE_VERSION_MICRO 100
 
 #define LIBSWSCALE_VERSION_INT  AV_VERSION_INT(LIBSWSCALE_VERSION_MAJOR, \
-- 
2.52.0


>From 64a83de64e89494d7b608917ab08fd34ccc704b2 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Feb 2026 17:51:12 +0100
Subject: [PATCH 5/6] swscale: mark scale-related SwsFlags as deprecated

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/swscale.h | 31 ++++++++++++++++---------------
 1 file changed, 16 insertions(+), 15 deletions(-)

diff --git a/libswscale/swscale.h b/libswscale/swscale.h
index 14c7d4cc57..5532826a99 100644
--- a/libswscale/swscale.h
+++ b/libswscale/swscale.h
@@ -108,21 +108,6 @@ typedef enum SwsScaler {
 } SwsScaler;
 
 typedef enum SwsFlags {
-    /**
-     * Scaler selection options. Only one may be active at a time.
-     */
-    SWS_FAST_BILINEAR = 1 <<  0, ///< fast bilinear filtering
-    SWS_BILINEAR      = 1 <<  1, ///< bilinear filtering
-    SWS_BICUBIC       = 1 <<  2, ///< 2-tap cubic B-spline
-    SWS_X             = 1 <<  3, ///< experimental
-    SWS_POINT         = 1 <<  4, ///< nearest neighbor
-    SWS_AREA          = 1 <<  5, ///< area averaging
-    SWS_BICUBLIN      = 1 <<  6, ///< bicubic luma, bilinear chroma
-    SWS_GAUSS         = 1 <<  7, ///< gaussian approximation
-    SWS_SINC          = 1 <<  8, ///< unwindowed sinc
-    SWS_LANCZOS       = 1 <<  9, ///< 3-tap sinc/sinc
-    SWS_SPLINE        = 1 << 10, ///< unwindowed natural cubic spline
-
     /**
      * Return an error on underspecified conversions. Without this flag,
      * unspecified fields are defaulted to sensible values.
@@ -183,6 +168,22 @@ typedef enum SwsFlags {
      */
     SWS_DIRECT_BGR      = 1 << 15, ///< This flag has no effect
     SWS_ERROR_DIFFUSION = 1 << 23, ///< Set `SwsContext.dither` instead
+
+    /**
+     * Scaler selection options. Only one may be active at a time.
+     * Deprecated in favor of `SwsContext.scaler`.
+     */
+    SWS_FAST_BILINEAR = 1 <<  0, ///< fast bilinear filtering
+    SWS_BILINEAR      = 1 <<  1, ///< bilinear filtering
+    SWS_BICUBIC       = 1 <<  2, ///< 2-tap cubic B-spline
+    SWS_X             = 1 <<  3, ///< experimental
+    SWS_POINT         = 1 <<  4, ///< nearest neighbor
+    SWS_AREA          = 1 <<  5, ///< area averaging
+    SWS_BICUBLIN      = 1 <<  6, ///< bicubic luma, bilinear chroma
+    SWS_GAUSS         = 1 <<  7, ///< gaussian approximation
+    SWS_SINC          = 1 <<  8, ///< unwindowed sinc
+    SWS_LANCZOS       = 1 <<  9, ///< 3-tap sinc/sinc
+    SWS_SPLINE        = 1 << 10, ///< unwindowed natural cubic spline
 } SwsFlags;
 
 typedef enum SwsIntent {
-- 
2.52.0


>From 0c9be01412e117deda2032d1bcaf7459c749a797 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Thu, 5 Mar 2026 18:52:38 +0100
Subject: [PATCH 6/6] doc/scaler: document new sws scaler flags

And label the old ones as deprecated.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 doc/scaler.texi | 66 +++++++++++++++++++++++++++++++++++++++----------
 1 file changed, 53 insertions(+), 13 deletions(-)

diff --git a/doc/scaler.texi b/doc/scaler.texi
index 0c2b952395..aecc3ad248 100644
--- a/doc/scaler.texi
+++ b/doc/scaler.texi
@@ -11,48 +11,88 @@ For programmatic use, they can be set explicitly in the
 
 @table @option
 
+@anchor{scaler}
+@item scaler, scaler_sub
+Choose the scaling algorithm to use. Default value is @samp{auto} for both.
+It accepts the following values:
+
+@table @samp
+@item auto
+Aumotic choice. For @samp{scaler_sub}, this means the same algorithm as
+@samp{scaler}. For @samp{scaler}, this defaults to the scaler flag selected
+by @samp{sws_flags}.
+
+@item bilinear
+Bilinear filter. (AKA triangle filter)
+
+@item bicubic
+2-tap cubic BC-spline (AKA Mitchell-Netravali spline). The B and C parameters
+can be configured by setting @code{param0} and @code{param1}, defaulting to
+0.0 and 0.6 respectively.
+
+@item point, neighbor
+Point sampling (AKA nearest neighbor).
+
+@item area
+Area averaging. Equivalent to @samp{bilinear} for upscaling.
+
+@item gaussian
+2-tap Gaussian filter approximation. The sharpness parameter can be configured
+by setting @code{param0}, defaulting to 3.0.
+
+@item sinc
+Unwindowed sinc filter.
+
+@item lanczos
+Lanczos resampling (sinc windowed sinc). The number of filter taps can
+be configured by setting @code{param0}, defaulting to 3.
+
+@item spline
+Unwindowed natural bicubic spline.
+@end table
+
 @anchor{sws_flags}
 @item sws_flags
 Set the scaler flags. This is also used to set the scaling
-algorithm. Only a single algorithm should be selected. Default
-value is @samp{bicubic}.
+algorithm, though this usage is deprecated in favor of setting @samp{scaler}.
+Only a single algorithm may be selected. Default value is @samp{bicubic}.
 
 It accepts the following values:
 @table @samp
 @item fast_bilinear
-Select fast bilinear scaling algorithm.
+Select fast bilinear scaling algorithm. (Deprecated)
 
 @item bilinear
-Select bilinear scaling algorithm.
+Select bilinear scaling algorithm. (Deprecated)
 
 @item bicubic
-Select bicubic scaling algorithm.
+Select bicubic scaling algorithm. (Deprecated)
 
 @item experimental
-Select experimental scaling algorithm.
+Select experimental scaling algorithm. (Deprecated)
 
 @item neighbor
-Select nearest neighbor rescaling algorithm.
+Select nearest neighbor rescaling algorithm. (Deprecated)
 
 @item area
-Select averaging area rescaling algorithm.
+Select averaging area rescaling algorithm. (Deprecated)
 
 @item bicublin
 Select bicubic scaling algorithm for the luma component, bilinear for
-chroma components.
+chroma components. (Deprecated)
 
 @item gauss
-Select Gaussian rescaling algorithm.
+Select Gaussian rescaling algorithm. (Deprecated)
 
 @item sinc
-Select sinc rescaling algorithm.
+Select sinc rescaling algorithm. (Deprecated)
 
 @item lanczos
 Select Lanczos rescaling algorithm. The default width (alpha) is 3 and can be
-changed by setting @code{param0}.
+changed by setting @code{param0}. (Deprecated)
 
 @item spline
-Select natural bicubic spline rescaling algorithm.
+Select natural bicubic spline rescaling algorithm. (Deprecated)
 
 @item print_info
 Enable printing/debug logging.
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to