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

As well as a few cosmetic commits backported from 
https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22540


>From 926c759958d04249eb643b02b9cc14e6552da913 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 27 Mar 2026 14:17:19 +0100
Subject: [PATCH 01/14] swscale/format: move SwsFormat sanitization to helper
 function

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/format.c | 69 ++++++++++++++++++++++++---------------------
 1 file changed, 37 insertions(+), 32 deletions(-)

diff --git a/libswscale/format.c b/libswscale/format.c
index 3464a8af7c..af37ee09c8 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -305,6 +305,42 @@ int sws_isSupportedEndiannessConversion(enum AVPixelFormat 
pix_fmt)
     legacy_format_entries[pix_fmt].is_supported_endianness : 0;
 }
 
+static void sanitize_fmt(SwsFormat *fmt, const AVPixFmtDescriptor *desc)
+{
+    if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL | 
AV_PIX_FMT_FLAG_BAYER)) {
+        /* RGB-like family */
+        fmt->csp   = AVCOL_SPC_RGB;
+        fmt->range = AVCOL_RANGE_JPEG;
+    } else if (desc->flags & AV_PIX_FMT_FLAG_XYZ) {
+        fmt->csp   = AVCOL_SPC_UNSPECIFIED;
+        fmt->color = (SwsColor) {
+            .prim = AVCOL_PRI_BT709, /* swscale currently hard-codes this XYZ 
matrix */
+            .trc  = AVCOL_TRC_SMPTE428,
+        };
+    } else if (desc->nb_components < 3) {
+        /* Grayscale formats */
+        fmt->color.prim = AVCOL_PRI_UNSPECIFIED;
+        fmt->csp        = AVCOL_SPC_UNSPECIFIED;
+        if (desc->flags & AV_PIX_FMT_FLAG_FLOAT)
+            fmt->range = AVCOL_RANGE_UNSPECIFIED;
+        else
+            fmt->range = AVCOL_RANGE_JPEG; // FIXME: this restriction should 
be lifted
+    }
+
+    switch (av_pix_fmt_desc_get_id(desc)) {
+    case AV_PIX_FMT_YUVJ420P:
+    case AV_PIX_FMT_YUVJ411P:
+    case AV_PIX_FMT_YUVJ422P:
+    case AV_PIX_FMT_YUVJ444P:
+    case AV_PIX_FMT_YUVJ440P:
+        fmt->range = AVCOL_RANGE_JPEG;
+        break;
+    }
+
+    if (!desc->log2_chroma_w && !desc->log2_chroma_h)
+        fmt->loc = AVCHROMA_LOC_UNSPECIFIED;
+}
+
 /**
  * This function also sanitizes and strips the input data, removing irrelevant
  * fields for certain formats.
@@ -346,38 +382,7 @@ SwsFormat ff_fmt_from_frame(const AVFrame *frame, int 
field)
     av_assert1(fmt.height > 0);
     av_assert1(fmt.format != AV_PIX_FMT_NONE);
     av_assert0(desc);
-    if (desc->flags & (AV_PIX_FMT_FLAG_RGB | AV_PIX_FMT_FLAG_PAL | 
AV_PIX_FMT_FLAG_BAYER)) {
-        /* RGB-like family */
-        fmt.csp   = AVCOL_SPC_RGB;
-        fmt.range = AVCOL_RANGE_JPEG;
-    } else if (desc->flags & AV_PIX_FMT_FLAG_XYZ) {
-        fmt.csp   = AVCOL_SPC_UNSPECIFIED;
-        fmt.color = (SwsColor) {
-            .prim = AVCOL_PRI_BT709, /* swscale currently hard-codes this XYZ 
matrix */
-            .trc  = AVCOL_TRC_SMPTE428,
-        };
-    } else if (desc->nb_components < 3) {
-        /* Grayscale formats */
-        fmt.color.prim = AVCOL_PRI_UNSPECIFIED;
-        fmt.csp        = AVCOL_SPC_UNSPECIFIED;
-        if (desc->flags & AV_PIX_FMT_FLAG_FLOAT)
-            fmt.range = AVCOL_RANGE_UNSPECIFIED;
-        else
-            fmt.range = AVCOL_RANGE_JPEG; // FIXME: this restriction should be 
lifted
-    }
-
-    switch (frame->format) {
-    case AV_PIX_FMT_YUVJ420P:
-    case AV_PIX_FMT_YUVJ411P:
-    case AV_PIX_FMT_YUVJ422P:
-    case AV_PIX_FMT_YUVJ444P:
-    case AV_PIX_FMT_YUVJ440P:
-        fmt.range = AVCOL_RANGE_JPEG;
-        break;
-    }
-
-    if (!desc->log2_chroma_w && !desc->log2_chroma_h)
-        fmt.loc = AVCHROMA_LOC_UNSPECIFIED;
+    sanitize_fmt(&fmt, desc);
 
     if (frame->flags & AV_FRAME_FLAG_INTERLACED) {
         fmt.height = (fmt.height + (field == FIELD_TOP)) >> 1;
-- 
2.52.0


>From 6d9d219a1b55124234d65d0b0837a0e60775e6a0 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 27 Mar 2026 14:19:09 +0100
Subject: [PATCH 02/14] swscale/format: add helper function to get "default"
 SwsFormat

But still apply the sanitization/defaulting logic from ff_fmt_from_frame().

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/format.c | 10 ++++++++++
 libswscale/format.h |  5 +++++
 2 files changed, 15 insertions(+)

diff --git a/libswscale/format.c b/libswscale/format.c
index af37ee09c8..f7d420029e 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -479,6 +479,16 @@ skip_hdr10:
     return fmt;
 }
 
+SwsFormat ff_fmt_from_pixfmt(enum AVPixelFormat pixfmt)
+{
+    SwsFormat fmt;
+    ff_fmt_clear(&fmt);
+    fmt.format = pixfmt;
+    fmt.desc = av_pix_fmt_desc_get(pixfmt);
+    sanitize_fmt(&fmt, fmt.desc);
+    return fmt;
+}
+
 static int infer_prim_ref(SwsColor *csp, const SwsColor *ref)
 {
     if (csp->prim != AVCOL_PRI_UNSPECIFIED)
diff --git a/libswscale/format.h b/libswscale/format.h
index d66851893f..ba5447da8b 100644
--- a/libswscale/format.h
+++ b/libswscale/format.h
@@ -106,6 +106,11 @@ static inline void ff_fmt_clear(SwsFormat *fmt)
  */
 SwsFormat ff_fmt_from_frame(const AVFrame *frame, int field);
 
+/**
+ * Subset of ff_fmt_from_frame() that sets default metadata for the format.
+ */
+SwsFormat ff_fmt_from_pixfmt(enum AVPixelFormat pixfmt);
+
 static inline int ff_color_equal(const SwsColor *c1, const SwsColor *c2)
 {
     return  c1->prim == c2->prim &&
-- 
2.52.0


>From 82905de60061a3c17badbba7ce83e613677f11f0 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 27 Mar 2026 14:24:46 +0100
Subject: [PATCH 03/14] swscale/tests/sws_ops: simplify using
 ff_fmt_from_pixfmt()

No need to allocate a dummy frame just for ff_fmt_from_frame().

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/tests/sws_ops.c | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c
index 504fc34f6b..d2803c6da9 100644
--- a/libswscale/tests/sws_ops.c
+++ b/libswscale/tests/sws_ops.c
@@ -27,17 +27,16 @@
 #include <fcntl.h>
 #endif
 
-static int run_test(SwsContext *const ctx, AVFrame *frame,
-                    const AVPixFmtDescriptor *const src_desc,
-                    const AVPixFmtDescriptor *const dst_desc)
+static int run_test(SwsContext *const ctx, enum AVPixelFormat src_fmt,
+                    enum AVPixelFormat dst_fmt)
 {
-    /* Reuse ff_fmt_from_frame() to ensure correctly sanitized metadata */
-    frame->format = av_pix_fmt_desc_get_id(src_desc);
-    SwsFormat src = ff_fmt_from_frame(frame, 0);
-    frame->format = av_pix_fmt_desc_get_id(dst_desc);
-    SwsFormat dst = ff_fmt_from_frame(frame, 0);
+    SwsFormat src = ff_fmt_from_pixfmt(src_fmt);
+    SwsFormat dst = ff_fmt_from_pixfmt(dst_fmt);
     bool incomplete = ff_infer_colors(&src.color, &dst.color);
 
+    src.width  = dst.width  = 16;
+    src.height = dst.height = 16;
+
     SwsOpList *ops = ff_sws_op_list_alloc();
     if (!ops)
         return AVERROR(ENOMEM);
@@ -124,10 +123,8 @@ bad_option:
     }
 
     SwsContext *ctx = sws_alloc_context();
-    AVFrame *frame = av_frame_alloc();
-    if (!ctx || !frame)
+    if (!ctx)
         goto fail;
-    frame->width = frame->height = 16;
 
     av_log_set_callback(log_stdout);
     for (const AVPixFmtDescriptor *src = NULL; (src = 
av_pix_fmt_desc_next(src));) {
@@ -138,7 +135,7 @@ bad_option:
             enum AVPixelFormat dst_fmt = av_pix_fmt_desc_get_id(dst);
             if (dst_fmt < dst_fmt_min || dst_fmt > dst_fmt_max)
                 continue;
-            int err = run_test(ctx, frame, src, dst);
+            int err = run_test(ctx, src_fmt, dst_fmt);
             if (err < 0)
                 goto fail;
         }
@@ -146,7 +143,6 @@ bad_option:
 
     ret = 0;
 fail:
-    av_frame_free(&frame);
     sws_free_context(&ctx);
     return ret;
 
-- 
2.52.0


>From 45ff7396a3d5f4a45706705d4bd93dd6b9a8901a Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 27 Mar 2026 14:38:07 +0100
Subject: [PATCH 04/14] swscale/ops: add helper function to enumerate over all
 op lists

This moves the logic from tests/sws_ops into the library itself, where it
can be reused by e.g. the aarch64 asmgen backend to iterate over all possible
operation types it can expect to see.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++
 libswscale/ops.h | 26 +++++++++++++++++
 2 files changed, 101 insertions(+)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index ece49e299f..08c1d64864 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -25,6 +25,7 @@
 #include "libavutil/rational.h"
 #include "libavutil/refstruct.h"
 
+#include "format.h"
 #include "ops.h"
 #include "ops_internal.h"
 
@@ -877,3 +878,77 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
 
     av_log(log, lev, "    (X = unused, z = byteswapped, + = exact, 0 = 
zero)\n");
 }
+
+static int enum_ops_fmt(SwsContext *ctx, void *opaque,
+                        int (*cb)(SwsContext *ctx, void *opaque, SwsOpList 
*ops),
+                        enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt)
+{
+    const SwsPixelType type = SWS_PIXEL_F32;
+    SwsOpList *ops = ff_sws_op_list_alloc();
+    if (!ops)
+        return AVERROR(ENOMEM);
+
+    ops->src = ff_fmt_from_pixfmt(src_fmt);
+    ops->dst = ff_fmt_from_pixfmt(dst_fmt);
+    ops->src.width  = ops->dst.width  = 16;
+    ops->src.height = ops->dst.height = 16;
+
+    bool incomplete = ff_infer_colors(&ops->src.color, &ops->dst.color);
+    if (ff_sws_decode_pixfmt(ops, src_fmt) < 0 ||
+        ff_sws_decode_colors(ctx, type, ops, &ops->src, &incomplete) < 0 ||
+        ff_sws_encode_colors(ctx, type, ops, &ops->src, &ops->dst, 
&incomplete) < 0 ||
+        ff_sws_encode_pixfmt(ops, dst_fmt) < 0)
+        goto fail;
+
+    int ret = ff_sws_op_list_optimize(ops);
+    if (ret < 0)
+        return ret;
+
+    ret = cb(ctx, opaque, ops);
+    if (ret < 0)
+        return ret;
+
+fail:
+    /* silently skip unsupported formats */
+    ff_sws_op_list_free(&ops);
+    return 0;
+}
+
+int ff_sws_enum_op_lists(SwsContext *ctx, void *opaque,
+                         int (*cb)(SwsContext *ctx, void *opaque, SwsOpList 
*ops))
+{
+    for (const AVPixFmtDescriptor *src = NULL; (src = 
av_pix_fmt_desc_next(src));) {
+        for (const AVPixFmtDescriptor *dst = NULL; (dst = 
av_pix_fmt_desc_next(dst));) {
+            const enum AVPixelFormat src_fmt = av_pix_fmt_desc_get_id(src);
+            const enum AVPixelFormat dst_fmt = av_pix_fmt_desc_get_id(dst);
+            int ret = enum_ops_fmt(ctx, opaque, cb, src_fmt, dst_fmt);
+            if (ret < 0)
+                return ret;
+        }
+    }
+
+    return 0;
+}
+
+struct EnumOpaque {
+    void *opaque;
+    int (*cb)(SwsContext *ctx, void *opaque, SwsOp *op);
+};
+
+static int enum_ops(SwsContext *ctx, void *opaque, SwsOpList *ops)
+{
+    struct EnumOpaque *priv = opaque;
+    for (int i = 0; i < ops->num_ops; i++) {
+        int ret = priv->cb(ctx, priv->opaque, &ops->ops[i]);
+        if (ret < 0)
+            return ret;
+    }
+    return 0;
+}
+
+int ff_sws_enum_ops(SwsContext *ctx, void *opaque,
+                    int (*cb)(SwsContext *ctx, void *opaque, SwsOp *op))
+{
+    struct EnumOpaque priv = { opaque, cb };
+    return ff_sws_enum_op_lists(ctx, &priv, enum_ops);
+}
diff --git a/libswscale/ops.h b/libswscale/ops.h
index bc8e751f7b..39d3ffe526 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -319,4 +319,30 @@ enum SwsOpCompileFlags {
 int ff_sws_compile_pass(SwsGraph *graph, SwsOpList **ops, int flags,
                         SwsPass *input, SwsPass **output);
 
+/**
+ * Helper function to enumerate over all possible (optimized) operation lists,
+ * under the current set of options in `ctx`, and run the given callback on
+ * each list.
+ *
+ * @return 0 on success, the return value if cb() < 0, or a negative error code
+ *
+ * @note `ops` belongs to ff_sws_enum_ops_lists(), but may be mutated by `cb`.
+ * @see ff_sws_enum_ops()
+ */
+int ff_sws_enum_op_lists(SwsContext *ctx, void *opaque,
+                         int (*cb)(SwsContext *ctx, void *opaque, SwsOpList 
*ops));
+
+/**
+ * Helper function to enumerate over all possible operations, under the current
+ * set of options in `ctx`, and run the given callback on each operation.
+ *
+ * @return 0 on success, the return value if cb() < 0, or a negative error code
+ *
+ * @note May contain duplicates. `op` belongs to ff_sws_num_ops(), but may be
+ *       mutated by `cb`.
+ * @see ff_sws_num_op_lists()
+ */
+int ff_sws_enum_ops(SwsContext *ctx, void *opaque,
+                    int (*cb)(SwsContext *ctx, void *opaque, SwsOp *op));
+
 #endif
-- 
2.52.0


>From b7f49a4c3e71d798ad31af737a00e73743e49431 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 27 Mar 2026 14:39:40 +0100
Subject: [PATCH 05/14] swscale/tests/sws_ops: simplify using
 ff_sws_enum_op_lists()

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/tests/sws_ops.c | 55 +++++++-------------------------------
 1 file changed, 9 insertions(+), 46 deletions(-)

diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c
index d2803c6da9..c4c1728ce1 100644
--- a/libswscale/tests/sws_ops.c
+++ b/libswscale/tests/sws_ops.c
@@ -27,46 +27,19 @@
 #include <fcntl.h>
 #endif
 
-static int run_test(SwsContext *const ctx, enum AVPixelFormat src_fmt,
-                    enum AVPixelFormat dst_fmt)
+static int print_ops(SwsContext *const ctx, void *opaque, SwsOpList *ops)
 {
-    SwsFormat src = ff_fmt_from_pixfmt(src_fmt);
-    SwsFormat dst = ff_fmt_from_pixfmt(dst_fmt);
-    bool incomplete = ff_infer_colors(&src.color, &dst.color);
+    av_log(opaque, AV_LOG_INFO, "%s -> %s:\n",
+           av_get_pix_fmt_name(ops->src.format),
+           av_get_pix_fmt_name(ops->dst.format));
 
-    src.width  = dst.width  = 16;
-    src.height = dst.height = 16;
-
-    SwsOpList *ops = ff_sws_op_list_alloc();
-    if (!ops)
-        return AVERROR(ENOMEM);
-    ops->src = src;
-    ops->dst = dst;
-
-    if (ff_sws_decode_pixfmt(ops, src.format) < 0)
-        goto fail;
-    if (ff_sws_decode_colors(ctx, SWS_PIXEL_F32, ops, &src, &incomplete) < 0)
-        goto fail;
-    if (ff_sws_encode_colors(ctx, SWS_PIXEL_F32, ops, &src, &dst, &incomplete) 
< 0)
-        goto fail;
-    if (ff_sws_encode_pixfmt(ops, dst.format) < 0)
-        goto fail;
-
-    av_log(NULL, AV_LOG_INFO, "%s -> %s:\n",
-           av_get_pix_fmt_name(src.format), av_get_pix_fmt_name(dst.format));
-
-    ff_sws_op_list_optimize(ops);
     if (ff_sws_op_list_is_noop(ops))
-        av_log(NULL, AV_LOG_INFO, "  (no-op)\n");
+        av_log(opaque, AV_LOG_INFO, "  (no-op)\n");
     else
-        ff_sws_op_list_print(NULL, AV_LOG_INFO, AV_LOG_INFO, ops);
+        ff_sws_op_list_print(opaque, AV_LOG_INFO, AV_LOG_INFO, ops);
 
-fail:
-    /* silently skip unsupported formats */
-    ff_sws_op_list_free(&ops);
     return 0;
 }
-
 static void log_stdout(void *avcl, int level, const char *fmt, va_list vl)
 {
     if (level != AV_LOG_INFO) {
@@ -127,19 +100,9 @@ bad_option:
         goto fail;
 
     av_log_set_callback(log_stdout);
-    for (const AVPixFmtDescriptor *src = NULL; (src = 
av_pix_fmt_desc_next(src));) {
-        enum AVPixelFormat src_fmt = av_pix_fmt_desc_get_id(src);
-        if (src_fmt < src_fmt_min || src_fmt > src_fmt_max)
-            continue;
-        for (const AVPixFmtDescriptor *dst = NULL; (dst = 
av_pix_fmt_desc_next(dst));) {
-            enum AVPixelFormat dst_fmt = av_pix_fmt_desc_get_id(dst);
-            if (dst_fmt < dst_fmt_min || dst_fmt > dst_fmt_max)
-                continue;
-            int err = run_test(ctx, src_fmt, dst_fmt);
-            if (err < 0)
-                goto fail;
-        }
-    }
+    ret = ff_sws_enum_op_lists(ctx, NULL, print_ops);
+    if (ret < 0)
+        goto fail;
 
     ret = 0;
 fail:
-- 
2.52.0


>From c3d03b2aa0c2c9280d2b4e17dd935bb2fa1a3fcb Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Tue, 24 Mar 2026 16:13:57 +0100
Subject: [PATCH 06/14] swscale/tests/sws_ops: add -v option to set log
 verbosity

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/sws_ops.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c
index c4c1728ce1..4e91b73b3c 100644
--- a/libswscale/tests/sws_ops.c
+++ b/libswscale/tests/sws_ops.c
@@ -44,7 +44,7 @@ static void log_stdout(void *avcl, int level, const char 
*fmt, va_list vl)
 {
     if (level != AV_LOG_INFO) {
         av_log_default_callback(avcl, level, fmt, vl);
-    } else {
+    } else if (av_log_get_level() >= AV_LOG_INFO) {
         vfprintf(stdout, fmt, vl);
     }
 }
@@ -71,6 +71,8 @@ int main(int argc, char **argv)
                     "       Only test the specified destination pixel format\n"
                     "   -src <pixfmt>\n"
                     "       Only test the specified source pixel format\n"
+                    "   -v <level>\n"
+                    "       Enable log verbosity at given level\n"
             );
             return 0;
         }
@@ -88,6 +90,8 @@ int main(int argc, char **argv)
                 fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]);
                 goto error;
             }
+        } else if (!strcmp(argv[i], "-v")) {
+            av_log_set_level(atoi(argv[i + 1]));
         } else {
 bad_option:
             fprintf(stderr, "bad option or argument missing (%s) see -help\n", 
argv[i]);
-- 
2.52.0


>From 3552fd5d213757e89ebe02ce303bd15b9222cc81 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Mar 2026 16:30:28 +0100
Subject: [PATCH 07/14] swscale/ops: correctly uninit all ops in
 ff_sws_op_list_remove_at()

This only ever removed a single op, even with count > 1.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 08c1d64864..d2c7ec6fc9 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -588,9 +588,10 @@ const SwsOp *ff_sws_op_list_output(const SwsOpList *ops)
 
 void ff_sws_op_list_remove_at(SwsOpList *ops, int index, int count)
 {
-    const int end = ops->num_ops - count;
     av_assert2(index >= 0 && count >= 0 && index + count <= ops->num_ops);
-    op_uninit(&ops->ops[index]);
+    for (int i = 0; i < count; i++)
+        op_uninit(&ops->ops[index + i]);
+    const int end = ops->num_ops - count;
     for (int i = index; i < end; i++)
         ops->ops[i] = ops->ops[i + count];
     ops->num_ops = end;
-- 
2.52.0


>From 92dfd73d12c566b67ed5d4e3cc07085709336c84 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Tue, 17 Mar 2026 01:21:37 +0100
Subject: [PATCH 08/14] swscale/ops: print exact constant on SWS_OP_SCALE

More informative.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 2 +-
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index d2c7ec6fc9..11015f23cd 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -853,7 +853,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra,
                    PRINTQ(op->lin.m[3][0]), PRINTQ(op->lin.m[3][1]), 
PRINTQ(op->lin.m[3][2]), PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4]));
             break;
         case SWS_OP_SCALE:
-            av_log(log, lev, "%-20s: * %s\n", name, PRINTQ(op->c.q));
+            av_log(log, lev, "%-20s: * %d/%d\n", name, op->c.q.num, 
op->c.q.den);
             break;
         case SWS_OP_TYPE_NB:
             break;
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index c83105406f..4cfce95495 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-121d60ee0261ca5d9650a8d1e9e8a060
+c014aa1e7c12d3bb101951487c406148
-- 
2.52.0


>From 2614284bd4155a278356bcbd41b17eca7b177c6f Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Mar 2026 11:26:14 +0100
Subject: [PATCH 09/14] swscale/ops: use AVBPrint for assembling op
 descriptions

This commit does not yet touch the PRINTQ macro, but it gets rid of at least
one unnecessary hand-managed buffer.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 147 ++++++++++++++++++++++++-----------------------
 1 file changed, 76 insertions(+), 71 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 11015f23cd..463b6bf0ae 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -20,6 +20,7 @@
 
 #include "libavutil/avassert.h"
 #include "libavutil/avstring.h"
+#include "libavutil/bprint.h"
 #include "libavutil/bswap.h"
 #include "libavutil/mem.h"
 #include "libavutil/rational.h"
@@ -723,18 +724,6 @@ static char describe_comp_flags(SwsCompFlags flags)
         return '.';
 }
 
-static const char *describe_order(SwsSwizzleOp order, int planes, char buf[32])
-{
-    if (order.mask == SWS_SWIZZLE(0, 1, 2, 3).mask)
-        return "";
-
-    av_strlcpy(buf, ", via {", 32);
-    for (int i = 0; i < planes; i++)
-        av_strlcatf(buf, 32, "%s%d", i ? ", " : "", order.in[i]);
-    av_strlcat(buf, "}", 32);
-    return buf;
-}
-
 static const char *print_q(const AVRational q, char buf[], int buf_len)
 {
     if (!q.den) {
@@ -756,109 +745,125 @@ static const char *print_q(const AVRational q, char 
buf[], int buf_len)
 void ff_sws_op_list_print(void *log, int lev, int lev_extra,
                           const SwsOpList *ops)
 {
+    AVBPrint bp;
     if (!ops->num_ops) {
         av_log(log, lev, "  (empty)\n");
         return;
     }
 
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
     for (int i = 0; i < ops->num_ops; i++) {
         const SwsOp *op   = &ops->ops[i];
         const SwsOp *next = i + 1 < ops->num_ops ? &ops->ops[i + 1] : op;
         const char *name  = ff_sws_op_type_name(op->op);
-        char buf[32];
-
-        av_log(log, lev, "  [%3s %c%c%c%c -> %c%c%c%c] ",
-               ff_sws_pixel_type_name(op->type),
-               op->comps.unused[0] ? 'X' : '.',
-               op->comps.unused[1] ? 'X' : '.',
-               op->comps.unused[2] ? 'X' : '.',
-               op->comps.unused[3] ? 'X' : '.',
-               next->comps.unused[0] ? 'X' : 
describe_comp_flags(op->comps.flags[0]),
-               next->comps.unused[1] ? 'X' : 
describe_comp_flags(op->comps.flags[1]),
-               next->comps.unused[2] ? 'X' : 
describe_comp_flags(op->comps.flags[2]),
-               next->comps.unused[3] ? 'X' : 
describe_comp_flags(op->comps.flags[3]));
+        av_bprint_clear(&bp);
+        av_bprintf(&bp, "  [%3s %c%c%c%c -> %c%c%c%c] ",
+                   ff_sws_pixel_type_name(op->type),
+                   op->comps.unused[0] ? 'X' : '.',
+                   op->comps.unused[1] ? 'X' : '.',
+                   op->comps.unused[2] ? 'X' : '.',
+                   op->comps.unused[3] ? 'X' : '.',
+                   next->comps.unused[0] ? 'X' : 
describe_comp_flags(op->comps.flags[0]),
+                   next->comps.unused[1] ? 'X' : 
describe_comp_flags(op->comps.flags[1]),
+                   next->comps.unused[2] ? 'X' : 
describe_comp_flags(op->comps.flags[2]),
+                   next->comps.unused[3] ? 'X' : 
describe_comp_flags(op->comps.flags[3]));
 
         switch (op->op) {
         case SWS_OP_INVALID:
         case SWS_OP_SWAP_BYTES:
-            av_log(log, lev, "%s\n", name);
+            av_bprintf(&bp, "%s", name);
             break;
         case SWS_OP_READ:
         case SWS_OP_WRITE:
-            av_log(log, lev, "%-20s: %d elem(s) %s >> %d%s\n", name,
-                   op->rw.elems,  op->rw.packed ? "packed" : "planar",
-                   op->rw.frac,
-                   describe_order(op->op == SWS_OP_READ ? ops->order_src
-                                                        : ops->order_dst,
-                                  op->rw.packed ? 1 : op->rw.elems, buf));
+            av_bprintf(&bp, "%-20s: %d elem(s) %s >> %d", name,
+                       op->rw.elems,  op->rw.packed ? "packed" : "planar",
+                       op->rw.frac);
+            SwsSwizzleOp order = op->op == SWS_OP_READ ? ops->order_src : 
ops->order_dst;
+            if (order.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) {
+                const int planes = op->rw.packed ? 1 : op->rw.elems;
+                av_bprintf(&bp, ", via {");
+                for (int i = 0; i < planes; i++)
+                    av_bprintf(&bp, "%s%d", i ? ", " : "", order.in[i]);
+                av_bprintf(&bp, "}");
+            }
             break;
         case SWS_OP_LSHIFT:
-            av_log(log, lev, "%-20s: << %u\n", name, op->c.u);
+            av_bprintf(&bp, "%-20s: << %u", name, op->c.u);
             break;
         case SWS_OP_RSHIFT:
-            av_log(log, lev, "%-20s: >> %u\n", name, op->c.u);
+            av_bprintf(&bp, "%-20s: >> %u", name, op->c.u);
             break;
         case SWS_OP_PACK:
         case SWS_OP_UNPACK:
-            av_log(log, lev, "%-20s: {%d %d %d %d}\n", name,
-                   op->pack.pattern[0], op->pack.pattern[1],
-                   op->pack.pattern[2], op->pack.pattern[3]);
+            av_bprintf(&bp, "%-20s: {%d %d %d %d}", name,
+                       op->pack.pattern[0], op->pack.pattern[1],
+                       op->pack.pattern[2], op->pack.pattern[3]);
             break;
         case SWS_OP_CLEAR:
-            av_log(log, lev, "%-20s: {%s %s %s %s}\n", name,
-                   op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
-                   op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
-                   op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
-                   op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
+            av_bprintf(&bp, "%-20s: {%s %s %s %s}", name,
+                       op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
+                       op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
+                       op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
+                       op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
             break;
         case SWS_OP_SWIZZLE:
-            av_log(log, lev, "%-20s: %d%d%d%d\n", name,
-                   op->swizzle.x, op->swizzle.y, op->swizzle.z, op->swizzle.w);
+            av_bprintf(&bp, "%-20s: %d%d%d%d", name,
+                       op->swizzle.x, op->swizzle.y, op->swizzle.z, 
op->swizzle.w);
             break;
         case SWS_OP_CONVERT:
-            av_log(log, lev, "%-20s: %s -> %s%s\n", name,
-                   ff_sws_pixel_type_name(op->type),
-                   ff_sws_pixel_type_name(op->convert.to),
-                   op->convert.expand ? " (expand)" : "");
+            av_bprintf(&bp, "%-20s: %s -> %s%s", name,
+                       ff_sws_pixel_type_name(op->type),
+                       ff_sws_pixel_type_name(op->convert.to),
+                       op->convert.expand ? " (expand)" : "");
             break;
         case SWS_OP_DITHER:
-            av_log(log, lev, "%-20s: %dx%d matrix + {%d %d %d %d}\n", name,
-                    1 << op->dither.size_log2, 1 << op->dither.size_log2,
-                    op->dither.y_offset[0], op->dither.y_offset[1],
-                    op->dither.y_offset[2], op->dither.y_offset[3]);
+            av_bprintf(&bp, "%-20s: %dx%d matrix + {%d %d %d %d}", name,
+                       1 << op->dither.size_log2, 1 << op->dither.size_log2,
+                       op->dither.y_offset[0], op->dither.y_offset[1],
+                       op->dither.y_offset[2], op->dither.y_offset[3]);
             break;
         case SWS_OP_MIN:
-            av_log(log, lev, "%-20s: x <= {%s %s %s %s}\n", name,
-                    op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
-                    op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
-                    op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
-                    op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
+            av_bprintf(&bp, "%-20s: x <= {%s %s %s %s}", name,
+                       op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
+                       op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
+                       op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
+                       op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
             break;
         case SWS_OP_MAX:
-            av_log(log, lev, "%-20s: {%s %s %s %s} <= x\n", name,
-                    op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
-                    op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
-                    op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
-                    op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
+            av_bprintf(&bp, "%-20s: {%s %s %s %s} <= x", name,
+                       op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
+                       op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
+                       op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
+                       op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
             break;
         case SWS_OP_LINEAR:
-            av_log(log, lev, "%-20s: %s [[%s %s %s %s %s] "
-                                        "[%s %s %s %s %s] "
-                                        "[%s %s %s %s %s] "
-                                        "[%s %s %s %s %s]]\n",
-                   name, describe_lin_mask(op->lin.mask),
-                   PRINTQ(op->lin.m[0][0]), PRINTQ(op->lin.m[0][1]), 
PRINTQ(op->lin.m[0][2]), PRINTQ(op->lin.m[0][3]), PRINTQ(op->lin.m[0][4]),
-                   PRINTQ(op->lin.m[1][0]), PRINTQ(op->lin.m[1][1]), 
PRINTQ(op->lin.m[1][2]), PRINTQ(op->lin.m[1][3]), PRINTQ(op->lin.m[1][4]),
-                   PRINTQ(op->lin.m[2][0]), PRINTQ(op->lin.m[2][1]), 
PRINTQ(op->lin.m[2][2]), PRINTQ(op->lin.m[2][3]), PRINTQ(op->lin.m[2][4]),
-                   PRINTQ(op->lin.m[3][0]), PRINTQ(op->lin.m[3][1]), 
PRINTQ(op->lin.m[3][2]), PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4]));
+            av_bprintf(&bp, "%-20s: %s [[%s %s %s %s %s] "
+                                       "[%s %s %s %s %s] "
+                                       "[%s %s %s %s %s] "
+                                       "[%s %s %s %s %s]]",
+                       name, describe_lin_mask(op->lin.mask),
+                       PRINTQ(op->lin.m[0][0]), PRINTQ(op->lin.m[0][1]),
+                       PRINTQ(op->lin.m[0][2]), PRINTQ(op->lin.m[0][3]),
+                       PRINTQ(op->lin.m[0][4]), PRINTQ(op->lin.m[1][0]),
+                       PRINTQ(op->lin.m[1][1]), PRINTQ(op->lin.m[1][2]),
+                       PRINTQ(op->lin.m[1][3]), PRINTQ(op->lin.m[1][4]),
+                       PRINTQ(op->lin.m[2][0]), PRINTQ(op->lin.m[2][1]),
+                       PRINTQ(op->lin.m[2][2]), PRINTQ(op->lin.m[2][3]),
+                       PRINTQ(op->lin.m[2][4]), PRINTQ(op->lin.m[3][0]),
+                       PRINTQ(op->lin.m[3][1]), PRINTQ(op->lin.m[3][2]),
+                       PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4]));
             break;
         case SWS_OP_SCALE:
-            av_log(log, lev, "%-20s: * %d/%d\n", name, op->c.q.num, 
op->c.q.den);
+            av_bprintf(&bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den);
             break;
         case SWS_OP_TYPE_NB:
             break;
         }
 
+        av_assert0(av_bprint_is_complete(&bp));
+        av_log(log, lev, "%s\n", bp.str);
+
         if (op->comps.min[0].den || op->comps.min[1].den ||
             op->comps.min[2].den || op->comps.min[3].den ||
             op->comps.max[0].den || op->comps.max[1].den ||
-- 
2.52.0


>From a08f875f03c638d0a73169bb2bee9372e32fc1d2 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Mar 2026 11:46:54 +0100
Subject: [PATCH 10/14] swscale/ops: remove , from comp min/max print-out for
 consistency

Interferes with an upcoming simplification, otherwise.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 2 +-
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 463b6bf0ae..3c926db61a 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -869,7 +869,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra,
             op->comps.max[0].den || op->comps.max[1].den ||
             op->comps.max[2].den || op->comps.max[3].den)
         {
-            av_log(log, lev_extra, "    min: {%s, %s, %s, %s}, max: {%s, %s, 
%s, %s}\n",
+            av_log(log, lev_extra, "    min: {%s %s %s %s}, max: {%s %s %s 
%s}\n",
                    next->comps.unused[0] ? "_" : PRINTQ(op->comps.min[0]),
                    next->comps.unused[1] ? "_" : PRINTQ(op->comps.min[1]),
                    next->comps.unused[2] ? "_" : PRINTQ(op->comps.min[2]),
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index 4cfce95495..28ca16f094 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-c014aa1e7c12d3bb101951487c406148
+ca5418b78a95b6b4eab07253f13ef731
-- 
2.52.0


>From 4644eb81be0edc6dfd53f893df7e29557cdcb2e0 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Mar 2026 11:55:24 +0100
Subject: [PATCH 11/14] swscale/ops: refactor PRINTQ() macro

Instead of allocating a billion tiny temporary buffers, these helpers now
directly append to an AVBPrint. I decided to explicitly control whether or not
a value with denom 0 should be printed as "inf/nan" or as "_", because a lot
of ops have the implicit semantic of "den == 0 -> ignored". At the same time,
we don't want to obscure legitimate NAN/INF values when the do occur
unintentionally.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 96 ++++++++++++++++++++++++------------------------
 1 file changed, 47 insertions(+), 49 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 3c926db61a..bf9611a3ad 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -724,23 +724,36 @@ static char describe_comp_flags(SwsCompFlags flags)
         return '.';
 }
 
-static const char *print_q(const AVRational q, char buf[], int buf_len)
+static void print_q(AVBPrint *bp, const AVRational q, bool ignore_den0)
 {
-    if (!q.den) {
-        return q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan";
+    if (!q.den && ignore_den0) {
+        av_bprintf(bp, "_");
+    } else if (!q.den) {
+        av_bprintf(bp, "%s", q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan");
     } else if (q.den == 1) {
-        snprintf(buf, buf_len, "%d", q.num);
-        return buf;
+        av_bprintf(bp, "%d", q.num);
     } else if (abs(q.num) > 1000 || abs(q.den) > 1000) {
-        snprintf(buf, buf_len, "%f", av_q2d(q));
-        return buf;
+        av_bprintf(bp, "%f", av_q2d(q));
     } else {
-        snprintf(buf, buf_len, "%d/%d", q.num, q.den);
-        return buf;
+        av_bprintf(bp, "%d/%d", q.num, q.den);
     }
 }
 
-#define PRINTQ(q) print_q(q, (char[32]){0}, sizeof(char[32]))
+static void print_q4(AVBPrint *bp, const AVRational q4[4], bool ignore_den0,
+                     const bool unused[4])
+{
+    av_bprintf(bp, "{");
+    for (int i = 0; i < 4; i++) {
+        if (i)
+            av_bprintf(bp, " ");
+        if (unused && unused[i]) {
+            av_bprintf(bp, "_");
+        } else {
+            print_q(bp, q4[i], ignore_den0);
+        }
+    }
+    av_bprintf(bp, "}");
+}
 
 void ff_sws_op_list_print(void *log, int lev, int lev_extra,
                           const SwsOpList *ops)
@@ -801,11 +814,8 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
                        op->pack.pattern[2], op->pack.pattern[3]);
             break;
         case SWS_OP_CLEAR:
-            av_bprintf(&bp, "%-20s: {%s %s %s %s}", name,
-                       op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
-                       op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
-                       op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
-                       op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
+            av_bprintf(&bp, "%-20s: ", name);
+            print_q4(&bp, op->c.q4, true, NULL);
             break;
         case SWS_OP_SWIZZLE:
             av_bprintf(&bp, "%-20s: %d%d%d%d", name,
@@ -824,35 +834,25 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
                        op->dither.y_offset[2], op->dither.y_offset[3]);
             break;
         case SWS_OP_MIN:
-            av_bprintf(&bp, "%-20s: x <= {%s %s %s %s}", name,
-                       op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
-                       op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
-                       op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
-                       op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
+            av_bprintf(&bp, "%-20s: x <= ", name);
+            print_q4(&bp, op->c.q4, true, NULL);
             break;
         case SWS_OP_MAX:
-            av_bprintf(&bp, "%-20s: {%s %s %s %s} <= x", name,
-                       op->c.q4[0].den ? PRINTQ(op->c.q4[0]) : "_",
-                       op->c.q4[1].den ? PRINTQ(op->c.q4[1]) : "_",
-                       op->c.q4[2].den ? PRINTQ(op->c.q4[2]) : "_",
-                       op->c.q4[3].den ? PRINTQ(op->c.q4[3]) : "_");
+            av_bprintf(&bp, "%-20s: ", name);
+            print_q4(&bp, op->c.q4, true, NULL);
+            av_bprintf(&bp, " <= x");
             break;
         case SWS_OP_LINEAR:
-            av_bprintf(&bp, "%-20s: %s [[%s %s %s %s %s] "
-                                       "[%s %s %s %s %s] "
-                                       "[%s %s %s %s %s] "
-                                       "[%s %s %s %s %s]]",
-                       name, describe_lin_mask(op->lin.mask),
-                       PRINTQ(op->lin.m[0][0]), PRINTQ(op->lin.m[0][1]),
-                       PRINTQ(op->lin.m[0][2]), PRINTQ(op->lin.m[0][3]),
-                       PRINTQ(op->lin.m[0][4]), PRINTQ(op->lin.m[1][0]),
-                       PRINTQ(op->lin.m[1][1]), PRINTQ(op->lin.m[1][2]),
-                       PRINTQ(op->lin.m[1][3]), PRINTQ(op->lin.m[1][4]),
-                       PRINTQ(op->lin.m[2][0]), PRINTQ(op->lin.m[2][1]),
-                       PRINTQ(op->lin.m[2][2]), PRINTQ(op->lin.m[2][3]),
-                       PRINTQ(op->lin.m[2][4]), PRINTQ(op->lin.m[3][0]),
-                       PRINTQ(op->lin.m[3][1]), PRINTQ(op->lin.m[3][2]),
-                       PRINTQ(op->lin.m[3][3]), PRINTQ(op->lin.m[3][4]));
+            av_bprintf(&bp, "%-20s: %s [", name, 
describe_lin_mask(op->lin.mask));
+            for (int i = 0; i < 4; i++) {
+                av_bprintf(&bp, "%s[", i ? " " : "");
+                for (int j = 0; j < 5; j++) {
+                    av_bprintf(&bp, j ? " " : "");
+                    print_q(&bp, op->lin.m[i][j], false);
+                }
+                av_bprintf(&bp, "]");
+            }
+            av_bprintf(&bp, "]");
             break;
         case SWS_OP_SCALE:
             av_bprintf(&bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den);
@@ -869,15 +869,13 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
             op->comps.max[0].den || op->comps.max[1].den ||
             op->comps.max[2].den || op->comps.max[3].den)
         {
-            av_log(log, lev_extra, "    min: {%s %s %s %s}, max: {%s %s %s 
%s}\n",
-                   next->comps.unused[0] ? "_" : PRINTQ(op->comps.min[0]),
-                   next->comps.unused[1] ? "_" : PRINTQ(op->comps.min[1]),
-                   next->comps.unused[2] ? "_" : PRINTQ(op->comps.min[2]),
-                   next->comps.unused[3] ? "_" : PRINTQ(op->comps.min[3]),
-                   next->comps.unused[0] ? "_" : PRINTQ(op->comps.max[0]),
-                   next->comps.unused[1] ? "_" : PRINTQ(op->comps.max[1]),
-                   next->comps.unused[2] ? "_" : PRINTQ(op->comps.max[2]),
-                   next->comps.unused[3] ? "_" : PRINTQ(op->comps.max[3]));
+            av_bprint_clear(&bp);
+            av_bprintf(&bp, "    min: ");
+            print_q4(&bp, op->comps.min, false, next->comps.unused);
+            av_bprintf(&bp, ", max: ");
+            print_q4(&bp, op->comps.max, false, next->comps.unused);
+            av_assert0(av_bprint_is_complete(&bp));
+            av_log(log, lev_extra, "%s\n", bp.str);
         }
 
     }
-- 
2.52.0


>From fa097028c937a11eddfb754d6307c75bad7ff36d Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Mar 2026 12:00:08 +0100
Subject: [PATCH 12/14] swscale/ops: avoid printing values for ignored
 components

Makes the list output a tiny bit tidier. This is cheap to support now thanks
to the print_q4() helper.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 6 +++---
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index bf9611a3ad..1d5acabffe 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -815,7 +815,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra,
             break;
         case SWS_OP_CLEAR:
             av_bprintf(&bp, "%-20s: ", name);
-            print_q4(&bp, op->c.q4, true, NULL);
+            print_q4(&bp, op->c.q4, true, next->comps.unused);
             break;
         case SWS_OP_SWIZZLE:
             av_bprintf(&bp, "%-20s: %d%d%d%d", name,
@@ -835,11 +835,11 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
             break;
         case SWS_OP_MIN:
             av_bprintf(&bp, "%-20s: x <= ", name);
-            print_q4(&bp, op->c.q4, true, NULL);
+            print_q4(&bp, op->c.q4, true, next->comps.unused);
             break;
         case SWS_OP_MAX:
             av_bprintf(&bp, "%-20s: ", name);
-            print_q4(&bp, op->c.q4, true, NULL);
+            print_q4(&bp, op->c.q4, true, next->comps.unused);
             av_bprintf(&bp, " <= x");
             break;
         case SWS_OP_LINEAR:
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index 28ca16f094..4385d809e7 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-ca5418b78a95b6b4eab07253f13ef731
+e5a6788fa43852d75544e7fb6ae7744d
-- 
2.52.0


>From a812af9acfbe9f10f48fa6925ddf1dda6d44b1f6 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Fri, 27 Mar 2026 15:22:39 +0100
Subject: [PATCH 13/14] swscale/ops: move op-formatting code to helper function

Annoyingly, access to order_src/dst requires access to the SwsOpList, so
we have to append that data after the fact.

Maybe this is another incremental tick in favor of `SwsReadWriteOp` in the
ever-present question in my head of whether the plane order should go there
or into SwsOpList.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 148 +++++++++++++++++++++++++----------------------
 libswscale/ops.h |   7 +++
 2 files changed, 85 insertions(+), 70 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 1d5acabffe..9f44fa95b5 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -755,6 +755,82 @@ static void print_q4(AVBPrint *bp, const AVRational q4[4], 
bool ignore_den0,
     av_bprintf(bp, "}");
 }
 
+void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4])
+{
+    const char *name  = ff_sws_op_type_name(op->op);
+
+    switch (op->op) {
+    case SWS_OP_INVALID:
+    case SWS_OP_SWAP_BYTES:
+        av_bprintf(bp, "%s", name);
+        break;
+    case SWS_OP_READ:
+    case SWS_OP_WRITE:
+        av_bprintf(bp, "%-20s: %d elem(s) %s >> %d", name,
+                    op->rw.elems,  op->rw.packed ? "packed" : "planar",
+                    op->rw.frac);
+        break;
+    case SWS_OP_LSHIFT:
+        av_bprintf(bp, "%-20s: << %u", name, op->c.u);
+        break;
+    case SWS_OP_RSHIFT:
+        av_bprintf(bp, "%-20s: >> %u", name, op->c.u);
+        break;
+    case SWS_OP_PACK:
+    case SWS_OP_UNPACK:
+        av_bprintf(bp, "%-20s: {%d %d %d %d}", name,
+                    op->pack.pattern[0], op->pack.pattern[1],
+                    op->pack.pattern[2], op->pack.pattern[3]);
+        break;
+    case SWS_OP_CLEAR:
+        av_bprintf(bp, "%-20s: ", name);
+        print_q4(bp, op->c.q4, true, unused);
+        break;
+    case SWS_OP_SWIZZLE:
+        av_bprintf(bp, "%-20s: %d%d%d%d", name,
+                    op->swizzle.x, op->swizzle.y, op->swizzle.z, 
op->swizzle.w);
+        break;
+    case SWS_OP_CONVERT:
+        av_bprintf(bp, "%-20s: %s -> %s%s", name,
+                    ff_sws_pixel_type_name(op->type),
+                    ff_sws_pixel_type_name(op->convert.to),
+                    op->convert.expand ? " (expand)" : "");
+        break;
+    case SWS_OP_DITHER:
+        av_bprintf(bp, "%-20s: %dx%d matrix + {%d %d %d %d}", name,
+                    1 << op->dither.size_log2, 1 << op->dither.size_log2,
+                    op->dither.y_offset[0], op->dither.y_offset[1],
+                    op->dither.y_offset[2], op->dither.y_offset[3]);
+        break;
+    case SWS_OP_MIN:
+        av_bprintf(bp, "%-20s: x <= ", name);
+        print_q4(bp, op->c.q4, true, unused);
+        break;
+    case SWS_OP_MAX:
+        av_bprintf(bp, "%-20s: ", name);
+        print_q4(bp, op->c.q4, true, unused);
+        av_bprintf(bp, " <= x");
+        break;
+    case SWS_OP_LINEAR:
+        av_bprintf(bp, "%-20s: %s [", name, describe_lin_mask(op->lin.mask));
+        for (int i = 0; i < 4; i++) {
+            av_bprintf(bp, "%s[", i ? " " : "");
+            for (int j = 0; j < 5; j++) {
+                av_bprintf(bp, j ? " " : "");
+                print_q(bp, op->lin.m[i][j], false);
+            }
+            av_bprintf(bp, "]");
+        }
+        av_bprintf(bp, "]");
+        break;
+    case SWS_OP_SCALE:
+        av_bprintf(bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den);
+        break;
+    case SWS_OP_TYPE_NB:
+        break;
+    }
+}
+
 void ff_sws_op_list_print(void *log, int lev, int lev_extra,
                           const SwsOpList *ops)
 {
@@ -769,7 +845,6 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra,
     for (int i = 0; i < ops->num_ops; i++) {
         const SwsOp *op   = &ops->ops[i];
         const SwsOp *next = i + 1 < ops->num_ops ? &ops->ops[i + 1] : op;
-        const char *name  = ff_sws_op_type_name(op->op);
         av_bprint_clear(&bp);
         av_bprintf(&bp, "  [%3s %c%c%c%c -> %c%c%c%c] ",
                    ff_sws_pixel_type_name(op->type),
@@ -782,16 +857,8 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
                    next->comps.unused[2] ? 'X' : 
describe_comp_flags(op->comps.flags[2]),
                    next->comps.unused[3] ? 'X' : 
describe_comp_flags(op->comps.flags[3]));
 
-        switch (op->op) {
-        case SWS_OP_INVALID:
-        case SWS_OP_SWAP_BYTES:
-            av_bprintf(&bp, "%s", name);
-            break;
-        case SWS_OP_READ:
-        case SWS_OP_WRITE:
-            av_bprintf(&bp, "%-20s: %d elem(s) %s >> %d", name,
-                       op->rw.elems,  op->rw.packed ? "packed" : "planar",
-                       op->rw.frac);
+        ff_sws_op_desc(&bp, op, next->comps.unused);
+        if (op->op == SWS_OP_READ || op->op == SWS_OP_WRITE) {
             SwsSwizzleOp order = op->op == SWS_OP_READ ? ops->order_src : 
ops->order_dst;
             if (order.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) {
                 const int planes = op->rw.packed ? 1 : op->rw.elems;
@@ -800,65 +867,6 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
                     av_bprintf(&bp, "%s%d", i ? ", " : "", order.in[i]);
                 av_bprintf(&bp, "}");
             }
-            break;
-        case SWS_OP_LSHIFT:
-            av_bprintf(&bp, "%-20s: << %u", name, op->c.u);
-            break;
-        case SWS_OP_RSHIFT:
-            av_bprintf(&bp, "%-20s: >> %u", name, op->c.u);
-            break;
-        case SWS_OP_PACK:
-        case SWS_OP_UNPACK:
-            av_bprintf(&bp, "%-20s: {%d %d %d %d}", name,
-                       op->pack.pattern[0], op->pack.pattern[1],
-                       op->pack.pattern[2], op->pack.pattern[3]);
-            break;
-        case SWS_OP_CLEAR:
-            av_bprintf(&bp, "%-20s: ", name);
-            print_q4(&bp, op->c.q4, true, next->comps.unused);
-            break;
-        case SWS_OP_SWIZZLE:
-            av_bprintf(&bp, "%-20s: %d%d%d%d", name,
-                       op->swizzle.x, op->swizzle.y, op->swizzle.z, 
op->swizzle.w);
-            break;
-        case SWS_OP_CONVERT:
-            av_bprintf(&bp, "%-20s: %s -> %s%s", name,
-                       ff_sws_pixel_type_name(op->type),
-                       ff_sws_pixel_type_name(op->convert.to),
-                       op->convert.expand ? " (expand)" : "");
-            break;
-        case SWS_OP_DITHER:
-            av_bprintf(&bp, "%-20s: %dx%d matrix + {%d %d %d %d}", name,
-                       1 << op->dither.size_log2, 1 << op->dither.size_log2,
-                       op->dither.y_offset[0], op->dither.y_offset[1],
-                       op->dither.y_offset[2], op->dither.y_offset[3]);
-            break;
-        case SWS_OP_MIN:
-            av_bprintf(&bp, "%-20s: x <= ", name);
-            print_q4(&bp, op->c.q4, true, next->comps.unused);
-            break;
-        case SWS_OP_MAX:
-            av_bprintf(&bp, "%-20s: ", name);
-            print_q4(&bp, op->c.q4, true, next->comps.unused);
-            av_bprintf(&bp, " <= x");
-            break;
-        case SWS_OP_LINEAR:
-            av_bprintf(&bp, "%-20s: %s [", name, 
describe_lin_mask(op->lin.mask));
-            for (int i = 0; i < 4; i++) {
-                av_bprintf(&bp, "%s[", i ? " " : "");
-                for (int j = 0; j < 5; j++) {
-                    av_bprintf(&bp, j ? " " : "");
-                    print_q(&bp, op->lin.m[i][j], false);
-                }
-                av_bprintf(&bp, "]");
-            }
-            av_bprintf(&bp, "]");
-            break;
-        case SWS_OP_SCALE:
-            av_bprintf(&bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den);
-            break;
-        case SWS_OP_TYPE_NB:
-            break;
         }
 
         av_assert0(av_bprint_is_complete(&bp));
diff --git a/libswscale/ops.h b/libswscale/ops.h
index 39d3ffe526..53e460ad51 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -25,6 +25,8 @@
 #include <stdbool.h>
 #include <stdalign.h>
 
+#include <libavutil/bprint.h>
+
 #include "graph.h"
 
 typedef enum SwsPixelType {
@@ -208,6 +210,11 @@ typedef struct SwsOp {
     SwsComps comps;
 } SwsOp;
 
+/**
+ * Describe an operation in human-readable form.
+ */
+void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4]);
+
 /**
  * Frees any allocations associated with an SwsOp and sets it to {0}.
  */
-- 
2.52.0


>From 92f36752a111578ad98170674da0cf608c79c4fa Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Mon, 16 Feb 2026 17:20:01 +0100
Subject: [PATCH 14/14] tests/sws_ops: add option for summarizing all operation
 patterns

This can be used to either manually verify, or perhaps programmatically
generate, the list of operation patterns that need to be supported by a
backend to be feature-complete.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/tests/sws_ops.c | 86 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 83 insertions(+), 3 deletions(-)

diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c
index 4e91b73b3c..f2e82c3435 100644
--- a/libswscale/tests/sws_ops.c
+++ b/libswscale/tests/sws_ops.c
@@ -18,7 +18,11 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/mem.h"
 #include "libavutil/pixdesc.h"
+#include "libavutil/tree.h"
 #include "libswscale/ops.h"
 #include "libswscale/format.h"
 
@@ -40,6 +44,67 @@ static int print_ops(SwsContext *const ctx, void *opaque, 
SwsOpList *ops)
 
     return 0;
 }
+
+static int cmp_str(const void *a, const void *b)
+{
+    return strcmp(a, b);
+}
+
+static const bool unused[4] = { false, false, false, false };
+static int register_op(SwsContext *ctx, void *opaque, SwsOp *op)
+{
+    struct AVTreeNode **root = opaque;
+    AVBPrint bp;
+    char *desc;
+
+    /* Strip irrelevant constant data from some operations */
+    switch (op->op) {
+    case SWS_OP_LINEAR:
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < 5; j++)
+                op->lin.m[i][j] = (AVRational) { 0, 1 };
+        }
+        break;
+    case SWS_OP_SCALE:
+    case SWS_OP_MIN:
+    case SWS_OP_MAX:
+    case SWS_OP_CLEAR:
+        for (int i = 0; i < 4; i++)
+            op->c.q4[i] = (AVRational) { 0, !!op->c.q4[i].den };
+        break;
+    case SWS_OP_DITHER:
+        /* Strip arbitrary offset */
+        for (int i = 0; i < 4; i++)
+            op->dither.y_offset[i] = op->dither.y_offset[i] >= 0 ? 0 : -1;
+        break;
+    }
+
+    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+    ff_sws_op_desc(&bp, op, unused);
+    int ret = av_bprint_finalize(&bp, &desc);
+    if (ret < 0)
+        return ret;
+
+    struct AVTreeNode *node = av_tree_node_alloc();
+    if (!node) {
+        av_free(desc);
+        return AVERROR(ENOMEM);
+    }
+
+    av_tree_insert(root, desc, cmp_str, &node);
+    if (node) {
+        av_free(node);
+        av_free(desc);
+    }
+    return ret;
+}
+
+static int print_summary(void *opaque, void *key)
+{
+    av_log(opaque, AV_LOG_INFO, "%s\n", (const char *) key);
+    return 0;
+}
+
 static void log_stdout(void *avcl, int level, const char *fmt, va_list vl)
 {
     if (level != AV_LOG_INFO) {
@@ -55,6 +120,7 @@ int main(int argc, char **argv)
     enum AVPixelFormat dst_fmt_min = 0;
     enum AVPixelFormat src_fmt_max = AV_PIX_FMT_NB - 1;
     enum AVPixelFormat dst_fmt_max = AV_PIX_FMT_NB - 1;
+    bool summarize = false;
     int ret = 1;
 
 #ifdef _WIN32
@@ -73,6 +139,8 @@ int main(int argc, char **argv)
                     "       Only test the specified source pixel format\n"
                     "   -v <level>\n"
                     "       Enable log verbosity at given level\n"
+                    "   -summarize <1 or 0>\n"
+                    "       Summarize operation types, instead of printing op 
lists\n"
             );
             return 0;
         }
@@ -92,6 +160,8 @@ int main(int argc, char **argv)
             }
         } else if (!strcmp(argv[i], "-v")) {
             av_log_set_level(atoi(argv[i + 1]));
+        } else if (!strcmp(argv[i], "-summarize")) {
+            summarize = atoi(argv[i + 1]);
         } else {
 bad_option:
             fprintf(stderr, "bad option or argument missing (%s) see -help\n", 
argv[i]);
@@ -104,9 +174,19 @@ bad_option:
         goto fail;
 
     av_log_set_callback(log_stdout);
-    ret = ff_sws_enum_op_lists(ctx, NULL, print_ops);
-    if (ret < 0)
-        goto fail;
+
+    if (summarize) {
+        struct AVTreeNode *root = NULL;
+        ret = ff_sws_enum_ops(ctx, &root, register_op);
+        if (ret < 0)
+            goto fail;
+        av_tree_enumerate(root, NULL, NULL, print_summary);
+        av_tree_destroy(root);
+    } else {
+        ret = ff_sws_enum_op_lists(ctx, NULL, print_ops);
+        if (ret < 0)
+            goto fail;
+    }
 
     ret = 0;
 fail:
-- 
2.52.0

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

Reply via email to