Commit graph

2912 commits

Author SHA1 Message Date
Andreas Rheinhardt
373c86bac8 swscale/utils: Improve type-safety
SwsContext.{src,dst}_format is int (but uses enum AVPixelFormat)
values, so the accesses need to be performed using an int
for -fshort-enums support.

Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
2026-02-10 19:44:46 +01:00
Zhao Zhili
9ebfd68096 swscale/swscale: force SwsDither and SwsAlphaBlend to 32 bits
The 'sws_dither' and 'alphablend' options access 'SwsDither' and
'SwsAlphaBlend' enum fields as integers. This is unsafe when the
code is compiled with -fshort-enums, as the enum size might be
smaller than an int.

Since the 'dither' and 'alpha_blend' struct members are part of the
public API, their types cannot be easily changed.

To ensure safe integer access and maintain ABI compatibility across
different compiler settings, a MAX_ENUM value is added to force the
enums to a 32-bit underlying type.
2026-02-04 04:00:53 +00:00
Andreas Rheinhardt
d9e8c85617 swscale/x86/ops_int: Check for cpuflag instead of avx_enabled
This would make this code compatible with forcing VEX encodings
for the SSE4 codepath and is also more correct, because avx_enabled
would be enabled for AVX, although the instructions used in these
codepaths require AVX2.

Reviewed-by: Niklas Haas <ffmpeg@haasn.dev>
Signed-off-by: Andreas Rheinhardt <andreas.rheinhardt@outlook.com>
2026-01-26 13:10:15 +01:00
Kacper Michajłow
10db62d205 swscale/utils: zero init filter memory as before
Commit 795bb37a39 removed zeroing of those
buffers, without mention, which introduces corrupted output.

Fixes: 795bb37a39
Fixes: https://github.com/mpv-player/mpv/issues/17317
2026-01-26 09:45:16 +00:00
Michael Niedermayer
404775a141 swscale/utils: Sanity check sizeFactor
Fixes: multiple integer overflows
Fixes: out of array access

The PoC modifies filter parameters generally inaccessable to an attacker

Found-by: Zhenpeng (Leo) Lin from depthfirst
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2026-01-23 17:53:33 +00:00
Michael Niedermayer
795bb37a39 swscale/utils: Avoid FF_ALLOC_TYPED_ARRAY() and use av_malloc_array() directly
Fixes: multiple integer overflows
Fixes: out of array access

Regression since: a408d03ee6

The PoC modifies filter parameters generally inaccessable to an attacker

Found-by: Zhenpeng (Leo) Lin from depthfirst
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2026-01-23 17:53:33 +00:00
AnandajithS
ed085692d9 doc: fix typos
Fix trivial grammatical errors.

Signed-off-by: AnandajithS <anandajiths2006@gmail.com>
2026-01-18 03:33:04 +00:00
Ramiro Polla
0dfaed77a6 swscale/tests/sws_ops: add optional -src and -dst parameters 2025-12-26 13:14:28 +00:00
Niklas Haas
abb1524138 Revert "swscale/ops: clarify SwsOpList.src/dst semantics"
This reverts commit c94e8afe5d.

These are now actually purely informational.
2025-12-24 16:37:22 +00:00
Niklas Haas
c0f49db53d swscale/ops: remove broken value range assumption hack
This information is now pre-filled automatically for SWS_OP_READ when
relevant.

yuv444p10msbbe -> rgb24:
   [u16 XXXX -> +++X] SWS_OP_READ         : 3 elem(s) planar >> 0
   [u16 ...X -> +++X] SWS_OP_SWAP_BYTES
   [u16 ...X -> +++X] SWS_OP_RSHIFT       : >> 6
   [u16 ...X -> +++X] SWS_OP_CONVERT      : u16 -> f32
   [f32 ...X -> ...X] SWS_OP_LINEAR       : matrix3+off3 [...]
   [f32 ...X -> ...X] SWS_OP_DITHER       : 16x16 matrix + {0 3 2 5}
   [f32 ...X -> ...X] SWS_OP_MAX          : {0 0 0 0} <= x
+  [f32 ...X -> ...X] SWS_OP_MIN          : x <= {255 255 255 _}
   [f32 ...X -> +++X] SWS_OP_CONVERT      : f32 -> u8
   [ u8 ...X -> +++X] SWS_OP_WRITE        : 3 elem(s) packed >> 0
    (X = unused, + = exact, 0 = zero)

(This clamp is needed and was incorrectly optimized away before, because the
`SWS_OP_RSHIFT` incorrectly distorted the value range assertion)
2025-12-24 16:37:22 +00:00
Niklas Haas
ede8318e9f swscale/ops: explicitly reset value range after SWS_OP_UNPACK
The current logic implicitly pulled the new value range out of SwsComps using
ff_sws_apply_op_q(), but this was quite ill-formed and not very robust. In
particular, it only worked because of the implicit assumption that the value
range was always set to 0b1111...111.

This actually poses a serious problem for 32-bit packed formats, whose
value range actually does not fit into AVRational. In the past, it only
worked because the value would implicitly overflow to -1, which SWS_OP_UNPACK
would then correctly extract the bits out from again.

In general, it's cleaner (and sufficient) to just explicitly reset the value
range on SWS_OP_UNPACK again.
2025-12-24 16:37:22 +00:00
Niklas Haas
a0032fb40f swscale/ops: use switch/case for updating SwsComps ranges
A bit more readable and makes it clear what the special cases are.
2025-12-24 16:37:22 +00:00
Niklas Haas
5f1be98f62 swscale/ops: add SWS_COMP_SWAPPED
This flag keeps track of whether a pixel is currently byte-swapped or
not. Not needed by current backends, but informative and useful for
catching potential endianness errors.

Updates a lot of FATE tests with a cosmetic diff like this:

 rgb24 -> gray16be:
   [ u8 XXXX -> +++X] SWS_OP_READ         : 3 elem(s) packed >> 0
   [ u8 ...X -> +++X] SWS_OP_CONVERT      : u8 -> f32
   [f32 ...X -> .++X] SWS_OP_LINEAR       : dot3 [...]
   [f32 .XXX -> +++X] SWS_OP_CONVERT      : f32 -> u16
-  [u16 .XXX -> +++X] SWS_OP_SWAP_BYTES
-  [u16 .XXX -> +++X] SWS_OP_WRITE        : 1 elem(s) planar >> 0
-    (X = unused, + = exact, 0 = zero)
+  [u16 .XXX -> zzzX] SWS_OP_SWAP_BYTES
+  [u16 .XXX -> zzzX] SWS_OP_WRITE        : 1 elem(s) planar >> 0
+    (X = unused, z = byteswapped, + = exact, 0 = zero)

(The choice of `z` to represent swapped integers is arbitrary, but I think
it's visually evocative and distinct from the other symbols)
2025-12-24 16:37:22 +00:00
Niklas Haas
5a6602e959 swscale/format: add pixel range metadata to SWS_OP_READ 2025-12-24 16:37:22 +00:00
Niklas Haas
9586b81373 swscale/ops: don't strip SwsComps from SWS_OP_READ
The current behavior of assuming the value range implicitly on SWS_OP_READ
has a number of serious drawbacks and shortcomings:

- It ignored the effects of SWS_OP_RSHIFT, such as for p010 and related
  MSB-aligned formats. (This is actually a bug)

- It adds a needless dependency on the "purely informative" src/dst fields
  inside SwsOpList.

- It is difficult to reason about when acted upon by SWS_OP_SWAP_BYTES, and
  the existing hack of simply ignoring SWAP_BYTES on the value range is not
  a very good solution here.

Instead, we need a more principled way for the op list generating code
to communicate extra metadata about the operations read to the optimizer.

I think the simplest way of doing this is to allow the SwsComps field attached
to SWS_OP_READ to carry additional, user-provided information about the values
read.

This requires changing ff_sws_op_list_update_comps() slightly to not completely
overwrite SwsComps on SWS_OP_READ, but instead merge the implicit information
with the explictly provided one.
2025-12-24 16:37:22 +00:00
Niklas Haas
61eca588dc swscale/ops: move ff_sws_op_list_update_comps() to ops.c
I think this is ultimately a better home, since the semantics of this are
not really tied to optimization itself; and because I want to make it an
explicitly suported part of the user-facing API (rather than just an
internal-use field).

The secondary motivating reason here is that I intend to use internal
helpers of `ops.c` inside the next commit. (Though this is a weak reason
on its own, and not sufficient to justify this move by itself.)
2025-12-24 16:37:22 +00:00
Michael Niedermayer
d41b8d95a7 swscale/output: Use 64bit in addition in yuv2gbrp16_full_X_c() for RGB + Y
Fixes: signed integer overflow: -1159988356 + -1082982400 cannot be represented in type 'int'
Fixes: 461519938/clusterfuzz-testcase-minimized-ffmpeg_SWS_fuzzer-4777382021234688

Found-by:  continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
2025-12-23 09:14:44 +00:00
Niklas Haas
fafd72ef04 swscale/ops_internal: fix ff_sws_pack_op_decode()
This function was assuming that the bits are MSB-aligned, but they are
LSB-aligned in both practice (and in the actual backend).

Also update the documentation of SwsPackOp to make this clearer.

Fixes an incorrect omission of a clamp after decoding e.g. rgb4, since
the max value range was incorrectly determined as 0 as a result of unpacking
the MSB bits instead of the LSB bits:

 bgr4 -> gray:
   [ u8 XXXX -> +XXX] SWS_OP_READ         : 1 elem(s) packed >> 1
   [ u8 .XXX -> +++X] SWS_OP_UNPACK       : {1 2 1 0}
   [ u8 ...X -> +++X] SWS_OP_SWIZZLE      : 2103
   [ u8 ...X -> +++X] SWS_OP_CONVERT      : u8 -> f32
   [f32 ...X -> .++X] SWS_OP_LINEAR       : dot3 [...]
   [f32 .XXX -> .++X] SWS_OP_DITHER       : 16x16 matrix + {0 3 2 5}
+  [f32 .XXX -> .++X] SWS_OP_MIN          : x <= {255 _ _ _}
   [f32 .XXX -> +++X] SWS_OP_CONVERT      : f32 -> u8
   [ u8 .XXX -> +++X] SWS_OP_WRITE        : 1 elem(s) planar >> 0
     (X = unused, + = exact, 0 = zero)
2025-12-22 20:14:31 +00:00
Niklas Haas
7505264b6a swscale/ops: update comment on SWS_COMP_EXACT
That the integer is "in-range" is implied by the min/max range tracking,
not the flag itself.
2025-12-20 13:52:45 +00:00
Niklas Haas
1d0fd7fabf swscale/ops: categorize ops by type compatibility
This is a more useful grouping than the previous, somewhat arbitrary one.
2025-12-20 13:52:45 +00:00
Niklas Haas
94777ed2eb swscale/ops_chain: fix comment 2025-12-20 13:52:45 +00:00
Niklas Haas
75ba2bf457 swscale/ops: correctly truncate on ff_sws_apply_op_q(SWS_OP_RSHIFT)
Instead of using a "precise" division, simulate the actual truncation.

Note that the division by `den` is unneeded in principle because the
denominator *should* always be 1 for an integer, but this way we don't
explode if the user should happen to pass `4/2` or something.

Fixes a lot of unnecessary clamps w.r.t. xv36, e.g.:

 xv36be -> yuv444p12be:
   [u16 XXXX -> ++++] SWS_OP_READ         : 4 elem(s) packed >> 0
   [u16 ...X -> ++++] SWS_OP_SWAP_BYTES
   [u16 ...X -> ++++] SWS_OP_SWIZZLE      : 1023
   [u16 ...X -> ++++] SWS_OP_RSHIFT       : >> 4
-  [u16 ...X -> ++++] SWS_OP_CONVERT      : u16 -> f32
-  [f32 ...X -> ++++] SWS_OP_MIN          : x <= {4095 4095 4095 _}
-  [f32 ...X -> ++++] SWS_OP_CONVERT      : f32 -> u16
   [u16 ...X -> ++++] SWS_OP_SWAP_BYTES
   [u16 ...X -> ++++] SWS_OP_WRITE        : 3 elem(s) planar >> 0
     (X = unused, + = exact, 0 = zero)
2025-12-20 13:52:45 +00:00
Niklas Haas
d1eaea1a03 swscale/ops: add type assertions to ff_sws_apply_op_q() 2025-12-20 13:52:45 +00:00
Niklas Haas
258dbfdbc9 swscale/format: only generate SHIFT ops when needed
Otherwise, we may spuriously generate illegal combinations like
SWS_OP_LSHIFT on SWS_PIXEL_F32.
2025-12-20 13:52:45 +00:00
Niklas Haas
c31f3926d1 swscale/ops_optimizer: simplify loop slightly (cosmetic)
We always `goto retry` whenever an optimization case is hit, so we don't
need to defer the increment of `n`.
2025-12-20 13:52:45 +00:00
Niklas Haas
900d91b541 swscale/ops_optimizer: apply optimizations in a more predictable order
Instead of blindly interleaving re-ordering and minimizing optimizations,
separate this loop into several passes - the first pass will minimize the
operation list in-place as much as possible, and the second pass will apply any
desired re-orderings. (We also want to try pushing clear back before any other
re-orderings, as this can trigger more phase 1 optimizations)

This restructuring leads to significantly more predictable and stable behavior,
especially when introducing more operation types going forwards. Does not
actually affect the current results, but matters with some upcoming changes
I have planned.
2025-12-20 13:52:45 +00:00
Niklas Haas
c51c63058c swscale/ops_optimizer: don't commute clear with itself
These would normally be merged, not swapped.
2025-12-20 13:52:45 +00:00
rcombs
d1962881ae swscale: use configured YUV matrix with paletted-RGB inputs
This replaces hardcoded BT601.
2025-12-16 01:24:40 +00:00
Niklas Haas
18edb246c8 swscale/ops_optimizer: correctly commute swizzles with dither ops
This requires updating the order of the dither matrix offsets as well. In
particular, this can only be done cleanly if the dither matrix offsets are
compatible between duplicated channels; but this should be guaranteed after
the previous commits.

As a side note, this also fixes a bug where we pushed SWS_OP_SWIZZLE past
SWS_OP_DITHER even for very low bit depth output (e.g. rgb4), which led to
a big loss in accuracy due to loss of per-channel dither noise.

Improves loss of e.g. gray -> rgb444 from 0.00358659 to 0.00261414,
now beating legacy swscale in these cases as well.
2025-12-15 14:31:58 +00:00
Niklas Haas
7bce47cc63 swscale/ops_optimizer: only commute swizzle ops if coefficients are identical
Otherwise, this is invalid; as the result of applying the next operation
after channel duplication may not commute with the result of duplicating
the result of applying the next operation on only one channel.

In practice, this was using the last seen channel's coefficients.

Note that this did not actually affect anything in practice, because the only
relevant ops (MIN/MAX) were always generated with identical coefficients for
identical channel ranges.

However, it will matter moving forwards, as e.g. dither ops may not be
commuted freely if their matrix offsets differ per channel.
2025-12-15 14:31:58 +00:00
Niklas Haas
1cc4c2b236 swscale/ops_optimizer: rework ambiguous op_type_is_independent()
The current usage is ambiguous between "affects each component equally" and
"affects each component independently" - and arguably, the current behavior
was a bug (since SWS_OP_DITHER should not commute with a SWIZZLE, at least
from a bit-exactness PoV).

However, when trying to define cleaner replacements for these concepts, I
realized there are too many special cases; and given that we only have two
use sites, I decided to just split them directly into "commute" functions
for those particular usage cases.

As an added benefit, this moves the commutation logic out of the already-long
ff_sws_ops_list_optimize().
2025-12-15 14:31:58 +00:00
Niklas Haas
6184924892 swscale/format: don't add chroma noise when dithering grayscale content
On the surface, this trades a tiny bit of PSNR for not introducing chroma
noise into grayscale images. However, the main reason for this change is
actually motivated by a desire to avoid regressing the status quo of
duplicating swizzles being able to be commuted past dither ops.
2025-12-15 14:31:58 +00:00
Niklas Haas
d5174f9e5b swscale/format: add source format info to ff_sws_encode_colors()
Specifically, I need access to this for generating a better dither matrix.
2025-12-15 14:31:58 +00:00
Niklas Haas
3f7e3cedb5 swscale/x86/ops_float: store and load per row dither offset directly
Instead of computing y + N with a hard-coded index offset, calculate the
relative offset as a 16-bit integer in C and add that to the pointer directly.
Since we no longer mask the resulting combined address, this may result in
overread, but that's fine since we over-provisioned the array in the previous
commit.
2025-12-15 14:31:58 +00:00
Niklas Haas
0744260ff0 swscale/x86/ops: over-allocate dither matrix
I want to change the way offsets are calculated inside the dither asm;
and the cleanest way to solve that problem is to just over-allocate the
entire dither matrix based on the maximum offset range we expected.
2025-12-15 14:31:58 +00:00
Niklas Haas
b1c96b99fa swscale/x86/ops_float: remove special case for 2x2 matrix
This is an exceptionally unlikely (in fact, currently impossible) case to
actually hit, and not worth micro-optimizing for. More specifically, having
this special case prevents me from easily adding per-row offsets.
2025-12-15 14:31:58 +00:00
Niklas Haas
d62102d679 swscale/ops_tmpl_float: actually skip allocation for !size_log2 case
This check was wrong; 1 << 0 = 1. The intent was to skip allocating a 1x1
matrix by assuming it is a constant 0.5. But as written, the check never
actually executed.

This did not affect the runtime performance, nor did it leak memory; but it
did mean we didn't hit the intended `assert`.
2025-12-15 14:31:58 +00:00
Niklas Haas
58f933798f swscale/ops_tmpl_float: respect specified dither matrix offsets
Since we only need 8 bytes to store the dither matrix pointer, we actually
still have 8 bytes left-over. That means we could either store the 8-bit
row offset directly, or alternatively compute a 16-bit pointer offsets.

I have chosen to do the former for the C backend, in the interest of
simplicity.

The one downside of this approach is that it would fail on hypothetical
128-bit platforms; although I seriously hope that this code does not live
long enough to see the need for 128-bit addressable memory.
2025-12-15 14:31:58 +00:00
Niklas Haas
960cf3015e swscale/ops: add explicit row offset to SwsDitherOp
To improve decorrelation between components, we offset the dither matrix
slightly for each component. This is currently done by adding a hard-coded
offset of {0, 3, 2, 5} to each of the four components, respectively.

However, this represents a serious challenge when re-ordering SwsDitherOp
past a swizzle, or when splitting an SwsOpList into multiple sub-operations
(e.g. for decoupling luma from subsampled chroma when they are independent).

To fix this on a fundamental level, we have to keep track of the offset per
channel as part of the SwsDitherOp metadata, and respect those values at
runtime.

This commit merely adds the metadata; the update to the underlying backends
will come in a follow-up commit. The FATE change is merely due to the
added offsets in the op list print-out.
2025-12-15 14:31:58 +00:00
Niklas Haas
7b773aba82 swscale/format: merge fmt_* helpers into a single fmt_analyze()
Handles the split between "regular" and "irregular" pixel formats in a single
place, and opens up the door for more complicated formats.
2025-12-09 09:47:48 +00:00
Niklas Haas
a8dc3a543c swscale/format: consolidate format information into a single struct
I use a switch/case instead of an array because this is only needed for
irregular formats, which are very sparse.
2025-12-09 09:47:48 +00:00
Niklas Haas
3160ace20a swscale/format: derive fmt_read_write() for regular formats 2025-12-09 09:47:48 +00:00
Niklas Haas
004127f00b swscale/format: explicitly test for unsupported subsampled formats
This includes semiplanar formats. Note that the first check typically
subsumes the second check, but I decided to keep both for clarity.
2025-12-09 09:47:48 +00:00
Niklas Haas
748855b227 swscale/format: derive fmt_shift() from AVPixFmtDescriptor
XV36 is the odd one out, being a byte-shifted packed format whose components
don't actually cross any byte boundaries.
2025-12-09 09:47:48 +00:00
Niklas Haas
2feb848252 swscale/format: derive fmt_swizzle() from AVPixFmtDescriptor when possible
Unfortunately, this is exceptionally difficult to handle in the general case,
when packed/bitstream formats come into play - the actual interpretation of
the offset, shift etc. are so difficult to deal with in a general case that
I think it's simpler to continue falling back to a static table of variants
for these exceptions. They are fortunately small in number.
2025-12-09 09:47:48 +00:00
Niklas Haas
83d572e6f6 swscale/format: check SwsPixelType in fmt_read_write()
This is the only function that actually has the ability to return an
error, so just move the pixel type assignment here and add a check to
ensure a valid pixel type is found.
2025-12-09 09:47:48 +00:00
Niklas Haas
ef2ce57c31 swscale/format: exclude U32 from sws_pixel_type()
This function is supposed to give us representable pixel types; but U32 is not
representable (due only to the AVRational range limit).
2025-12-09 09:47:48 +00:00
Martin Storsjö
69b4474367 swscale/tests: Fix fate-sws-ops-list on Windows
Set stdout to binary mode, to avoid platform specific differences
in the output that is hashed.
2025-12-08 23:02:30 +02:00
Niklas Haas
c94e8afe5d swscale/ops: clarify SwsOpList.src/dst semantics
Turns out these are not, in fact, purely informative - but the optimizer
can take them into account. This should be documented properly.

I tried to think of a way to avoid needing this in the optimizer, but any
way I could think of would require shoving this to SwsReadWriteOp, which I
am particularly unwilling to do.
2025-12-08 20:09:37 +00:00
Niklas Haas
f39fe6380c swscale/ops_optimizer: set correct value range for subpixel reads
e.g. rgb4 only reads values up to 15, not 255.

Setting this correctly eliminates a number of redundant clamps in cases
like e.g. rgb4 -> monow.
2025-12-08 20:09:37 +00:00