PR #25773 opened by Forgejo_Fairy URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25773 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25773.patch
Frames tagged `log100`, `log316`, or `vlog` are rejected during resize and pixel-format conversion because `sws_test_transfer()` requires a display EOTF even when no color mapping is needed. For example, `format=yuv422p10le,setparams=color_trc=log316,scale=128:72` currently fails with `Unsupported input`. Accept these three known transfers during frame validation and require an EOTF/inverse EOTF only after the graph determines that color mapping is necessary. Ordinary operations preserve the transfer tag; unsupported transfer, primaries, and luminance conversions continue to return `ENOTSUP`. Document the conditional support in the public API. Add `fate-sws-transfer` with reference output covering pixel equality and tag preservation for copy, resize, and YUV-to-RGB conversion, rejection of unsupported color mapping, and invalid transfer values. Validation on Linux/AArch64: - All six reported transfer/operation combinations fail before the fix and pass afterward. - The new FATE test fails on unpatched master and passes with the fix, including under AddressSanitizer/UndefinedBehaviorSanitizer with leak detection. - Existing swscale range-convert, unscaled-threaded, pixdesc-query, and floatimg-cmp FATE tests pass. - BT.709, PQ, HLG, and linear resize controls, plus a supported linear-to-BT.709 conversion, pass. - `git diff --check` passes. The original regression was verified by testing b9dfe8138eb2 and its parent. The later resize behavior change was also verified at 3a2c5050c6eb and its parent. Fixes #25697. >From 0c22d16b0709e5a3c0a58c414abf89c38bb1ed9b Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Thu, 5 Nov 2026 01:48:10 +0000 Subject: [PATCH] swscale: allow logarithmic transfers without color mapping Frames tagged log100, log316 or vlog are rejected by sws_test_transfer() even when resizing or converting pixel formats without changing their color encoding. These transfers have no display EOTF in libavutil. Accept the three known transfers, and check for the required EOTF and inverse only after determining that the color map is not a no-op. This also keeps unsupported primaries and luminance conversions from calling missing transfer functions when the transfer tags are equal. Document the conditional support and add FATE coverage for pixel and tag preservation, unsupported color mapping, and invalid transfer values. Fixes: https://code.ffmpeg.org/FFmpeg/FFmpeg/issues/25697 Assisted-by: Fairy --- libswscale/Makefile | 1 + libswscale/format.c | 10 ++- libswscale/graph.c | 5 ++ libswscale/swscale.h | 3 + libswscale/tests/transfer.c | 161 ++++++++++++++++++++++++++++++++++++ tests/fate/libswscale.mak | 4 + tests/ref/fate/sws-transfer | 30 +++++++ 7 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 libswscale/tests/transfer.c create mode 100644 tests/ref/fate/sws-transfer diff --git a/libswscale/Makefile b/libswscale/Makefile index 4bd5ff551be..fe804bd4e7e 100644 --- a/libswscale/Makefile +++ b/libswscale/Makefile @@ -54,6 +54,7 @@ TESTPROGS = colorspace \ swscale \ sws_ops \ sws_ops_aarch64 \ + transfer \ unscaled_threaded \ DEVPROGS = uops_macros_gen \ diff --git a/libswscale/format.c b/libswscale/format.c index fbe2d813f9c..623c7b83730 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -673,7 +673,15 @@ int sws_test_transfer(enum AVColorTransferCharacteristic trc, int output) { av_csp_eotf_function eotf = output ? av_csp_itu_eotf_inv(trc) : av_csp_itu_eotf(trc); - return trc == AVCOL_TRC_UNSPECIFIED || eotf != NULL; + switch (trc) { + case AVCOL_TRC_LOG: + case AVCOL_TRC_LOG_SQRT: + case AVCOL_TRC_V_LOG: + /* Supported without color mapping, despite lacking an EOTF. */ + return 1; + default: + return trc == AVCOL_TRC_UNSPECIFIED || eotf != NULL; + } } static int test_range(enum AVColorRange range) diff --git a/libswscale/graph.c b/libswscale/graph.c index f4ae0f5871d..b1149bf9ba2 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -19,6 +19,7 @@ */ #include "libavutil/avassert.h" +#include "libavutil/csp.h" #include "libavutil/cpu.h" #include "libavutil/error.h" #include "libavutil/hwcontext.h" @@ -783,6 +784,10 @@ static int generate_3dlut(SwsGraph *graph, SwsFormat *src, SwsFormat *dst) if (ff_sws_color_map_noop(&map)) return 0; + /* Transfer functions are only needed when actually mapping colors. */ + if (!av_csp_itu_eotf(map.src.trc) || !av_csp_itu_eotf_inv(map.dst.trc)) + return AVERROR(ENOTSUP); + if (src->hw_format != AV_PIX_FMT_NONE || dst->hw_format != AV_PIX_FMT_NONE) return AVERROR(ENOTSUP); diff --git a/libswscale/swscale.h b/libswscale/swscale.h index c913e76985b..12cf3eb75c6 100644 --- a/libswscale/swscale.h +++ b/libswscale/swscale.h @@ -376,6 +376,9 @@ int sws_test_primaries(enum AVColorPrimaries primaries, int output); /** * Test if a given color transfer function is supported. * + * Some transfer functions are only supported when no color mapping is needed. + * Use sws_frame_setup() to test a specific source/destination combination. + * * @param output If 0, test if compatible with the source/input frame; * otherwise, with the destination/output frame. * @param trc The color transfer function to check. diff --git a/libswscale/tests/transfer.c b/libswscale/tests/transfer.c new file mode 100644 index 00000000000..011398f2ca9 --- /dev/null +++ b/libswscale/tests/transfer.c @@ -0,0 +1,161 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdio.h> +#include <string.h> + +#include "libavutil/error.h" +#include "libavutil/imgutils.h" +#include "libavutil/mastering_display_metadata.h" +#include "libavutil/pixdesc.h" +#include "libswscale/swscale.h" + +static AVFrame *alloc_frame(enum AVPixelFormat format, int width, int height) +{ + AVFrame *frame = av_frame_alloc(); + + if (!frame) + return NULL; + frame->format = format; + frame->width = width; + frame->height = height; + frame->colorspace = AVCOL_SPC_BT709; + frame->color_primaries = AVCOL_PRI_BT709; + frame->color_trc = AVCOL_TRC_BT709; + frame->color_range = AVCOL_RANGE_MPEG; + if (av_frame_get_buffer(frame, 0) < 0) + av_frame_free(&frame); + return frame; +} + +static int test_transfer(enum AVColorTransferCharacteristic trc) +{ + SwsContext *sws = sws_alloc_context(); + AVFrame *src = alloc_frame(AV_PIX_FMT_YUV422P10, 64, 36); + AVFrame *dst = NULL, *ref = NULL; + AVMasteringDisplayMetadata *mdm; + int ret = AVERROR(ENOMEM); + + if (!sws || !src) + goto end; + sws->threads = 1; + sws->flags = SWS_BILINEAR | SWS_BITEXACT; + sws->dither = SWS_DITHER_NONE; + for (int p = 0; p < 3; p++) { + for (int y = 0; y < src->height; y++) { + uint16_t *row = (uint16_t *)(src->data[p] + y * src->linesize[p]); + for (int x = 0; x < (p ? src->width / 2 : src->width); x++) + row[x] = (x * 37 + y * 73 + p * 59) & 1023; + } + } + + printf("%s: input=%d output=%d\n", av_color_transfer_name(trc), + sws_test_transfer(trc, 0), sws_test_transfer(trc, 1)); + for (int op = 0; op < 3; op++) { + enum AVPixelFormat fmt = op == 2 ? AV_PIX_FMT_GBRP10 : src->format; + int width = op == 1 ? 128 : 64, height = op == 1 ? 72 : 36; + unsigned mismatches = 0; + + dst = alloc_frame(fmt, width, height); + ref = alloc_frame(fmt, width, height); + if (!dst || !ref) { + ret = AVERROR(ENOMEM); + goto end; + } + src->color_trc = AVCOL_TRC_BT709; + ret = sws_scale_frame(sws, ref, src); + if (ret < 0) + goto end; + src->color_trc = dst->color_trc = trc; + ret = sws_scale_frame(sws, dst, src); + printf(" %s: %s", op == 0 ? "copy" : op == 1 ? "resize" : "rgb", + ret < 0 ? av_err2str(ret) : "success"); + if (ret >= 0) { + for (int p = 0; p < 3; p++) { + int bytes = av_image_get_linesize(fmt, width, p); + for (int y = 0; y < height; y++) + mismatches += !!memcmp(dst->data[p] + y * dst->linesize[p], + ref->data[p] + y * ref->linesize[p], bytes); + } + printf(" mismatches=%u transfer=%s", mismatches, + av_color_transfer_name(dst->color_trc)); + } + printf("\n"); + av_frame_free(&dst); + av_frame_free(&ref); + } + + dst = alloc_frame(src->format, 128, 72); + if (!dst) { + ret = AVERROR(ENOMEM); + goto end; + } + for (int op = 0; op < 4; op++) { + src->color_trc = dst->color_trc = trc; + dst->color_primaries = src->color_primaries; + switch (op) { + case 0: dst->color_trc = AVCOL_TRC_BT709; break; + case 1: src->color_trc = AVCOL_TRC_BT709; break; + case 2: dst->color_primaries = AVCOL_PRI_BT2020; break; + case 3: + mdm = av_mastering_display_metadata_create_side_data(dst); + if (!mdm) { + ret = AVERROR(ENOMEM); + goto end; + } + mdm->has_luminance = 1; + mdm->min_luminance = (AVRational) { 1, 1 }; + mdm->max_luminance = (AVRational) { 100, 1 }; + break; + } + ret = sws_frame_setup(sws, dst, src); + printf(" %s: unsupported=%d\n", + op == 0 ? "to-bt709" : op == 1 ? "from-bt709" : + op == 2 ? "primaries" : "black-point", ret == AVERROR(ENOTSUP)); + } + ret = 0; +end: + av_frame_free(&src); + av_frame_free(&dst); + av_frame_free(&ref); + sws_free_context(&sws); + return ret; +} + +int main(void) +{ + static const enum AVColorTransferCharacteristic transfers[] = { + AVCOL_TRC_LOG, AVCOL_TRC_LOG_SQRT, AVCOL_TRC_V_LOG, + }; + static const int invalid[] = { + -1, AVCOL_TRC_RESERVED0, AVCOL_TRC_RESERVED, AVCOL_TRC_NB, + AVCOL_TRC_EXT_BASE - 1, AVCOL_TRC_EXT_NB, + }; + int ret; + + av_log_set_level(AV_LOG_QUIET); + for (int i = 0; i < FF_ARRAY_ELEMS(transfers); i++) { + ret = test_transfer(transfers[i]); + if (ret < 0) + return 1; + } + for (int i = 0; i < FF_ARRAY_ELEMS(invalid); i++) + printf("invalid %d: input=%d output=%d\n", invalid[i], + sws_test_transfer(invalid[i], 0), sws_test_transfer(invalid[i], 1)); + return 0; +} diff --git a/tests/fate/libswscale.mak b/tests/fate/libswscale.mak index 044215ed66b..b3844bcd306 100644 --- a/tests/fate/libswscale.mak +++ b/tests/fate/libswscale.mak @@ -1,3 +1,7 @@ +FATE_LIBSWSCALE += fate-sws-transfer +fate-sws-transfer: libswscale/tests/transfer$(EXESUF) +fate-sws-transfer: CMD = run libswscale/tests/transfer$(EXESUF) + FATE_LIBSWSCALE += fate-sws-range-convert fate-sws-range-convert: libswscale/tests/range_convert$(EXESUF) fate-sws-range-convert: CMD = run libswscale/tests/range_convert$(EXESUF) diff --git a/tests/ref/fate/sws-transfer b/tests/ref/fate/sws-transfer new file mode 100644 index 00000000000..b1f5c7cdf2a --- /dev/null +++ b/tests/ref/fate/sws-transfer @@ -0,0 +1,30 @@ +log100: input=1 output=1 + copy: success mismatches=0 transfer=log100 + resize: success mismatches=0 transfer=log100 + rgb: success mismatches=0 transfer=log100 + to-bt709: unsupported=1 + from-bt709: unsupported=1 + primaries: unsupported=1 + black-point: unsupported=1 +log316: input=1 output=1 + copy: success mismatches=0 transfer=log316 + resize: success mismatches=0 transfer=log316 + rgb: success mismatches=0 transfer=log316 + to-bt709: unsupported=1 + from-bt709: unsupported=1 + primaries: unsupported=1 + black-point: unsupported=1 +vlog: input=1 output=1 + copy: success mismatches=0 transfer=vlog + resize: success mismatches=0 transfer=vlog + rgb: success mismatches=0 transfer=vlog + to-bt709: unsupported=1 + from-bt709: unsupported=1 + primaries: unsupported=1 + black-point: unsupported=1 +invalid -1: input=0 output=0 +invalid 0: input=0 output=0 +invalid 3: input=0 output=0 +invalid 19: input=0 output=0 +invalid 255: input=0 output=0 +invalid 257: input=0 output=0 -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
