Commit a74f493bae for ffmpeg
commit a74f493bae2575f44401ec9caa55664d5913b7fb
Author: Kacper Michajłow <kasper93@gmail.com>
Date: Wed Sep 30 16:26:21 2026 +0200
swscale: infer an unspecified chroma location from the other side
A frame without a chroma location was taken as center sited, so a
conversion with the location set on one side only moved the chroma to or
from center. Take it from the other side instead, when building the graph,
where unspecified primaries and transfer characteristics are inferred too,
and in sws_is_noop(). vf_scale then forwards such a frame unconverted, so
it tags it with the output location.
Signed-off-by: Kacper Michajłow <kasper93@gmail.com>
diff --git a/libavfilter/vf_scale.c b/libavfilter/vf_scale.c
index 775b0ce63d..876fa773de 100644
--- a/libavfilter/vf_scale.c
+++ b/libavfilter/vf_scale.c
@@ -871,6 +871,7 @@ scale:
}
if (sws_is_noop(out, in)) {
+ in->chroma_location = out->chroma_location;
av_frame_free(&out);
in->flags = flags_orig;
*frame_out = in;
diff --git a/libswscale/format.c b/libswscale/format.c
index 254b9353c4..631719fa8e 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -556,6 +556,27 @@ bool ff_infer_colors(SwsColor *src, SwsColor *dst)
return incomplete;
}
+static int infer_loc_ref(SwsFormat *fmt, const SwsFormat *ref)
+{
+ if (fmt->loc != AVCHROMA_LOC_UNSPECIFIED ||
+ ref->loc == AVCHROMA_LOC_UNSPECIFIED ||
+ (!fmt->desc->log2_chroma_w && !fmt->desc->log2_chroma_h))
+ return 0;
+
+ fmt->loc = ref->loc;
+ return 1;
+}
+
+bool ff_infer_chroma_loc(SwsFormat *src, SwsFormat *dst)
+{
+ int incomplete = 0;
+
+ incomplete |= infer_loc_ref(dst, src);
+ incomplete |= infer_loc_ref(src, dst);
+
+ return incomplete;
+}
+
void ff_sws_chroma_pos(const SwsFormat *fmt, bool *incomplete,
int *out_x_pos, int *out_y_pos)
{
@@ -700,6 +721,7 @@ int sws_is_noop(const AVFrame *dst, const AVFrame *src)
for (int field = 0; field < 2; field++) {
SwsFormat dst_fmt = ff_fmt_from_frame(dst, field);
SwsFormat src_fmt = ff_fmt_from_frame(src, field);
+ ff_infer_chroma_loc(&src_fmt, &dst_fmt);
if (!ff_fmt_equal(&dst_fmt, &src_fmt))
return 0;
if (!dst_fmt.interlaced)
diff --git a/libswscale/format.h b/libswscale/format.h
index 35e93faf7b..49f4342dc3 100644
--- a/libswscale/format.h
+++ b/libswscale/format.h
@@ -161,6 +161,12 @@ int ff_test_fmt(SwsBackend backends, const SwsFormat *fmt, int output);
/* Returns true if the formats are incomplete, false otherwise */
bool ff_infer_colors(SwsColor *src, SwsColor *dst);
+/**
+ * Infers an unspecified chroma location of a subsampled format from the
+ * other format. Returns true if a location was inferred, false otherwise.
+ */
+bool ff_infer_chroma_loc(SwsFormat *src, SwsFormat *dst);
+
/**
* Wrapper around av_chroma_location_enum_to_pos() that accounts for
* the per-field offset introduced by interlacing.
diff --git a/libswscale/graph.c b/libswscale/graph.c
index 8e005f3c50..5a5d469a51 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -804,6 +804,8 @@ static int init_passes(SwsGraph *graph)
SwsPass *pass = NULL; /* read from main input image */
int ret;
+ graph->incomplete |= ff_infer_chroma_loc(&src, &dst);
+
ret = generate_3dlut(graph, &src, &dst);
if (ret < 0)
return ret;
diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak
index 2bc6d14081..1a7d9a0192 100644
--- a/tests/fate/filter-video.mak
+++ b/tests/fate/filter-video.mak
@@ -574,6 +574,12 @@ fate-filter-scalechroma-uyvy422: CMD = framecrc -flags bitexact -lavfi testsrc2=
fate-filter-scalechroma-yuyv422-topleft: CMD = framecrc -flags bitexact -lavfi testsrc2=s=64x64:r=1:d=1,scale=flags=accurate_rnd+bitexact,format=yuyv422,scale=in_chroma_loc=left:out_chroma_loc=topleft:flags=accurate_rnd+bitexact,format=yuv420p -frames:v 1
fate-filter-scalechroma-nv24: CMD = framecrc -flags bitexact -lavfi testsrc2=s=64x64:r=1:d=1,scale=flags=accurate_rnd+bitexact,format=nv24,scale=out_chroma_loc=left:flags=accurate_rnd+bitexact,format=yuv420p -frames:v 1
+FATE_FILTER-$(call FILTERFRAMECRC, TESTSRC2 FORMAT SCALE) += fate-filter-scalechroma-infer
+fate-filter-scalechroma-infer: CMD = framecrc -flags bitexact -lavfi testsrc2=s=64x64:r=1:d=1,format=yuv420p,scale=96:80:out_chroma_loc=left:flags=accurate_rnd+bitexact,format=yuv420p -frames:v 1
+
+FATE_FILTER_FFPROBE-$(call ALLYES, FFPROBE LAVFI_INDEV WRAPPED_AVFRAME_DECODER TESTSRC2_FILTER FORMAT_FILTER SCALE_FILTER) += fate-filter-scalechroma-infer-noop
+fate-filter-scalechroma-infer-noop: CMD = probe -print_format compact -show_entries frame=chroma_location -f lavfi "testsrc2=s=64x64:r=1:d=1,format=yuv420p,scale=out_chroma_loc=left"
+
FATE_FILTER_VSYNTH-$(call FILTERDEMDEC, SCALE, RAWVIDEO, RAWVIDEO) += fate-filter-scalechroma
fate-filter-scalechroma: tests/data/vsynth1.yuv
fate-filter-scalechroma: CMD = framecrc -flags bitexact -s 352x288 -pix_fmt yuv444p -i $(TARGET_PATH)/tests/data/vsynth1.yuv -pix_fmt yuv420p -sws_flags +bitexact -vf scale=out_chroma_loc=bottomleft
diff --git a/tests/ref/fate/filter-scalechroma-infer b/tests/ref/fate/filter-scalechroma-infer
new file mode 100644
index 0000000000..e5f7bb2a5a
--- /dev/null
+++ b/tests/ref/fate/filter-scalechroma-infer
@@ -0,0 +1,6 @@
+#tb 0: 1/1
+#media_type 0: video
+#codec_id 0: rawvideo
+#dimensions 0: 96x80
+#sar 0: 5/6
+0, 0, 0, 1, 11520, 0x11d57c6a
diff --git a/tests/ref/fate/filter-scalechroma-infer-noop b/tests/ref/fate/filter-scalechroma-infer-noop
new file mode 100644
index 0000000000..1aae295efe
--- /dev/null
+++ b/tests/ref/fate/filter-scalechroma-infer-noop
@@ -0,0 +1 @@
+frame|chroma_location=left