Commit 8cab6bed70 for ffmpeg

commit 8cab6bed70e8db2902344896a81a2385db5140b2
Author: Niklas Haas <git@haasn.dev>
Date:   Sat Nov 7 21:24:53 2026 +0100

    swscale/filters: fix SWS_SCALE_AREA kernel

    This behaved like an anti-aliased point filter when downscaling, which
    is not the same as a true area filter. That should behave like a trapezoid,
    not a box, approaching a bilinear kernel (triangle filter) as the ratio
    approaches 1.0.

    Signed-off-by: Niklas Haas <git@haasn.dev>

diff --git a/libswscale/filters.c b/libswscale/filters.c
index 5544116961..e1a3cfb91e 100644
--- a/libswscale/filters.c
+++ b/libswscale/filters.c
@@ -224,16 +224,6 @@ int ff_sws_filter_generate(void *log, const SwsFilterParams *params,
         stretch_inv = ratio;
     }

-    if (scaler == SWS_SCALE_AREA) {
-        /**
-         * SWS_SCALE_AREA is a pseudo-filter that is equivalent to bilinear
-         * filtering for upscaling (since bilinear just evenly mixes samples
-         * according to the relative distance), and equivalent to (anti-aliased)
-         * point sampling for downscaling.
-         */
-        scaler = ratio >= 1.0 ? SWS_SCALE_BILINEAR : SWS_SCALE_POINT;
-    }
-
     SwsFilterFunction fun = filter_functions[scaler];
     if (!fun.kernel)
         return AVERROR(EINVAL);
@@ -260,6 +250,12 @@ int ff_sws_filter_generate(void *log, const SwsFilterParams *params,
     case SWS_SCALE_BILINEAR:
         radius = 1.0 - SWS_MAX_REDUCE_CUTOFF;
         break;
+    case SWS_SCALE_AREA:
+        /* Above ratio 1.0, this behaves like bilinear */
+        fun.params[0] = fmin(ratio, 1.0);
+        fun.radius = 0.5 + 0.5 * fun.params[0];
+        radius = fun.radius - SWS_MAX_REDUCE_CUTOFF * fun.params[0];
+        break;
     default:
         /* Numerically estimate radius of nontrivial or parametric kernels */
         radius = est_filter_radius(&fun);
@@ -353,6 +349,19 @@ av_unused static double box(double x, const double *params)
     return 1.0;
 }

+/**
+ * Overlap between a destination pixel (unit box) and a source pixel of width
+ * params[0] <= 1, i.e. the convolution of two box filters (a trapezoid).
+ */
+static double area(double x, const double *params)
+{
+    const double w = params[0];
+    const double edge = 0.5 - 0.5 * w;
+    if (x <= edge)
+        return 1.0;
+    return fmax(1.0 - (x - edge) / w, 0.0);
+}
+
 av_unused static double triangle(double x, const double *params)
 {
     return 1.0 - x;
@@ -496,5 +505,5 @@ static const SwsFilterFunction filter_functions[SWS_SCALE_NB] = {
     [SWS_SCALE_SINC]        = { "sinc",         RADIUS_MAX, sinc },
     [SWS_SCALE_LANCZOS]     = { "lanczos",      -1.0, sinc, sinc, .params = { 3.0 } },
     [SWS_SCALE_SPLINE]      = { "spline",       RADIUS_MAX, spline },
-    /* SWS_SCALE_AREA is a pseudo-filter, see code above */
+    [SWS_SCALE_AREA]        = { "area",         1.0, area },
 };