ffmpeg-cvslog
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
September 2025
- 1 participants
- 195 discussions
[ffmpeg] branch master updated. ef856ef93e avfilter/vf_libplacebo: also output a frame during input gaps
by ffmpeg-git@ffmpeg.org 02 Sep '25
by ffmpeg-git@ffmpeg.org 02 Sep '25
02 Sep '25
The branch, master has been updated
via ef856ef93ef6c449f546c10f86c820d4dafe2fdc (commit)
via 68c083c43ff7a87619e15f13205ad45767b9fb53 (commit)
via fc5a6a3b390d2b2a0dbc6d1f1efa83a1f17a5d35 (commit)
from 1069d457c657488f8be0034f5ce67e8279bbfae8 (commit)
- Log -----------------------------------------------------------------
commit ef856ef93ef6c449f546c10f86c820d4dafe2fdc
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Tue Aug 12 12:20:11 2025 +0200
Commit: Niklas Haas <ffmpeg(a)haasn.dev>
CommitDate: Tue Sep 2 09:55:31 2025 +0000
avfilter/vf_libplacebo: also output a frame during input gaps
In constant FPS mode, it's possible for there no be *no* input active at
the current timestamp, even though there would be active inputs later in
the timeline (nb_active > 0). This would previously hit the AVERROR_BUG case.
With this change, we can instead output an empty frame.
diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
index e3761bead8..2e7e83e8db 100644
--- a/libavfilter/vf_libplacebo.c
+++ b/libavfilter/vf_libplacebo.c
@@ -1243,6 +1243,10 @@ static int libplacebo_activate(AVFilterContext *ctx)
}
}
+ /* In constant FPS mode, we can also output an empty frame if there is
+ * a gap in the input timeline and we still have active streams */
+ ok |= s->fps.num && s->nb_active > 0;
+
if (retry) {
return 0;
} else if (ok) {
commit 68c083c43ff7a87619e15f13205ad45767b9fb53
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Tue Aug 12 12:17:41 2025 +0200
Commit: Niklas Haas <ffmpeg(a)haasn.dev>
CommitDate: Tue Sep 2 09:55:31 2025 +0000
avfilter/vf_libplacebo: refactor status handling
Instead of having a single s->status field to track the most recently
EOF'd stream, track the number of active streams directly and only query
the most recent status at the end.
The reason this worked before is because we implicitly relied on the
assumption that `ok` would always be true until all streams are EOF, but
because it's possible to have gaps in the timeline when mixing multiple
streams, this is not always the case in principle.
In practice, this fixes a bug where the filter would early-exit (EOF)
too soon, when any input reached EOF and there is a gap in the timeline.
diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
index 70aa746e80..e3761bead8 100644
--- a/libavfilter/vf_libplacebo.c
+++ b/libavfilter/vf_libplacebo.c
@@ -170,8 +170,7 @@ typedef struct LibplaceboContext {
/* input state */
LibplaceboInput *inputs;
int nb_inputs;
- int64_t status_pts; ///< tracks status of most recently used input
- int status;
+ int nb_active;
/* settings */
char *out_format_string;
@@ -768,6 +767,7 @@ static int init_vulkan(AVFilterContext *avctx, const AVVulkanDeviceContext *hwct
return AVERROR(ENOMEM);
for (int i = 0; i < s->nb_inputs; i++)
RET(input_init(avctx, &s->inputs[i], i));
+ s->nb_active = s->nb_inputs;
s->linear_rr = pl_renderer_create(s->log, s->gpu);
/* fall through */
@@ -1166,11 +1166,7 @@ static int handle_input(AVFilterContext *ctx, LibplaceboInput *input)
pl_queue_push(input->queue, NULL); /* Signal EOF to pl_queue */
input->status = status;
input->status_pts = pts;
- if (!s->status || pts >= s->status_pts) {
- /* Also propagate to output unless overwritten by later status change */
- s->status = status;
- s->status_pts = pts;
- }
+ s->nb_active--;
}
return 0;
@@ -1254,8 +1250,18 @@ static int libplacebo_activate(AVFilterContext *ctx)
for (int i = 0; i < s->nb_inputs; i++)
drain_input_pts(&s->inputs[i], out_pts);
return output_frame(ctx, out_pts);
- } else if (s->status) {
- ff_outlink_set_status(outlink, s->status, s->status_pts);
+ } else if (s->nb_active == 0) {
+ /* Forward most recent status */
+ int status = s->inputs[0].status;
+ int64_t status_pts = s->inputs[0].status_pts;
+ for (int i = 1; i < s->nb_inputs; i++) {
+ const LibplaceboInput *in = &s->inputs[i];
+ if (in->status_pts > status_pts) {
+ status = s->inputs[i].status;
+ status_pts = s->inputs[i].status_pts;
+ }
+ }
+ ff_outlink_set_status(outlink, status, status_pts);
return 0;
}
commit fc5a6a3b390d2b2a0dbc6d1f1efa83a1f17a5d35
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Tue Aug 12 12:15:54 2025 +0200
Commit: Niklas Haas <ffmpeg(a)haasn.dev>
CommitDate: Tue Sep 2 09:55:31 2025 +0000
avfilter/vf_libplacebo: allow rendering empty frames
When using libplacebo to composite multiple streams with complex PTS
values, it's possible for there to be a "hole" in the output stream; in
particular in constant FPS mode. This commit refactors output_frame() to
allow it to handle the case of there being no active input.
diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
index c35473a44c..70aa746e80 100644
--- a/libavfilter/vf_libplacebo.c
+++ b/libavfilter/vf_libplacebo.c
@@ -950,22 +950,18 @@ static int output_frame(AVFilterContext *ctx, int64_t pts)
nb_visible++;
}
- /* It should be impossible to call output_frame() without at least one
- * valid nonempty frame mix */
- av_assert1(nb_visible > 0);
-
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out)
return AVERROR(ENOMEM);
+ if (!ref)
+ goto props_done;
+
RET(av_frame_copy_props(out, ref));
- out->pts = pts;
out->width = outlink->w;
out->height = outlink->h;
out->colorspace = outlink->colorspace;
out->color_range = outlink->color_range;
- if (s->fps.num)
- out->duration = 1;
if (s->deinterlace)
out->flags &= ~(AV_FRAME_FLAG_INTERLACED | AV_FRAME_FLAG_TOP_FIELD_FIRST);
@@ -1001,6 +997,11 @@ static int output_frame(AVFilterContext *ctx, int64_t pts)
out->sample_aspect_ratio = av_mul_q(ref->sample_aspect_ratio, stretch);
}
+props_done:
+ out->pts = pts;
+ if (s->fps.num)
+ out->duration = 1;
+
/* Map, render and unmap output frame */
if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
ok = pl_map_avframe_ex(s->gpu, &target, pl_avframe_params(
@@ -1069,6 +1070,9 @@ static int output_frame(AVFilterContext *ctx, int64_t pts)
target.crop = orig_target.crop = (struct pl_rect2df) {0};
pl_render_image(s->linear_rr, &target, &orig_target, &opts->params);
target = orig_target;
+ } else if (!ref) {
+ /* Render an empty image to clear the frame to the desired fill color */
+ pl_render_image(s->linear_rr, NULL, &target, &opts->params);
}
if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
-----------------------------------------------------------------------
Summary of changes:
libavfilter/vf_libplacebo.c | 46 +++++++++++++++++++++++++++++----------------
1 file changed, 30 insertions(+), 16 deletions(-)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. 1069d457c6 configure: report if unstable is enabled
by ffmpeg-git@ffmpeg.org 02 Sep '25
by ffmpeg-git@ffmpeg.org 02 Sep '25
02 Sep '25
The branch, master has been updated
via 1069d457c657488f8be0034f5ce67e8279bbfae8 (commit)
from eed5843392a6318894a9d4d408b0b163ee8e697f (commit)
- Log -----------------------------------------------------------------
commit 1069d457c657488f8be0034f5ce67e8279bbfae8
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Mon Sep 1 22:37:17 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 22:37:17 2025 -0300
configure: report if unstable is enabled
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/configure b/configure
index 3a0a3c560c..7ec4c3975b 100755
--- a/configure
+++ b/configure
@@ -8175,6 +8175,7 @@ echo "perl enabled ${perl-no}"
echo "pod2man enabled ${pod2man-no}"
echo "makeinfo enabled ${makeinfo-no}"
echo "makeinfo supports HTML ${makeinfo_html-no}"
+echo "experimental features ${unstable-no}"
echo "xmllint enabled ${xmllint-no}"
test -n "$random_seed" &&
echo "random seed ${random_seed}"
-----------------------------------------------------------------------
Summary of changes:
configure | 1 +
1 file changed, 1 insertion(+)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. eed5843392 configure: fix logic for the unstable option
by ffmpeg-git@ffmpeg.org 02 Sep '25
by ffmpeg-git@ffmpeg.org 02 Sep '25
02 Sep '25
The branch, master has been updated
via eed5843392a6318894a9d4d408b0b163ee8e697f (commit)
from 4ec2bffe62b1caa6ed2104795e8a7ebc422997a4 (commit)
- Log -----------------------------------------------------------------
commit eed5843392a6318894a9d4d408b0b163ee8e697f
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Mon Sep 1 22:31:07 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 22:31:07 2025 -0300
configure: fix logic for the unstable option
A --enable-* option should not have a condition where it's enabled by some
other mean, as is the case here if swscale is enabled.
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/configure b/configure
index 521eaeded6..3a0a3c560c 100755
--- a/configure
+++ b/configure
@@ -108,7 +108,7 @@ Configuration options:
--disable-runtime-cpudetect disable detecting CPU capabilities at runtime (smaller binary)
--enable-gray enable full grayscale support (slower color)
--disable-swscale-alpha disable alpha channel support in swscale
- --enable-unstable enable building optional unstable / experimental code
+ --disable-unstable disable building optional unstable / experimental code
--disable-all disable building components, libraries and programs
--disable-autodetect disable automatically detected external libraries [no]
@@ -4137,7 +4137,7 @@ avutil_suggest="clock_gettime ffnvcodec gcrypt libm libdrm libmfx opencl openssl
swresample_deps="avutil"
swresample_suggest="libm libsoxr stdatomic"
swscale_deps="avutil"
-swscale_suggest="libm stdatomic unstable"
+swscale_suggest="libm stdatomic"
avcodec_extralibs="pthreads_extralibs iconv_extralibs dxva2_extralibs liblcevc_dec_extralibs lcms2_extralibs"
avfilter_extralibs="pthreads_extralibs"
@@ -4237,6 +4237,7 @@ enable runtime_cpudetect
enable safe_bitstream_reader
enable static
enable swscale_alpha
+enable unstable
enable valgrind_backtrace
sws_max_filter_size_default=256
-----------------------------------------------------------------------
Summary of changes:
configure | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. 4ec2bffe62 configure: allow disabling experimental swscale code
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via 4ec2bffe62b1caa6ed2104795e8a7ebc422997a4 (commit)
via cc42bc1f4bf228e19293e61d9ddf68ff42952599 (commit)
via f8f7935a972d9f1a47b2de9b74a273231cd42b3a (commit)
via 5e6ffa0376b6b9fdc0a093d7ba702a88caf23a84 (commit)
via 982d3a98d0247e933783c14df5b3dfb4441769c6 (commit)
via a151b426f9eff9ba802cd1e7f8674c6d910186e7 (commit)
via 5aef513fb4408c06d52a96ab01e8902171ce00a1 (commit)
via 99d73064f5ad08bfa25e5de531024f9890a7c9a3 (commit)
via 696f3be322ae7d758a8e52e771bade54774c9f54 (commit)
via db2bc11a978fdafc1c60e1cfe63796789b46fdc4 (commit)
via d3ca0e300d86d8c49fd4f0a093126e153ecffe0c (commit)
via ea9ca3ff353734dde0c7415259e45ca845ebd38e (commit)
via 16e191c8efbd799a392e7b04e6e3d48a2849ba12 (commit)
via ce0938da8cd113d4a7c6d1e4f49f6bbad216855a (commit)
via 8406c56b0cb36e8aed56628d03702f90019f104d (commit)
via faf62cbdf5df78884c47b57aac74df3461052974 (commit)
via 5a28f392b90397d9cc9c40646c5d9529c8851f52 (commit)
via 322f606fa81ef6fe63cc78b291d3438274561c65 (commit)
via 041a318299e072c8ca5c4406610626946ef8b400 (commit)
via c18676aae476d2e9ef1d3bf945890869a8d3490e (commit)
from eb14d4582447aa6560a47e5d8c6f142d76540766 (commit)
- Log -----------------------------------------------------------------
commit 4ec2bffe62b1caa6ed2104795e8a7ebc422997a4
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Mon Jul 14 10:39:00 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
configure: allow disabling experimental swscale code
In theory we can also expand this to disable e.g. experimental codecs.
diff --git a/configure b/configure
index 9fe28c5af4..521eaeded6 100755
--- a/configure
+++ b/configure
@@ -108,6 +108,7 @@ Configuration options:
--disable-runtime-cpudetect disable detecting CPU capabilities at runtime (smaller binary)
--enable-gray enable full grayscale support (slower color)
--disable-swscale-alpha disable alpha channel support in swscale
+ --enable-unstable enable building optional unstable / experimental code
--disable-all disable building components, libraries and programs
--disable-autodetect disable automatically detected external libraries [no]
@@ -2073,6 +2074,7 @@ FEATURE_LIST="
small
static
swscale_alpha
+ unstable
"
# this list should be kept in linking order
@@ -4135,7 +4137,7 @@ avutil_suggest="clock_gettime ffnvcodec gcrypt libm libdrm libmfx opencl openssl
swresample_deps="avutil"
swresample_suggest="libm libsoxr stdatomic"
swscale_deps="avutil"
-swscale_suggest="libm stdatomic"
+swscale_suggest="libm stdatomic unstable"
avcodec_extralibs="pthreads_extralibs iconv_extralibs dxva2_extralibs liblcevc_dec_extralibs lcms2_extralibs"
avfilter_extralibs="pthreads_extralibs"
diff --git a/libswscale/Makefile b/libswscale/Makefile
index d3009c7f21..a096ed331e 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -15,11 +15,6 @@ OBJS = alphablend.o \
graph.o \
input.o \
lut3d.o \
- ops.o \
- ops_backend.o \
- ops_chain.o \
- ops_memcpy.o \
- ops_optimizer.o \
options.o \
output.o \
rgb2rgb.o \
@@ -31,6 +26,13 @@ OBJS = alphablend.o \
yuv2rgb.o \
vscale.o \
+OBJS-$(CONFIG_UNSTABLE) += \
+ ops.o \
+ ops_backend.o \
+ ops_chain.o \
+ ops_memcpy.o \
+ ops_optimizer.o \
+
# Objects duplicated from other libraries for shared builds
SHLIBOBJS += log2_tab.o half2float.o
diff --git a/libswscale/format.c b/libswscale/format.c
index 5c5ecfb807..9741688e98 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -605,6 +605,8 @@ int sws_is_noop(const AVFrame *dst, const AVFrame *src)
return 1;
}
+#if CONFIG_UNSTABLE
+
/* Returns the type suitable for a pixel after fully decoding/unpacking it */
static SwsPixelType fmt_pixel_type(enum AVPixelFormat fmt)
{
@@ -1536,3 +1538,5 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type,
.convert.to = fmt_pixel_type(fmt.format),
});
}
+
+#endif /* CONFIG_UNSTABLE */
diff --git a/libswscale/graph.c b/libswscale/graph.c
index d56793aac9..0a79b17f89 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -463,6 +463,7 @@ static int add_legacy_sws_pass(SwsGraph *graph, SwsFormat src, SwsFormat dst,
* Format conversion *
*********************/
+#if CONFIG_UNSTABLE
static int add_convert_pass(SwsGraph *graph, SwsFormat src, SwsFormat dst,
SwsPass *input, SwsPass **output)
{
@@ -536,6 +537,9 @@ fail:
return add_legacy_sws_pass(graph, src, dst, input, output);
return ret;
}
+#else
+#define add_convert_pass add_legacy_sws_pass
+#endif
/**************************
diff --git a/libswscale/x86/Makefile b/libswscale/x86/Makefile
index f1d6255549..f82b411fb1 100644
--- a/libswscale/x86/Makefile
+++ b/libswscale/x86/Makefile
@@ -18,7 +18,7 @@ X86ASM-OBJS += x86/input.o \
x86/yuv2yuvX.o \
ifdef ARCH_X86_64
-X86ASM-OBJS += x86/ops_int.o \
+X86ASM-OBJS-$(CONFIG_UNSTABLE) += x86/ops_int.o \
x86/ops_float.o \
x86/ops.o
endif
commit cc42bc1f4bf228e19293e61d9ddf68ff42952599
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Thu Mar 6 17:45:16 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/graph: allow experimental use of new format handler
The humor originally contained in this commit message has been
redacted to comply with the strict FFmpeg code quality standards.
diff --git a/libswscale/graph.c b/libswscale/graph.c
index 62c0fe3eb7..d56793aac9 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -34,6 +34,7 @@
#include "lut3d.h"
#include "swscale_internal.h"
#include "graph.h"
+#include "ops.h"
static int pass_alloc_output(SwsPass *pass)
{
@@ -458,6 +459,85 @@ static int add_legacy_sws_pass(SwsGraph *graph, SwsFormat src, SwsFormat dst,
return init_legacy_subpass(graph, sws, input, output);
}
+/*********************
+ * Format conversion *
+ *********************/
+
+static int add_convert_pass(SwsGraph *graph, SwsFormat src, SwsFormat dst,
+ SwsPass *input, SwsPass **output)
+{
+ const SwsPixelType type = SWS_PIXEL_F32;
+
+ SwsContext *ctx = graph->ctx;
+ SwsOpList *ops = NULL;
+ int ret = AVERROR(ENOTSUP);
+
+ /* Mark the entire new ops infrastructure as experimental for now */
+ if (!(ctx->flags & SWS_UNSTABLE))
+ goto fail;
+
+ /* The new format conversion layer cannot scale for now */
+ if (src.width != dst.width || src.height != dst.height ||
+ src.desc->log2_chroma_h || src.desc->log2_chroma_w ||
+ dst.desc->log2_chroma_h || dst.desc->log2_chroma_w)
+ goto fail;
+
+ /* The new code does not yet support alpha blending */
+ if (src.desc->flags & AV_PIX_FMT_FLAG_ALPHA &&
+ ctx->alpha_blend != SWS_ALPHA_BLEND_NONE)
+ goto fail;
+
+ ops = ff_sws_op_list_alloc();
+ if (!ops)
+ return AVERROR(ENOMEM);
+ ops->src = src;
+ ops->dst = dst;
+
+ ret = ff_sws_decode_pixfmt(ops, src.format);
+ if (ret < 0)
+ goto fail;
+ ret = ff_sws_decode_colors(ctx, type, ops, src, &graph->incomplete);
+ if (ret < 0)
+ goto fail;
+ ret = ff_sws_encode_colors(ctx, type, ops, dst, &graph->incomplete);
+ if (ret < 0)
+ goto fail;
+ ret = ff_sws_encode_pixfmt(ops, dst.format);
+ if (ret < 0)
+ goto fail;
+
+ av_log(ctx, AV_LOG_VERBOSE, "Conversion pass for %s -> %s:\n",
+ av_get_pix_fmt_name(src.format), av_get_pix_fmt_name(dst.format));
+
+ av_log(ctx, AV_LOG_DEBUG, "Unoptimized operation list:\n");
+ ff_sws_op_list_print(ctx, AV_LOG_DEBUG, ops);
+ av_log(ctx, AV_LOG_DEBUG, "Optimized operation list:\n");
+
+ ff_sws_op_list_optimize(ops);
+ if (ops->num_ops == 0) {
+ av_log(ctx, AV_LOG_VERBOSE, " optimized into memcpy\n");
+ ff_sws_op_list_free(&ops);
+ *output = input;
+ return 0;
+ }
+
+ ff_sws_op_list_print(ctx, AV_LOG_VERBOSE, ops);
+
+ ret = ff_sws_compile_pass(graph, ops, 0, dst, input, output);
+ if (ret < 0)
+ goto fail;
+
+ ret = 0;
+ /* fall through */
+
+fail:
+ ff_sws_op_list_free(&ops);
+ if (ret == AVERROR(ENOTSUP))
+ return add_legacy_sws_pass(graph, src, dst, input, output);
+ return ret;
+}
+
+
/**************************
* Gamut and tone mapping *
**************************/
@@ -527,7 +607,7 @@ static int adapt_colors(SwsGraph *graph, SwsFormat src, SwsFormat dst,
if (fmt_in != src.format) {
SwsFormat tmp = src;
tmp.format = fmt_in;
- ret = add_legacy_sws_pass(graph, src, tmp, input, &input);
+ ret = add_convert_pass(graph, src, tmp, input, &input);
if (ret < 0)
return ret;
}
@@ -569,7 +649,7 @@ static int init_passes(SwsGraph *graph)
src.color = dst.color;
if (!ff_fmt_equal(&src, &dst)) {
- ret = add_legacy_sws_pass(graph, src, dst, pass, &pass);
+ ret = add_convert_pass(graph, src, dst, pass, &pass);
if (ret < 0)
return ret;
}
commit f8f7935a972d9f1a47b2de9b74a273231cd42b3a
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Thu Mar 6 17:27:10 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/format: add new format decode/encode logic
This patch adds format handling code for the new operations. This entails
fully decoding a format to standardized RGB, and the inverse.
Handling it this way means we can always guarantee that a conversion path
exists from A to B without having to explicitly cover logic for each path;
and choosing RGB instead of YUV as the intermediate (as was done in swscale
v1) is more flexible with regards to enabling further operations such as
primaries conversions, linear scaling, etc.
In the case of YUV->YUV transform, the redundant matrix multiplication will
be canceled out anyways.
diff --git a/libswscale/format.c b/libswscale/format.c
index adc06671da..5c5ecfb807 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -21,8 +21,22 @@
#include "libavutil/avassert.h"
#include "libavutil/hdr_dynamic_metadata.h"
#include "libavutil/mastering_display_metadata.h"
+#include "libavutil/refstruct.h"
#include "format.h"
+#include "csputils.h"
+#include "ops_internal.h"
+
+#define Q(N) ((AVRational) { N, 1 })
+#define Q0 Q(0)
+#define Q1 Q(1)
+
+#define RET(x) \
+ do { \
+ int _ret = (x); \
+ if (_ret < 0) \
+ return _ret; \
+ } while (0)
typedef struct LegacyFormatEntry {
uint8_t is_supported_in :1;
@@ -590,3 +604,935 @@ int sws_is_noop(const AVFrame *dst, const AVFrame *src)
return 1;
}
+
+/* Returns the type suitable for a pixel after fully decoding/unpacking it */
+static SwsPixelType fmt_pixel_type(enum AVPixelFormat fmt)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+ const int bits = FFALIGN(desc->comp[0].depth, 8);
+ if (desc->flags & AV_PIX_FMT_FLAG_FLOAT) {
+ switch (bits) {
+ case 32: return SWS_PIXEL_F32;
+ }
+ } else {
+ switch (bits) {
+ case 8: return SWS_PIXEL_U8;
+ case 16: return SWS_PIXEL_U16;
+ case 32: return SWS_PIXEL_U32;
+ }
+ }
+
+ return SWS_PIXEL_NONE;
+}
+
+static SwsSwizzleOp fmt_swizzle(enum AVPixelFormat fmt)
+{
+ switch (fmt) {
+ case AV_PIX_FMT_ARGB:
+ case AV_PIX_FMT_0RGB:
+ case AV_PIX_FMT_AYUV64LE:
+ case AV_PIX_FMT_AYUV64BE:
+ case AV_PIX_FMT_AYUV:
+ case AV_PIX_FMT_X2RGB10LE:
+ case AV_PIX_FMT_X2RGB10BE:
+ return (SwsSwizzleOp) {{ .x = 3, 0, 1, 2 }};
+ case AV_PIX_FMT_BGR24:
+ case AV_PIX_FMT_BGR8:
+ case AV_PIX_FMT_BGR4:
+ case AV_PIX_FMT_BGR4_BYTE:
+ case AV_PIX_FMT_BGRA:
+ case AV_PIX_FMT_BGR565BE:
+ case AV_PIX_FMT_BGR565LE:
+ case AV_PIX_FMT_BGR555BE:
+ case AV_PIX_FMT_BGR555LE:
+ case AV_PIX_FMT_BGR444BE:
+ case AV_PIX_FMT_BGR444LE:
+ case AV_PIX_FMT_BGR48BE:
+ case AV_PIX_FMT_BGR48LE:
+ case AV_PIX_FMT_BGRA64BE:
+ case AV_PIX_FMT_BGRA64LE:
+ case AV_PIX_FMT_BGR0:
+ case AV_PIX_FMT_VUYA:
+ case AV_PIX_FMT_VUYX:
+ return (SwsSwizzleOp) {{ .x = 2, 1, 0, 3 }};
+ case AV_PIX_FMT_ABGR:
+ case AV_PIX_FMT_0BGR:
+ case AV_PIX_FMT_X2BGR10LE:
+ case AV_PIX_FMT_X2BGR10BE:
+ return (SwsSwizzleOp) {{ .x = 3, 2, 1, 0 }};
+ case AV_PIX_FMT_YA8:
+ case AV_PIX_FMT_YA16BE:
+ case AV_PIX_FMT_YA16LE:
+ return (SwsSwizzleOp) {{ .x = 0, 3, 1, 2 }};
+ case AV_PIX_FMT_XV30BE:
+ case AV_PIX_FMT_XV30LE:
+ return (SwsSwizzleOp) {{ .x = 3, 2, 0, 1 }};
+ case AV_PIX_FMT_VYU444:
+ case AV_PIX_FMT_V30XBE:
+ case AV_PIX_FMT_V30XLE:
+ return (SwsSwizzleOp) {{ .x = 2, 0, 1, 3 }};
+ case AV_PIX_FMT_XV36BE:
+ case AV_PIX_FMT_XV36LE:
+ case AV_PIX_FMT_XV48BE:
+ case AV_PIX_FMT_XV48LE:
+ case AV_PIX_FMT_UYVA:
+ return (SwsSwizzleOp) {{ .x = 1, 0, 2, 3 }};
+ case AV_PIX_FMT_GBRP:
+ case AV_PIX_FMT_GBRP9BE:
+ case AV_PIX_FMT_GBRP9LE:
+ case AV_PIX_FMT_GBRP10BE:
+ case AV_PIX_FMT_GBRP10LE:
+ case AV_PIX_FMT_GBRP12BE:
+ case AV_PIX_FMT_GBRP12LE:
+ case AV_PIX_FMT_GBRP14BE:
+ case AV_PIX_FMT_GBRP14LE:
+ case AV_PIX_FMT_GBRP16BE:
+ case AV_PIX_FMT_GBRP16LE:
+ case AV_PIX_FMT_GBRPF16BE:
+ case AV_PIX_FMT_GBRPF16LE:
+ case AV_PIX_FMT_GBRAP:
+ case AV_PIX_FMT_GBRAP10LE:
+ case AV_PIX_FMT_GBRAP10BE:
+ case AV_PIX_FMT_GBRAP12LE:
+ case AV_PIX_FMT_GBRAP12BE:
+ case AV_PIX_FMT_GBRAP14LE:
+ case AV_PIX_FMT_GBRAP14BE:
+ case AV_PIX_FMT_GBRAP16LE:
+ case AV_PIX_FMT_GBRAP16BE:
+ case AV_PIX_FMT_GBRPF32BE:
+ case AV_PIX_FMT_GBRPF32LE:
+ case AV_PIX_FMT_GBRAPF16BE:
+ case AV_PIX_FMT_GBRAPF16LE:
+ case AV_PIX_FMT_GBRAPF32BE:
+ case AV_PIX_FMT_GBRAPF32LE:
+ case AV_PIX_FMT_GBRP10MSBBE:
+ case AV_PIX_FMT_GBRP10MSBLE:
+ case AV_PIX_FMT_GBRP12MSBBE:
+ case AV_PIX_FMT_GBRP12MSBLE:
+ return (SwsSwizzleOp) {{ .x = 1, 2, 0, 3 }};
+ default:
+ return (SwsSwizzleOp) {{ .x = 0, 1, 2, 3 }};
+ }
+}
+
+static SwsSwizzleOp swizzle_inv(SwsSwizzleOp swiz) {
+ /* Input[x] =: Output[swizzle.x] */
+ unsigned out[4];
+ out[swiz.x] = 0;
+ out[swiz.y] = 1;
+ out[swiz.z] = 2;
+ out[swiz.w] = 3;
+ return (SwsSwizzleOp) {{ .x = out[0], out[1], out[2], out[3] }};
+}
+
+/* Shift factor for MSB aligned formats */
+static int fmt_shift(enum AVPixelFormat fmt)
+{
+ switch (fmt) {
+ case AV_PIX_FMT_P010BE:
+ case AV_PIX_FMT_P010LE:
+ case AV_PIX_FMT_P210BE:
+ case AV_PIX_FMT_P210LE:
+ case AV_PIX_FMT_Y210BE:
+ case AV_PIX_FMT_Y210LE:
+ case AV_PIX_FMT_YUV444P10MSBBE:
+ case AV_PIX_FMT_YUV444P10MSBLE:
+ case AV_PIX_FMT_GBRP10MSBBE:
+ case AV_PIX_FMT_GBRP10MSBLE:
+ return 6;
+ case AV_PIX_FMT_P012BE:
+ case AV_PIX_FMT_P012LE:
+ case AV_PIX_FMT_P212BE:
+ case AV_PIX_FMT_P212LE:
+ case AV_PIX_FMT_P412BE:
+ case AV_PIX_FMT_P412LE:
+ case AV_PIX_FMT_XV36BE:
+ case AV_PIX_FMT_XV36LE:
+ case AV_PIX_FMT_XYZ12BE:
+ case AV_PIX_FMT_XYZ12LE:
+ case AV_PIX_FMT_YUV444P12MSBBE:
+ case AV_PIX_FMT_YUV444P12MSBLE:
+ case AV_PIX_FMT_GBRP12MSBBE:
+ case AV_PIX_FMT_GBRP12MSBLE:
+ return 4;
+ }
+
+ return 0;
+}
+
+/**
+ * This initializes all absent components explicitly to zero. There is no
+ * need to worry about the correct neutral value as fmt_decode() will
+ * implicitly ignore and overwrite absent components in any case. This function
+ * is just to ensure that we don't operate on undefined memory. In most cases,
+ * it will end up getting pushed towards the output or optimized away entirely
+ * by the optimization pass.
+ */
+static SwsConst fmt_clear(enum AVPixelFormat fmt)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+ const bool has_chroma = desc->nb_components >= 3;
+ const bool has_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA;
+
+ SwsConst c = {0};
+ if (!has_chroma)
+ c.q4[1] = c.q4[2] = Q0;
+ if (!has_alpha)
+ c.q4[3] = Q0;
+
+ return c;
+}
+
+static int fmt_read_write(enum AVPixelFormat fmt, SwsReadWriteOp *rw_op,
+ SwsPackOp *pack_op)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+ if (!desc)
+ return AVERROR(EINVAL);
+
+ switch (fmt) {
+ case AV_PIX_FMT_NONE:
+ case AV_PIX_FMT_NB:
+ break;
+
+ /* Packed bitstream formats */
+ case AV_PIX_FMT_MONOWHITE:
+ case AV_PIX_FMT_MONOBLACK:
+ *pack_op = (SwsPackOp) {0};
+ *rw_op = (SwsReadWriteOp) {
+ .elems = 1,
+ .frac = 3,
+ };
+ return 0;
+ case AV_PIX_FMT_RGB4:
+ case AV_PIX_FMT_BGR4:
+ *pack_op = (SwsPackOp) {{ 1, 2, 1 }};
+ *rw_op = (SwsReadWriteOp) {
+ .elems = 1,
+ .packed = true,
+ .frac = 1,
+ };
+ return 0;
+ /* Packed 8-bit aligned formats */
+ case AV_PIX_FMT_RGB4_BYTE:
+ case AV_PIX_FMT_BGR4_BYTE:
+ *pack_op = (SwsPackOp) {{ 1, 2, 1 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ case AV_PIX_FMT_BGR8:
+ *pack_op = (SwsPackOp) {{ 2, 3, 3 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ case AV_PIX_FMT_RGB8:
+ *pack_op = (SwsPackOp) {{ 3, 3, 2 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+
+ /* Packed 16-bit aligned formats */
+ case AV_PIX_FMT_RGB565BE:
+ case AV_PIX_FMT_RGB565LE:
+ case AV_PIX_FMT_BGR565BE:
+ case AV_PIX_FMT_BGR565LE:
+ *pack_op = (SwsPackOp) {{ 5, 6, 5 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ case AV_PIX_FMT_RGB555BE:
+ case AV_PIX_FMT_RGB555LE:
+ case AV_PIX_FMT_BGR555BE:
+ case AV_PIX_FMT_BGR555LE:
+ *pack_op = (SwsPackOp) {{ 5, 5, 5 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ case AV_PIX_FMT_RGB444BE:
+ case AV_PIX_FMT_RGB444LE:
+ case AV_PIX_FMT_BGR444BE:
+ case AV_PIX_FMT_BGR444LE:
+ *pack_op = (SwsPackOp) {{ 4, 4, 4 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ /* Packed 32-bit aligned 4:4:4 formats */
+ case AV_PIX_FMT_X2RGB10BE:
+ case AV_PIX_FMT_X2RGB10LE:
+ case AV_PIX_FMT_X2BGR10BE:
+ case AV_PIX_FMT_X2BGR10LE:
+ case AV_PIX_FMT_XV30BE:
+ case AV_PIX_FMT_XV30LE:
+ *pack_op = (SwsPackOp) {{ 2, 10, 10, 10 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ case AV_PIX_FMT_V30XBE:
+ case AV_PIX_FMT_V30XLE:
+ *pack_op = (SwsPackOp) {{ 10, 10, 10, 2 }};
+ *rw_op = (SwsReadWriteOp) { .elems = 1, .packed = true };
+ return 0;
+ /* 3 component formats with one channel ignored */
+ case AV_PIX_FMT_RGB0:
+ case AV_PIX_FMT_BGR0:
+ case AV_PIX_FMT_0RGB:
+ case AV_PIX_FMT_0BGR:
+ case AV_PIX_FMT_XV36BE:
+ case AV_PIX_FMT_XV36LE:
+ case AV_PIX_FMT_XV48BE:
+ case AV_PIX_FMT_XV48LE:
+ case AV_PIX_FMT_VUYX:
+ *pack_op = (SwsPackOp) {0};
+ *rw_op = (SwsReadWriteOp) { .elems = 4, .packed = true };
+ return 0;
+ /* Unpacked byte-aligned 4:4:4 formats */
+ case AV_PIX_FMT_YUV444P:
+ case AV_PIX_FMT_YUVJ444P:
+ case AV_PIX_FMT_YUV444P9BE:
+ case AV_PIX_FMT_YUV444P9LE:
+ case AV_PIX_FMT_YUV444P10BE:
+ case AV_PIX_FMT_YUV444P10LE:
+ case AV_PIX_FMT_YUV444P12BE:
+ case AV_PIX_FMT_YUV444P12LE:
+ case AV_PIX_FMT_YUV444P14BE:
+ case AV_PIX_FMT_YUV444P14LE:
+ case AV_PIX_FMT_YUV444P16BE:
+ case AV_PIX_FMT_YUV444P16LE:
+ case AV_PIX_FMT_YUV444P10MSBBE:
+ case AV_PIX_FMT_YUV444P10MSBLE:
+ case AV_PIX_FMT_YUV444P12MSBBE:
+ case AV_PIX_FMT_YUV444P12MSBLE:
+ case AV_PIX_FMT_YUVA444P:
+ case AV_PIX_FMT_YUVA444P9BE:
+ case AV_PIX_FMT_YUVA444P9LE:
+ case AV_PIX_FMT_YUVA444P10BE:
+ case AV_PIX_FMT_YUVA444P10LE:
+ case AV_PIX_FMT_YUVA444P12BE:
+ case AV_PIX_FMT_YUVA444P12LE:
+ case AV_PIX_FMT_YUVA444P16BE:
+ case AV_PIX_FMT_YUVA444P16LE:
+ case AV_PIX_FMT_AYUV:
+ case AV_PIX_FMT_UYVA:
+ case AV_PIX_FMT_VYU444:
+ case AV_PIX_FMT_AYUV64BE:
+ case AV_PIX_FMT_AYUV64LE:
+ case AV_PIX_FMT_VUYA:
+ case AV_PIX_FMT_RGB24:
+ case AV_PIX_FMT_BGR24:
+ case AV_PIX_FMT_RGB48BE:
+ case AV_PIX_FMT_RGB48LE:
+ case AV_PIX_FMT_BGR48BE:
+ case AV_PIX_FMT_BGR48LE:
+ //case AV_PIX_FMT_RGB96BE: TODO: AVRational can't fit 2^32-1
+ //case AV_PIX_FMT_RGB96LE:
+ //case AV_PIX_FMT_RGBF16BE: TODO: no support for float16 currently
+ //case AV_PIX_FMT_RGBF16LE:
+ case AV_PIX_FMT_RGBF32BE:
+ case AV_PIX_FMT_RGBF32LE:
+ case AV_PIX_FMT_ARGB:
+ case AV_PIX_FMT_RGBA:
+ case AV_PIX_FMT_ABGR:
+ case AV_PIX_FMT_BGRA:
+ case AV_PIX_FMT_RGBA64BE:
+ case AV_PIX_FMT_RGBA64LE:
+ case AV_PIX_FMT_BGRA64BE:
+ case AV_PIX_FMT_BGRA64LE:
+ //case AV_PIX_FMT_RGBA128BE: TODO: AVRational can't fit 2^32-1
+ //case AV_PIX_FMT_RGBA128LE:
+ case AV_PIX_FMT_RGBAF32BE:
+ case AV_PIX_FMT_RGBAF32LE:
+ case AV_PIX_FMT_GBRP:
+ case AV_PIX_FMT_GBRP9BE:
+ case AV_PIX_FMT_GBRP9LE:
+ case AV_PIX_FMT_GBRP10BE:
+ case AV_PIX_FMT_GBRP10LE:
+ case AV_PIX_FMT_GBRP12BE:
+ case AV_PIX_FMT_GBRP12LE:
+ case AV_PIX_FMT_GBRP14BE:
+ case AV_PIX_FMT_GBRP14LE:
+ case AV_PIX_FMT_GBRP16BE:
+ case AV_PIX_FMT_GBRP16LE:
+ //case AV_PIX_FMT_GBRPF16BE: TODO
+ //case AV_PIX_FMT_GBRPF16LE:
+ case AV_PIX_FMT_GBRP10MSBBE:
+ case AV_PIX_FMT_GBRP10MSBLE:
+ case AV_PIX_FMT_GBRP12MSBBE:
+ case AV_PIX_FMT_GBRP12MSBLE:
+ case AV_PIX_FMT_GBRPF32BE:
+ case AV_PIX_FMT_GBRPF32LE:
+ case AV_PIX_FMT_GBRAP:
+ case AV_PIX_FMT_GBRAP10BE:
+ case AV_PIX_FMT_GBRAP10LE:
+ case AV_PIX_FMT_GBRAP12BE:
+ case AV_PIX_FMT_GBRAP12LE:
+ case AV_PIX_FMT_GBRAP14BE:
+ case AV_PIX_FMT_GBRAP14LE:
+ case AV_PIX_FMT_GBRAP16BE:
+ case AV_PIX_FMT_GBRAP16LE:
+ //case AV_PIX_FMT_GBRAPF16BE: TODO
+ //case AV_PIX_FMT_GBRAPF16LE:
+ case AV_PIX_FMT_GBRAPF32BE:
+ case AV_PIX_FMT_GBRAPF32LE:
+ case AV_PIX_FMT_GRAY8:
+ case AV_PIX_FMT_GRAY9BE:
+ case AV_PIX_FMT_GRAY9LE:
+ case AV_PIX_FMT_GRAY10BE:
+ case AV_PIX_FMT_GRAY10LE:
+ case AV_PIX_FMT_GRAY12BE:
+ case AV_PIX_FMT_GRAY12LE:
+ case AV_PIX_FMT_GRAY14BE:
+ case AV_PIX_FMT_GRAY14LE:
+ case AV_PIX_FMT_GRAY16BE:
+ case AV_PIX_FMT_GRAY16LE:
+ //case AV_PIX_FMT_GRAYF16BE: TODO
+ //case AV_PIX_FMT_GRAYF16LE:
+ //case AV_PIX_FMT_YAF16BE:
+ //case AV_PIX_FMT_YAF16LE:
+ case AV_PIX_FMT_GRAYF32BE:
+ case AV_PIX_FMT_GRAYF32LE:
+ case AV_PIX_FMT_YAF32BE:
+ case AV_PIX_FMT_YAF32LE:
+ case AV_PIX_FMT_YA8:
+ case AV_PIX_FMT_YA16LE:
+ case AV_PIX_FMT_YA16BE:
+ *pack_op = (SwsPackOp) {0};
+ *rw_op = (SwsReadWriteOp) {
+ .elems = desc->nb_components,
+ .packed = desc->nb_components > 1 && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR),
+ };
+ return 0;
+ }
+
+ return AVERROR(ENOTSUP);
+}
+
+static SwsPixelType get_packed_type(SwsPackOp pack)
+{
+ const int sum = pack.pattern[0] + pack.pattern[1] +
+ pack.pattern[2] + pack.pattern[3];
+ if (sum > 16)
+ return SWS_PIXEL_U32;
+ else if (sum > 8)
+ return SWS_PIXEL_U16;
+ else
+ return SWS_PIXEL_U8;
+}
+
+#if HAVE_BIGENDIAN
+# define NATIVE_ENDIAN_FLAG AV_PIX_FMT_FLAG_BE
+#else
+# define NATIVE_ENDIAN_FLAG 0
+#endif
+
+int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+ SwsPixelType pixel_type = fmt_pixel_type(fmt);
+ SwsPixelType raw_type = pixel_type;
+ SwsReadWriteOp rw_op;
+ SwsPackOp unpack;
+
+ RET(fmt_read_write(fmt, &rw_op, &unpack));
+ if (unpack.pattern[0])
+ raw_type = get_packed_type(unpack);
+
+ /* TODO: handle subsampled or semipacked input formats */
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_READ,
+ .type = raw_type,
+ .rw = rw_op,
+ }));
+
+ if ((desc->flags & AV_PIX_FMT_FLAG_BE) != NATIVE_ENDIAN_FLAG) {
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_SWAP_BYTES,
+ .type = raw_type,
+ }));
+ }
+
+ if (unpack.pattern[0]) {
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_UNPACK,
+ .type = raw_type,
+ .pack = unpack,
+ }));
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_CONVERT,
+ .type = raw_type,
+ .convert.to = pixel_type,
+ }));
+ }
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_SWIZZLE,
+ .type = pixel_type,
+ .swizzle = swizzle_inv(fmt_swizzle(fmt)),
+ }));
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_RSHIFT,
+ .type = pixel_type,
+ .c.u = fmt_shift(fmt),
+ }));
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_CLEAR,
+ .type = pixel_type,
+ .c = fmt_clear(fmt),
+ }));
+
+ return 0;
+}
+
+int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt)
+{
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
+ SwsPixelType pixel_type = fmt_pixel_type(fmt);
+ SwsPixelType raw_type = pixel_type;
+ SwsReadWriteOp rw_op;
+ SwsPackOp pack;
+
+ RET(fmt_read_write(fmt, &rw_op, &pack));
+ if (pack.pattern[0])
+ raw_type = get_packed_type(pack);
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_LSHIFT,
+ .type = pixel_type,
+ .c.u = fmt_shift(fmt),
+ }));
+
+ if (rw_op.elems > desc->nb_components) {
+ /* Format writes unused alpha channel, clear it explicitly for sanity */
+ av_assert1(!(desc->flags & AV_PIX_FMT_FLAG_ALPHA));
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_CLEAR,
+ .type = pixel_type,
+ .c.q4[3] = Q0,
+ }));
+ }
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_SWIZZLE,
+ .type = pixel_type,
+ .swizzle = fmt_swizzle(fmt),
+ }));
+
+ if (pack.pattern[0]) {
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_CONVERT,
+ .type = pixel_type,
+ .convert.to = raw_type,
+ }));
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_PACK,
+ .type = raw_type,
+ .pack = pack,
+ }));
+ }
+
+ if ((desc->flags & AV_PIX_FMT_FLAG_BE) != NATIVE_ENDIAN_FLAG) {
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_SWAP_BYTES,
+ .type = raw_type,
+ }));
+ }
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_WRITE,
+ .type = raw_type,
+ .rw = rw_op,
+ }));
+ return 0;
+}
+
+static inline AVRational av_neg_q(AVRational x)
+{
+ return (AVRational) { -x.num, x.den };
+}
+
+static SwsLinearOp fmt_encode_range(const SwsFormat fmt, bool *incomplete)
+{
+ SwsLinearOp c = { .m = {
+ { Q1, Q0, Q0, Q0, Q0 },
+ { Q0, Q1, Q0, Q0, Q0 },
+ { Q0, Q0, Q1, Q0, Q0 },
+ { Q0, Q0, Q0, Q1, Q0 },
+ }};
+
+ const int depth0 = fmt.desc->comp[0].depth;
+ const int depth1 = fmt.desc->comp[1].depth;
+ const int depth2 = fmt.desc->comp[2].depth;
+ const int depth3 = fmt.desc->comp[3].depth;
+
+ if (fmt.desc->flags & AV_PIX_FMT_FLAG_FLOAT)
+ return c; /* floats are directly output as-is */
+
+ if (fmt.csp == AVCOL_SPC_RGB || (fmt.desc->flags & AV_PIX_FMT_FLAG_XYZ)) {
+ c.m[0][0] = Q((1 << depth0) - 1);
+ c.m[1][1] = Q((1 << depth1) - 1);
+ c.m[2][2] = Q((1 << depth2) - 1);
+ } else if (fmt.range == AVCOL_RANGE_JPEG) {
+ /* Full range YUV */
+ c.m[0][0] = Q((1 << depth0) - 1);
+ if (fmt.desc->nb_components >= 3) {
+ /* This follows the ITU-R convention, which is slightly different
+ * from the JFIF convention. */
+ c.m[1][1] = Q((1 << depth1) - 1);
+ c.m[2][2] = Q((1 << depth2) - 1);
+ c.m[1][4] = Q(1 << (depth1 - 1));
+ c.m[2][4] = Q(1 << (depth2 - 1));
+ }
+ } else {
+ /* Limited range YUV */
+ if (fmt.range == AVCOL_RANGE_UNSPECIFIED)
+ *incomplete = true;
+ c.m[0][0] = Q(219 << (depth0 - 8));
+ c.m[0][4] = Q( 16 << (depth0 - 8));
+ if (fmt.desc->nb_components >= 3) {
+ c.m[1][1] = Q(224 << (depth1 - 8));
+ c.m[2][2] = Q(224 << (depth2 - 8));
+ c.m[1][4] = Q(128 << (depth1 - 8));
+ c.m[2][4] = Q(128 << (depth2 - 8));
+ }
+ }
+
+ if (fmt.desc->flags & AV_PIX_FMT_FLAG_ALPHA) {
+ const bool is_ya = fmt.desc->nb_components == 2;
+ c.m[3][3] = Q((1 << (is_ya ? depth1 : depth3)) - 1);
+ }
+
+ if (fmt.format == AV_PIX_FMT_MONOWHITE) {
+ /* This format is inverted, 0 = white, 1 = black */
+ c.m[0][4] = av_add_q(c.m[0][4], c.m[0][0]);
+ c.m[0][0] = av_neg_q(c.m[0][0]);
+ }
+
+ c.mask = ff_sws_linear_mask(c);
+ return c;
+}
+
+static SwsLinearOp fmt_decode_range(const SwsFormat fmt, bool *incomplete)
+{
+ SwsLinearOp c = fmt_encode_range(fmt, incomplete);
+
+ /* Invert main diagonal + offset: x = s * y + k ==> y = (x - k) / s */
+ for (int i = 0; i < 4; i++) {
+ c.m[i][i] = av_inv_q(c.m[i][i]);
+ c.m[i][4] = av_mul_q(c.m[i][4], av_neg_q(c.m[i][i]));
+ }
+
+ /* Explicitly initialize alpha for sanity */
+ if (!(fmt.desc->flags & AV_PIX_FMT_FLAG_ALPHA))
+ c.m[3][4] = Q1;
+
+ c.mask = ff_sws_linear_mask(c);
+ return c;
+}
+
+static AVRational *generate_bayer_matrix(const int size_log2)
+{
+ const int size = 1 << size_log2;
+ const int num_entries = size * size;
+ AVRational *m = av_refstruct_allocz(sizeof(*m) * num_entries);
+ av_assert1(size_log2 < 16);
+ if (!m)
+ return NULL;
+
+ /* Start with a 1x1 matrix */
+ m[0] = Q0;
+
+ /* Generate three copies of the current, appropriately scaled and offset */
+ for (int sz = 1; sz < size; sz <<= 1) {
+ const int den = 4 * sz * sz;
+ for (int y = 0; y < sz; y++) {
+ for (int x = 0; x < sz; x++) {
+ const AVRational cur = m[y * size + x];
+ m[(y + sz) * size + x + sz] = av_add_q(cur, av_make_q(1, den));
+ m[(y ) * size + x + sz] = av_add_q(cur, av_make_q(2, den));
+ m[(y + sz) * size + x ] = av_add_q(cur, av_make_q(3, den));
+ }
+ }
+ }
+
+ /**
+ * To correctly round, we need to evenly distribute the result on [0, 1),
+ * giving an average value of 1/2.
+ *
+ * After the above construction, we have a matrix with average value:
+ * [ 0/N + 1/N + 2/N + ... (N-1)/N ] / N = (N-1)/(2N)
+ * where N = size * size is the total number of entries.
+ *
+ * To make the average value equal to 1/2 = N/(2N), add a bias of 1/(2N).
+ */
+ for (int i = 0; i < num_entries; i++)
+ m[i] = av_add_q(m[i], av_make_q(1, 2 * num_entries));
+
+ return m;
+}
+
+static bool trc_is_hdr(enum AVColorTransferCharacteristic trc)
+{
+ static_assert(AVCOL_TRC_NB == 19, "Update this list when adding TRCs");
+ switch (trc) {
+ case AVCOL_TRC_LOG:
+ case AVCOL_TRC_LOG_SQRT:
+ case AVCOL_TRC_SMPTEST2084:
+ case AVCOL_TRC_ARIB_STD_B67:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int fmt_dither(SwsContext *ctx, SwsOpList *ops,
+ const SwsPixelType type, const SwsFormat fmt)
+{
+ SwsDither mode = ctx->dither;
+ SwsDitherOp dither;
+
+ if (mode == SWS_DITHER_AUTO) {
+ /* Visual threshold of perception: 12 bits for SDR, 14 bits for HDR */
+ const int jnd_bits = trc_is_hdr(fmt.color.trc) ? 14 : 12;
+ const int bpc = fmt.desc->comp[0].depth;
+ mode = bpc >= jnd_bits ? SWS_DITHER_NONE : SWS_DITHER_BAYER;
+ }
+
+ switch (mode) {
+ case SWS_DITHER_NONE:
+ if (ctx->flags & SWS_ACCURATE_RND) {
+ /* Add constant 0.5 for correct rounding */
+ AVRational *bias = av_refstruct_allocz(sizeof(*bias));
+ if (!bias)
+ return AVERROR(ENOMEM);
+ *bias = (AVRational) {1, 2};
+ return ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_DITHER,
+ .type = type,
+ .dither.matrix = bias,
+ });
+ } else {
+ return 0; /* No-op */
+ }
+ case SWS_DITHER_BAYER:
+ /* Hardcode 16x16 matrix for now; in theory we could adjust this
+ * based on the expected level of precision in the output, since lower
+ * bit depth outputs can suffice with smaller dither matrices; however
+ * in practice we probably want to use error diffusion for such low bit
+ * depths anyway */
+ dither.size_log2 = 4;
+ dither.matrix = generate_bayer_matrix(dither.size_log2);
+ if (!dither.matrix)
+ return AVERROR(ENOMEM);
+ return ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_DITHER,
+ .type = type,
+ .dither = dither,
+ });
+ case SWS_DITHER_ED:
+ case SWS_DITHER_A_DITHER:
+ case SWS_DITHER_X_DITHER:
+ return AVERROR(ENOTSUP);
+
+ case SWS_DITHER_NB:
+ break;
+ }
+
+ av_unreachable("Invalid dither mode");
+ return AVERROR(EINVAL);
+}
+
+static inline SwsLinearOp
+linear_mat3(const AVRational m00, const AVRational m01, const AVRational m02,
+ const AVRational m10, const AVRational m11, const AVRational m12,
+ const AVRational m20, const AVRational m21, const AVRational m22)
+{
+ SwsLinearOp c = {{
+ { m00, m01, m02, Q0, Q0 },
+ { m10, m11, m12, Q0, Q0 },
+ { m20, m21, m22, Q0, Q0 },
+ { Q0, Q0, Q0, Q1, Q0 },
+ }};
+
+ c.mask = ff_sws_linear_mask(c);
+ return c;
+}
+
+int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type,
+ SwsOpList *ops, const SwsFormat fmt, bool *incomplete)
+{
+ const AVLumaCoefficients *c = av_csp_luma_coeffs_from_avcsp(fmt.csp);
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_CONVERT,
+ .type = fmt_pixel_type(fmt.format),
+ .convert.to = type,
+ }));
+
+ /* Decode pixel format into standardized range */
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_LINEAR,
+ .lin = fmt_decode_range(fmt, incomplete),
+ }));
+
+ /* Final step, decode colorspace */
+ switch (fmt.csp) {
+ case AVCOL_SPC_RGB:
+ return 0;
+ case AVCOL_SPC_UNSPECIFIED:
+ c = av_csp_luma_coeffs_from_avcsp(AVCOL_SPC_BT470BG);
+ *incomplete = true;
+ /* fall through */
+ case AVCOL_SPC_FCC:
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_SMPTE170M:
+ case AVCOL_SPC_BT709:
+ case AVCOL_SPC_SMPTE240M:
+ case AVCOL_SPC_BT2020_NCL: {
+ AVRational crg = av_sub_q(Q0, av_div_q(c->cr, c->cg));
+ AVRational cbg = av_sub_q(Q0, av_div_q(c->cb, c->cg));
+ AVRational m02 = av_mul_q(Q(2), av_sub_q(Q1, c->cr));
+ AVRational m21 = av_mul_q(Q(2), av_sub_q(Q1, c->cb));
+ AVRational m11 = av_mul_q(cbg, m21);
+ AVRational m12 = av_mul_q(crg, m02);
+
+ return ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_LINEAR,
+ .lin = linear_mat3(
+ Q1, Q0, m02,
+ Q1, m11, m12,
+ Q1, m21, Q0
+ ),
+ });
+ }
+
+ case AVCOL_SPC_YCGCO:
+ return ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_LINEAR,
+ .lin = linear_mat3(
+ Q1, Q(-1), Q( 1),
+ Q1, Q( 1), Q( 0),
+ Q1, Q(-1), Q(-1)
+ ),
+ });
+
+ case AVCOL_SPC_BT2020_CL:
+ case AVCOL_SPC_SMPTE2085:
+ case AVCOL_SPC_CHROMA_DERIVED_NCL:
+ case AVCOL_SPC_CHROMA_DERIVED_CL:
+ case AVCOL_SPC_ICTCP:
+ case AVCOL_SPC_IPT_C2:
+ case AVCOL_SPC_YCGCO_RE:
+ case AVCOL_SPC_YCGCO_RO:
+ return AVERROR(ENOTSUP);
+
+ case AVCOL_SPC_RESERVED:
+ return AVERROR(EINVAL);
+
+ case AVCOL_SPC_NB:
+ break;
+ }
+
+ av_unreachable("Corrupt AVColorSpace value?");
+ return AVERROR(EINVAL);
+}
+
+int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type,
+ SwsOpList *ops, const SwsFormat fmt, bool *incomplete)
+{
+ const AVLumaCoefficients *c = av_csp_luma_coeffs_from_avcsp(fmt.csp);
+
+ switch (fmt.csp) {
+ case AVCOL_SPC_RGB:
+ break;
+ case AVCOL_SPC_UNSPECIFIED:
+ c = av_csp_luma_coeffs_from_avcsp(AVCOL_SPC_BT470BG);
+ *incomplete = true;
+ /* fall through */
+ case AVCOL_SPC_FCC:
+ case AVCOL_SPC_BT470BG:
+ case AVCOL_SPC_SMPTE170M:
+ case AVCOL_SPC_BT709:
+ case AVCOL_SPC_SMPTE240M:
+ case AVCOL_SPC_BT2020_NCL: {
+ AVRational cb1 = av_sub_q(c->cb, Q1);
+ AVRational cr1 = av_sub_q(c->cr, Q1);
+ AVRational m20 = av_make_q(1,2);
+ AVRational m10 = av_mul_q(m20, av_div_q(c->cr, cb1));
+ AVRational m11 = av_mul_q(m20, av_div_q(c->cg, cb1));
+ AVRational m21 = av_mul_q(m20, av_div_q(c->cg, cr1));
+ AVRational m22 = av_mul_q(m20, av_div_q(c->cb, cr1));
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_LINEAR,
+ .lin = linear_mat3(
+ c->cr, c->cg, c->cb,
+ m10, m11, m20,
+ m20, m21, m22
+ ),
+ }));
+ break;
+ }
+
+ case AVCOL_SPC_YCGCO:
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_LINEAR,
+ .lin = linear_mat3(
+ av_make_q( 1, 4), av_make_q(1, 2), av_make_q( 1, 4),
+ av_make_q( 1, 2), av_make_q(0, 1), av_make_q(-1, 2),
+ av_make_q(-1, 4), av_make_q(1, 2), av_make_q(-1, 4)
+ ),
+ }));
+ break;
+
+ case AVCOL_SPC_BT2020_CL:
+ case AVCOL_SPC_SMPTE2085:
+ case AVCOL_SPC_CHROMA_DERIVED_NCL:
+ case AVCOL_SPC_CHROMA_DERIVED_CL:
+ case AVCOL_SPC_ICTCP:
+ case AVCOL_SPC_IPT_C2:
+ case AVCOL_SPC_YCGCO_RE:
+ case AVCOL_SPC_YCGCO_RO:
+ return AVERROR(ENOTSUP);
+
+ case AVCOL_SPC_RESERVED:
+ case AVCOL_SPC_NB:
+ return AVERROR(EINVAL);
+ }
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_LINEAR,
+ .lin = fmt_encode_range(fmt, incomplete),
+ }));
+
+ if (!(fmt.desc->flags & AV_PIX_FMT_FLAG_FLOAT)) {
+ SwsConst range = {0};
+
+ const bool is_ya = fmt.desc->nb_components == 2;
+ for (int i = 0; i < fmt.desc->nb_components; i++) {
+ /* Clamp to legal pixel range */
+ const int idx = i * (is_ya ? 3 : 1);
+ range.q4[idx] = Q((1 << fmt.desc->comp[i].depth) - 1);
+ }
+
+ RET(fmt_dither(ctx, ops, type, fmt));
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_MAX,
+ .type = type,
+ .c.q4 = { Q0, Q0, Q0, Q0 },
+ }));
+
+ RET(ff_sws_op_list_append(ops, &(SwsOp) {
+ .op = SWS_OP_MIN,
+ .type = type,
+ .c = range,
+ }));
+ }
+
+ return ff_sws_op_list_append(ops, &(SwsOp) {
+ .type = type,
+ .op = SWS_OP_CONVERT,
+ .convert.to = fmt_pixel_type(fmt.format),
+ });
+}
diff --git a/libswscale/format.h b/libswscale/format.h
index be92038f4f..e6a1fd7116 100644
--- a/libswscale/format.h
+++ b/libswscale/format.h
@@ -148,4 +148,27 @@ int ff_test_fmt(const SwsFormat *fmt, int output);
/* Returns true if the formats are incomplete, false otherwise */
bool ff_infer_colors(SwsColor *src, SwsColor *dst);
+typedef struct SwsOpList SwsOpList;
+typedef enum SwsPixelType SwsPixelType;
+
+/**
+ * Append a set of operations for decoding/encoding raw pixels. This will
+ * handle input read/write, swizzling, shifting and byte swapping.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt);
+int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt);
+
+/**
+ * Append a set of operations for transforming decoded pixel values to/from
+ * normalized RGB in the specified gamut and pixel type.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
+ const SwsFormat fmt, bool *incomplete);
+int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
+ const SwsFormat fmt, bool *incomplete);
+
#endif /* SWSCALE_FORMAT_H */
commit 5e6ffa0376b6b9fdc0a093d7ba702a88caf23a84
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Tue Mar 25 22:55:13 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
tests/checkasm: add checkasm tests for swscale ops
Because of the lack of an external ABI on low-level kernels, we cannot
directly test internal functions. Instead, we construct a minimal op chain
consisting of a read, the op to be tested, and a write.
The bigger complication arises from the fact that the backend may generate
arbitrary internal state that needs to be passed back to the implementation,
which means we cannot directly call `func_ref` on the generated chain. To get
around this, always compile the op chain twice - once using the backend to be
tested, and once using the reference C backend.
The actual entry point may also just be a shared wrapper, so we need to
be very careful to run checkasm_check_func() on a pseudo-pointer that will
actually be unique for each combination of backend and active CPU flags.
diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile
index d65f48f97b..5ce4725543 100644
--- a/tests/checkasm/Makefile
+++ b/tests/checkasm/Makefile
@@ -69,7 +69,13 @@ AVFILTEROBJS-$(CONFIG_SOBEL_FILTER) += vf_convolution.o
CHECKASMOBJS-$(CONFIG_AVFILTER) += $(AVFILTEROBJS-yes)
# swscale tests
-SWSCALEOBJS += sw_gbrp.o sw_range_convert.o sw_rgb.o sw_scale.o sw_yuv2rgb.o sw_yuv2yuv.o
+SWSCALEOBJS += sw_gbrp.o \
+ sw_ops.o \
+ sw_range_convert.o \
+ sw_rgb.o \
+ sw_scale.o \
+ sw_yuv2rgb.o \
+ sw_yuv2yuv.o
CHECKASMOBJS-$(CONFIG_SWSCALE) += $(SWSCALEOBJS)
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 8693086575..5312d02909 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -307,6 +307,7 @@ static const struct {
{ "sw_scale", checkasm_check_sw_scale },
{ "sw_yuv2rgb", checkasm_check_sw_yuv2rgb },
{ "sw_yuv2yuv", checkasm_check_sw_yuv2yuv },
+ { "sw_ops", checkasm_check_sw_ops },
#endif
#if CONFIG_AVUTIL
{ "aes", checkasm_check_aes },
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 714afd1dc5..bb6b413aba 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -135,6 +135,7 @@ void checkasm_check_sw_rgb(void);
void checkasm_check_sw_scale(void);
void checkasm_check_sw_yuv2rgb(void);
void checkasm_check_sw_yuv2yuv(void);
+void checkasm_check_sw_ops(void);
void checkasm_check_takdsp(void);
void checkasm_check_utvideodsp(void);
void checkasm_check_v210dec(void);
diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c
new file mode 100644
index 0000000000..35ade0c751
--- /dev/null
+++ b/tests/checkasm/sw_ops.c
@@ -0,0 +1,776 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU 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 <string.h>
+
+#include "libavutil/avassert.h"
+#include "libavutil/mem_internal.h"
+#include "libavutil/refstruct.h"
+
+#include "libswscale/ops.h"
+#include "libswscale/ops_internal.h"
+
+#include "checkasm.h"
+
+enum {
+ LINES = 2,
+ NB_PLANES = 4,
+ PIXELS = 64,
+};
+
+enum {
+ U8 = SWS_PIXEL_U8,
+ U16 = SWS_PIXEL_U16,
+ U32 = SWS_PIXEL_U32,
+ F32 = SWS_PIXEL_F32,
+};
+
+#define FMT(fmt, ...) tprintf((char[256]) {0}, 256, fmt, __VA_ARGS__)
+static const char *tprintf(char buf[], size_t size, const char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, size, fmt, ap);
+ va_end(ap);
+ return buf;
+}
+
+static int rw_pixel_bits(const SwsOp *op)
+{
+ const int elems = op->rw.packed ? op->rw.elems : 1;
+ const int size = ff_sws_pixel_type_size(op->type);
+ const int bits = 8 >> op->rw.frac;
+ av_assert1(bits >= 1);
+ return elems * size * bits;
+}
+
+static float rndf(void)
+{
+ union { uint32_t u; float f; } x;
+ do {
+ x.u = rnd();
+ } while (!isnormal(x.f));
+ return x.f;
+}
+
+static void fill32f(float *line, int num, unsigned range)
+{
+ const float scale = (float) range / UINT32_MAX;
+ for (int i = 0; i < num; i++)
+ line[i] = range ? scale * rnd() : rndf();
+}
+
+static void fill32(uint32_t *line, int num, unsigned range)
+{
+ for (int i = 0; i < num; i++)
+ line[i] = range ? rnd() % (range + 1) : rnd();
+}
+
+static void fill16(uint16_t *line, int num, unsigned range)
+{
+ if (!range) {
+ fill32((uint32_t *) line, AV_CEIL_RSHIFT(num, 1), 0);
+ } else {
+ for (int i = 0; i < num; i++)
+ line[i] = rnd() % (range + 1);
+ }
+}
+
+static void fill8(uint8_t *line, int num, unsigned range)
+{
+ if (!range) {
+ fill32((uint32_t *) line, AV_CEIL_RSHIFT(num, 2), 0);
+ } else {
+ for (int i = 0; i < num; i++)
+ line[i] = rnd() % (range + 1);
+ }
+}
+
+static void check_ops(const char *report, const unsigned ranges[NB_PLANES],
+ const SwsOp *ops)
+{
+ SwsContext *ctx = sws_alloc_context();
+ SwsCompiledOp comp_ref = {0}, comp_new = {0};
+ const SwsOpBackend *backend_new = NULL;
+ SwsOpList oplist = { .ops = (SwsOp *) ops };
+ const SwsOp *read_op, *write_op;
+ static const unsigned def_ranges[4] = {0};
+ if (!ranges)
+ ranges = def_ranges;
+
+ declare_func(void, const SwsOpExec *, const void *, int bx, int y, int bx_end, int y_end);
+
+ DECLARE_ALIGNED_64(char, src0)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
+ DECLARE_ALIGNED_64(char, src1)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
+ DECLARE_ALIGNED_64(char, dst0)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
+ DECLARE_ALIGNED_64(char, dst1)[NB_PLANES][LINES][PIXELS * sizeof(uint32_t[4])];
+
+ if (!ctx)
+ return;
+ ctx->flags = SWS_BITEXACT;
+
+ read_op = &ops[0];
+ for (oplist.num_ops = 0; ops[oplist.num_ops].op; oplist.num_ops++)
+ write_op = &ops[oplist.num_ops];
+
+ const int read_size = PIXELS * rw_pixel_bits(read_op) >> 3;
+ const int write_size = PIXELS * rw_pixel_bits(write_op) >> 3;
+
+ for (int p = 0; p < NB_PLANES; p++) {
+ void *plane = src0[p];
+ switch (read_op->type) {
+ case U8: fill8(plane, sizeof(src0[p]) / sizeof(uint8_t), ranges[p]); break;
+ case U16: fill16(plane, sizeof(src0[p]) / sizeof(uint16_t), ranges[p]); break;
+ case U32: fill32(plane, sizeof(src0[p]) / sizeof(uint32_t), ranges[p]); break;
+ case F32: fill32f(plane, sizeof(src0[p]) / sizeof(uint32_t), ranges[p]); break;
+ }
+ }
+
+ memcpy(src1, src0, sizeof(src0));
+ memset(dst0, 0, sizeof(dst0));
+ memset(dst1, 0, sizeof(dst1));
+
+ /* Compile `ops` using both the asm and c backends */
+ for (int n = 0; ff_sws_op_backends[n]; n++) {
+ const SwsOpBackend *backend = ff_sws_op_backends[n];
+ const bool is_ref = !strcmp(backend->name, "c");
+ if (is_ref || !comp_new.func) {
+ SwsCompiledOp comp;
+ int ret = ff_sws_ops_compile_backend(ctx, backend, &oplist, &comp);
+ if (ret == AVERROR(ENOTSUP))
+ continue;
+ else if (ret < 0)
+ fail();
+ else if (PIXELS % comp.block_size != 0)
+ fail();
+
+ if (is_ref)
+ comp_ref = comp;
+ if (!comp_new.func) {
+ comp_new = comp;
+ backend_new = backend;
+ }
+ }
+ }
+
+ av_assert0(comp_ref.func && comp_new.func);
+
+ SwsOpExec exec = {0};
+ exec.width = PIXELS;
+ exec.height = exec.slice_h = 1;
+ for (int i = 0; i < NB_PLANES; i++) {
+ exec.in_stride[i] = sizeof(src0[i][0]);
+ exec.out_stride[i] = sizeof(dst0[i][0]);
+ exec.in_bump[i] = exec.in_stride[i] - read_size;
+ exec.out_bump[i] = exec.out_stride[i] - write_size;
+ }
+
+ /**
+ * Don't use check_func() because the actual function pointer may be a
+ * wrapper shared by multiple implementations. Instead, take a hash of both
+ * the backend pointer and the active CPU flags.
+ */
+ uintptr_t id = (uintptr_t) backend_new;
+ id ^= (id << 6) + (id >> 2) + 0x9e3779b97f4a7c15 + comp_new.cpu_flags;
+
+ checkasm_save_context();
+ if (checkasm_check_func((void *) id, "%s", report)) {
+ func_new = comp_new.func;
+ func_ref = comp_ref.func;
+
+ exec.block_size_in = comp_ref.block_size * rw_pixel_bits(read_op) >> 3;
+ exec.block_size_out = comp_ref.block_size * rw_pixel_bits(write_op) >> 3;
+ for (int i = 0; i < NB_PLANES; i++) {
+ exec.in[i] = (void *) src0[i];
+ exec.out[i] = (void *) dst0[i];
+ }
+ call_ref(&exec, comp_ref.priv, 0, 0, PIXELS / comp_ref.block_size, LINES);
+
+ exec.block_size_in = comp_new.block_size * rw_pixel_bits(read_op) >> 3;
+ exec.block_size_out = comp_new.block_size * rw_pixel_bits(write_op) >> 3;
+ for (int i = 0; i < NB_PLANES; i++) {
+ exec.in[i] = (void *) src1[i];
+ exec.out[i] = (void *) dst1[i];
+ }
+ call_new(&exec, comp_new.priv, 0, 0, PIXELS / comp_new.block_size, LINES);
+
+ for (int i = 0; i < NB_PLANES; i++) {
+ const char *name = FMT("%s[%d]", report, i);
+ const int stride = sizeof(dst0[i][0]);
+
+ switch (write_op->type) {
+ case U8:
+ checkasm_check(uint8_t, (void *) dst0[i], stride,
+ (void *) dst1[i], stride,
+ write_size, LINES, name);
+ break;
+ case U16:
+ checkasm_check(uint16_t, (void *) dst0[i], stride,
+ (void *) dst1[i], stride,
+ write_size >> 1, LINES, name);
+ break;
+ case U32:
+ checkasm_check(uint32_t, (void *) dst0[i], stride,
+ (void *) dst1[i], stride,
+ write_size >> 2, LINES, name);
+ break;
+ case F32:
+ checkasm_check(float_ulp, (void *) dst0[i], stride,
+ (void *) dst1[i], stride,
+ write_size >> 2, LINES, name, 0);
+ break;
+ }
+
+ if (write_op->rw.packed)
+ break;
+ }
+
+ bench_new(&exec, comp_new.priv, 0, 0, PIXELS / comp_new.block_size, LINES);
+ }
+
+ if (comp_new.func != comp_ref.func && comp_new.free)
+ comp_new.free(comp_new.priv);
+ if (comp_ref.free)
+ comp_ref.free(comp_ref.priv);
+ sws_free_context(&ctx);
+}
+
+#define CHECK_RANGES(NAME, RANGES, N_IN, N_OUT, IN, OUT, ...) \
+ do { \
+ check_ops(NAME, RANGES, (SwsOp[]) { \
+ { \
+ .op = SWS_OP_READ, \
+ .type = IN, \
+ .rw.elems = N_IN, \
+ }, \
+ __VA_ARGS__, \
+ { \
+ .op = SWS_OP_WRITE, \
+ .type = OUT, \
+ .rw.elems = N_OUT, \
+ }, {0} \
+ }); \
+ } while (0)
+
+#define MK_RANGES(R) ((const unsigned[]) { R, R, R, R })
+#define CHECK_RANGE(NAME, RANGE, N_IN, N_OUT, IN, OUT, ...) \
+ CHECK_RANGES(NAME, MK_RANGES(RANGE), N_IN, N_OUT, IN, OUT, __VA_ARGS__)
+
+#define CHECK_COMMON_RANGE(NAME, RANGE, IN, OUT, ...) \
+ CHECK_RANGE(FMT("%s_p1000", NAME), RANGE, 1, 1, IN, OUT, __VA_ARGS__); \
+ CHECK_RANGE(FMT("%s_p1110", NAME), RANGE, 3, 3, IN, OUT, __VA_ARGS__); \
+ CHECK_RANGE(FMT("%s_p1111", NAME), RANGE, 4, 4, IN, OUT, __VA_ARGS__); \
+ CHECK_RANGE(FMT("%s_p1001", NAME), RANGE, 4, 2, IN, OUT, __VA_ARGS__, { \
+ .op = SWS_OP_SWIZZLE, \
+ .type = OUT, \
+ .swizzle = SWS_SWIZZLE(0, 3, 1, 2), \
+ })
+
+#define CHECK(NAME, N_IN, N_OUT, IN, OUT, ...) \
+ CHECK_RANGE(NAME, 0, N_IN, N_OUT, IN, OUT, __VA_ARGS__)
+
+#define CHECK_COMMON(NAME, IN, OUT, ...) \
+ CHECK_COMMON_RANGE(NAME, 0, IN, OUT, __VA_ARGS__)
+
+static void check_read_write(void)
+{
+ for (SwsPixelType t = U8; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ for (int i = 1; i <= 4; i++) {
+ /* Test N->N planar read/write */
+ for (int o = 1; o <= i; o++) {
+ check_ops(FMT("rw_%d_%d_%s", i, o, type), NULL, (SwsOp[]) {
+ {
+ .op = SWS_OP_READ,
+ .type = t,
+ .rw.elems = i,
+ }, {
+ .op = SWS_OP_WRITE,
+ .type = t,
+ .rw.elems = o,
+ }, {0}
+ });
+ }
+
+ /* Test packed read/write */
+ if (i == 1)
+ continue;
+
+ check_ops(FMT("read_packed%d_%s", i, type), NULL, (SwsOp[]) {
+ {
+ .op = SWS_OP_READ,
+ .type = t,
+ .rw.elems = i,
+ .rw.packed = true,
+ }, {
+ .op = SWS_OP_WRITE,
+ .type = t,
+ .rw.elems = i,
+ }, {0}
+ });
+
+ check_ops(FMT("write_packed%d_%s", i, type), NULL, (SwsOp[]) {
+ {
+ .op = SWS_OP_READ,
+ .type = t,
+ .rw.elems = i,
+ }, {
+ .op = SWS_OP_WRITE,
+ .type = t,
+ .rw.elems = i,
+ .rw.packed = true,
+ }, {0}
+ });
+ }
+ }
+
+ /* Test fractional reads/writes */
+ for (int frac = 1; frac <= 3; frac++) {
+ const int bits = 8 >> frac;
+ const int range = (1 << bits) - 1;
+ if (bits == 2)
+ continue; /* no 2 bit packed formats currently exist */
+
+ check_ops(FMT("read_frac%d", frac), NULL, (SwsOp[]) {
+ {
+ .op = SWS_OP_READ,
+ .type = U8,
+ .rw.elems = 1,
+ .rw.frac = frac,
+ }, {
+ .op = SWS_OP_WRITE,
+ .type = U8,
+ .rw.elems = 1,
+ }, {0}
+ });
+
+ check_ops(FMT("write_frac%d", frac), MK_RANGES(range), (SwsOp[]) {
+ {
+ .op = SWS_OP_READ,
+ .type = U8,
+ .rw.elems = 1,
+ }, {
+ .op = SWS_OP_WRITE,
+ .type = U8,
+ .rw.elems = 1,
+ .rw.frac = frac,
+ }, {0}
+ });
+ }
+}
+
+static void check_swap_bytes(void)
+{
+ CHECK_COMMON("swap_bytes_16", U16, U16, {
+ .op = SWS_OP_SWAP_BYTES,
+ .type = U16,
+ });
+
+ CHECK_COMMON("swap_bytes_32", U32, U32, {
+ .op = SWS_OP_SWAP_BYTES,
+ .type = U32,
+ });
+}
+
+static void check_pack_unpack(void)
+{
+ const struct {
+ SwsPixelType type;
+ SwsPackOp op;
+ } patterns[] = {
+ { U8, {{ 3, 3, 2 }}},
+ { U8, {{ 2, 3, 3 }}},
+ { U8, {{ 1, 2, 1 }}},
+ {U16, {{ 5, 6, 5 }}},
+ {U16, {{ 5, 5, 5 }}},
+ {U16, {{ 4, 4, 4 }}},
+ {U32, {{ 2, 10, 10, 10 }}},
+ {U32, {{10, 10, 10, 2 }}},
+ };
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(patterns); i++) {
+ const SwsPixelType type = patterns[i].type;
+ const SwsPackOp pack = patterns[i].op;
+ const int num = pack.pattern[3] ? 4 : 3;
+ const char *pat = FMT("%d%d%d%d", pack.pattern[0], pack.pattern[1],
+ pack.pattern[2], pack.pattern[3]);
+ const int total = pack.pattern[0] + pack.pattern[1] +
+ pack.pattern[2] + pack.pattern[3];
+ const unsigned ranges[4] = {
+ (1 << pack.pattern[0]) - 1,
+ (1 << pack.pattern[1]) - 1,
+ (1 << pack.pattern[2]) - 1,
+ (1 << pack.pattern[3]) - 1,
+ };
+
+ CHECK_RANGES(FMT("pack_%s", pat), ranges, num, 1, type, type, {
+ .op = SWS_OP_PACK,
+ .type = type,
+ .pack = pack,
+ });
+
+ CHECK_RANGE(FMT("unpack_%s", pat), (1 << total) - 1, 1, num, type, type, {
+ .op = SWS_OP_UNPACK,
+ .type = type,
+ .pack = pack,
+ });
+ }
+}
+
+static AVRational rndq(SwsPixelType t)
+{
+ const unsigned num = rnd();
+ if (ff_sws_pixel_type_is_int(t)) {
+ const unsigned mask = (1 << (ff_sws_pixel_type_size(t) * 8)) - 1;
+ return (AVRational) { num & mask, 1 };
+ } else {
+ const unsigned den = rnd();
+ return (AVRational) { num, den ? den : 1 };
+ }
+}
+
+static void check_clear(void)
+{
+ for (SwsPixelType t = U8; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ const int bits = ff_sws_pixel_type_size(t) * 8;
+
+ /* TODO: AVRational can't fit 32 bit constants */
+ if (bits < 32) {
+ const AVRational chroma = (AVRational) { 1 << (bits - 1), 1};
+ const AVRational alpha = (AVRational) { (1 << bits) - 1, 1};
+ const AVRational zero = (AVRational) { 0, 1};
+ const AVRational none = {0};
+
+ const SwsConst patterns[] = {
+ /* Zero only */
+ {.q4 = { none, none, none, zero }},
+ {.q4 = { zero, none, none, none }},
+ /* Alpha only */
+ {.q4 = { none, none, none, alpha }},
+ {.q4 = { alpha, none, none, none }},
+ /* Chroma only */
+ {.q4 = { chroma, chroma, none, none }},
+ {.q4 = { none, chroma, chroma, none }},
+ {.q4 = { none, none, chroma, chroma }},
+ {.q4 = { chroma, none, chroma, none }},
+ {.q4 = { none, chroma, none, chroma }},
+ /* Alpha+chroma */
+ {.q4 = { chroma, chroma, none, alpha }},
+ {.q4 = { none, chroma, chroma, alpha }},
+ {.q4 = { alpha, none, chroma, chroma }},
+ {.q4 = { chroma, none, chroma, alpha }},
+ {.q4 = { alpha, chroma, none, chroma }},
+ /* Random values */
+ {.q4 = { none, rndq(t), rndq(t), rndq(t) }},
+ {.q4 = { none, rndq(t), rndq(t), rndq(t) }},
+ {.q4 = { none, rndq(t), rndq(t), rndq(t) }},
+ {.q4 = { none, rndq(t), rndq(t), rndq(t) }},
+ };
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(patterns); i++) {
+ CHECK(FMT("clear_pattern_%s[%d]", type, i), 4, 4, t, t, {
+ .op = SWS_OP_CLEAR,
+ .type = t,
+ .c = patterns[i],
+ });
+ }
+ } else if (!ff_sws_pixel_type_is_int(t)) {
+ /* Floating point YUV doesn't exist, only alpha needs to be cleared */
+ CHECK(FMT("clear_alpha_%s", type), 4, 4, t, t, {
+ .op = SWS_OP_CLEAR,
+ .type = t,
+ .c.q4[3] = { 0, 1 },
+ });
+ }
+ }
+}
+
+static void check_shift(void)
+{
+ for (SwsPixelType t = U16; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ if (!ff_sws_pixel_type_is_int(t))
+ continue;
+
+ for (int shift = 1; shift <= 8; shift++) {
+ CHECK_COMMON(FMT("lshift%d_%s", shift, type), t, t, {
+ .op = SWS_OP_LSHIFT,
+ .type = t,
+ .c.u = shift,
+ });
+
+ CHECK_COMMON(FMT("rshift%d_%s", shift, type), t, t, {
+ .op = SWS_OP_RSHIFT,
+ .type = t,
+ .c.u = shift,
+ });
+ }
+ }
+}
+
+static void check_swizzle(void)
+{
+ for (SwsPixelType t = U8; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ static const int patterns[][4] = {
+ /* Pure swizzle */
+ {3, 0, 1, 2},
+ {3, 0, 2, 1},
+ {2, 1, 0, 3},
+ {3, 2, 1, 0},
+ {3, 1, 0, 2},
+ {3, 2, 0, 1},
+ {1, 2, 0, 3},
+ {1, 0, 2, 3},
+ {2, 0, 1, 3},
+ {2, 3, 1, 0},
+ {2, 1, 3, 0},
+ {1, 2, 3, 0},
+ {1, 3, 2, 0},
+ {0, 2, 1, 3},
+ {0, 2, 3, 1},
+ {0, 3, 1, 2},
+ {3, 1, 2, 0},
+ {0, 3, 2, 1},
+ /* Luma expansion */
+ {0, 0, 0, 3},
+ {3, 0, 0, 0},
+ {0, 0, 0, 1},
+ {1, 0, 0, 0},
+ };
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(patterns); i++) {
+ const int x = patterns[i][0], y = patterns[i][1],
+ z = patterns[i][2], w = patterns[i][3];
+ CHECK(FMT("swizzle_%d%d%d%d_%s", x, y, z, w, type), 4, 4, t, t, {
+ .op = SWS_OP_SWIZZLE,
+ .type = t,
+ .swizzle = SWS_SWIZZLE(x, y, z, w),
+ });
+ }
+ }
+}
+
+static void check_convert(void)
+{
+ for (SwsPixelType i = U8; i < SWS_PIXEL_TYPE_NB; i++) {
+ const char *itype = ff_sws_pixel_type_name(i);
+ const int isize = ff_sws_pixel_type_size(i);
+ for (SwsPixelType o = U8; o < SWS_PIXEL_TYPE_NB; o++) {
+ const char *otype = ff_sws_pixel_type_name(o);
+ const int osize = ff_sws_pixel_type_size(o);
+ const char *name = FMT("convert_%s_%s", itype, otype);
+ if (i == o)
+ continue;
+
+ if (isize < osize || !ff_sws_pixel_type_is_int(o)) {
+ CHECK_COMMON(name, i, o, {
+ .op = SWS_OP_CONVERT,
+ .type = i,
+ .convert.to = o,
+ });
+ } else if (isize > osize || !ff_sws_pixel_type_is_int(i)) {
+ uint32_t range = (1 << osize * 8) - 1;
+ CHECK_COMMON_RANGE(name, range, i, o, {
+ .op = SWS_OP_CONVERT,
+ .type = i,
+ .convert.to = o,
+ });
+ }
+ }
+ }
+
+ /* Check expanding conversions */
+ CHECK_COMMON("expand16", U8, U16, {
+ .op = SWS_OP_CONVERT,
+ .type = U8,
+ .convert.to = U16,
+ .convert.expand = true,
+ });
+
+ CHECK_COMMON("expand32", U8, U32, {
+ .op = SWS_OP_CONVERT,
+ .type = U8,
+ .convert.to = U32,
+ .convert.expand = true,
+ });
+}
+
+static void check_dither(void)
+{
+ for (SwsPixelType t = F32; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ if (ff_sws_pixel_type_is_int(t))
+ continue;
+
+ /* Test all sizes up to 256x256 */
+ for (int size_log2 = 0; size_log2 <= 8; size_log2++) {
+ const int size = 1 << size_log2;
+ AVRational *matrix = av_refstruct_allocz(size * size * sizeof(*matrix));
+ if (!matrix) {
+ fail();
+ return;
+ }
+
+ if (size == 1) {
+ matrix[0] = (AVRational) { 1, 2 };
+ } else {
+ for (int i = 0; i < size * size; i++)
+ matrix[i] = rndq(t);
+ }
+
+ CHECK_COMMON(FMT("dither_%dx%d_%s", size, size, type), t, t, {
+ .op = SWS_OP_DITHER,
+ .type = t,
+ .dither.size_log2 = size_log2,
+ .dither.matrix = matrix,
+ });
+
+ av_refstruct_unref(&matrix);
+ }
+ }
+}
+
+static void check_min_max(void)
+{
+ for (SwsPixelType t = U8; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ CHECK_COMMON(FMT("min_%s", type), t, t, {
+ .op = SWS_OP_MIN,
+ .type = t,
+ .c.q4 = { rndq(t), rndq(t), rndq(t), rndq(t) },
+ });
+
+ CHECK_COMMON(FMT("max_%s", type), t, t, {
+ .op = SWS_OP_MAX,
+ .type = t,
+ .c.q4 = { rndq(t), rndq(t), rndq(t), rndq(t) },
+ });
+ }
+}
+
+static void check_linear(void)
+{
+ static const struct {
+ const char *name;
+ uint32_t mask;
+ } patterns[] = {
+ { "noop", 0 },
+ { "luma", SWS_MASK_LUMA },
+ { "alpha", SWS_MASK_ALPHA },
+ { "luma+alpha", SWS_MASK_LUMA | SWS_MASK_ALPHA },
+ { "dot3", 0x7 },
+ { "dot4", 0xF },
+ { "row0", SWS_MASK_ROW(0) },
+ { "row0+alpha", SWS_MASK_ROW(0) | SWS_MASK_ALPHA },
+ { "off3", SWS_MASK_OFF3 },
+ { "off3+alpha", SWS_MASK_OFF3 | SWS_MASK_ALPHA },
+ { "diag3", SWS_MASK_DIAG3 },
+ { "diag4", SWS_MASK_DIAG4 },
+ { "diag3+alpha", SWS_MASK_DIAG3 | SWS_MASK_ALPHA },
+ { "diag3+off3", SWS_MASK_DIAG3 | SWS_MASK_OFF3 },
+ { "diag3+off3+alpha", SWS_MASK_DIAG3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA },
+ { "diag4+off4", SWS_MASK_DIAG4 | SWS_MASK_OFF4 },
+ { "matrix3", SWS_MASK_MAT3 },
+ { "matrix3+off3", SWS_MASK_MAT3 | SWS_MASK_OFF3 },
+ { "matrix3+off3+alpha", SWS_MASK_MAT3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA },
+ { "matrix4", SWS_MASK_MAT4 },
+ { "matrix4+off4", SWS_MASK_MAT4 | SWS_MASK_OFF4 },
+ };
+
+ for (SwsPixelType t = F32; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ if (ff_sws_pixel_type_is_int(t))
+ continue;
+
+ for (int p = 0; p < FF_ARRAY_ELEMS(patterns); p++) {
+ const uint32_t mask = patterns[p].mask;
+ SwsLinearOp lin = { .mask = mask };
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 5; j++) {
+ if (mask & SWS_MASK(i, j)) {
+ lin.m[i][j] = rndq(t);
+ } else {
+ lin.m[i][j] = (AVRational) { i == j, 1 };
+ }
+ }
+ }
+
+ CHECK(FMT("linear_%s_%s", patterns[p].name, type), 4, 4, t, t, {
+ .op = SWS_OP_LINEAR,
+ .type = t,
+ .lin = lin,
+ });
+ }
+ }
+}
+
+static void check_scale(void)
+{
+ for (SwsPixelType t = F32; t < SWS_PIXEL_TYPE_NB; t++) {
+ const char *type = ff_sws_pixel_type_name(t);
+ const int bits = ff_sws_pixel_type_size(t) * 8;
+ if (ff_sws_pixel_type_is_int(t)) {
+ /* Ensure the result won't exceed the value range */
+ const unsigned max = (1 << bits) - 1;
+ const unsigned scale = rnd() & max;
+ const unsigned range = max / (scale ? scale : 1);
+ CHECK_COMMON_RANGE(FMT("scale_%s", type), range, t, t, {
+ .op = SWS_OP_SCALE,
+ .type = t,
+ .c.q = { scale, 1 },
+ });
+ } else {
+ CHECK_COMMON(FMT("scale_%s", type), t, t, {
+ .op = SWS_OP_SCALE,
+ .type = t,
+ .c.q = rndq(t),
+ });
+ }
+ }
+}
+
+void checkasm_check_sw_ops(void)
+{
+ check_read_write();
+ report("read_write");
+ check_swap_bytes();
+ report("swap_bytes");
+ check_pack_unpack();
+ report("pack_unpack");
+ check_clear();
+ report("clear");
+ check_shift();
+ report("shift");
+ check_swizzle();
+ report("swizzle");
+ check_convert();
+ report("convert");
+ check_dither();
+ report("dither");
+ check_min_max();
+ report("min_max");
+ check_linear();
+ report("linear");
+ check_scale();
+ report("scale");
+}
diff --git a/tests/fate/checkasm.mak b/tests/fate/checkasm.mak
index 366afa0373..56476d254c 100644
--- a/tests/fate/checkasm.mak
+++ b/tests/fate/checkasm.mak
@@ -46,6 +46,7 @@ FATE_CHECKASM = fate-checkasm-aacencdsp \
fate-checkasm-svq1enc \
fate-checkasm-synth_filter \
fate-checkasm-sw_gbrp \
+ fate-checkasm-sw_ops \
fate-checkasm-sw_range_convert \
fate-checkasm-sw_rgb \
fate-checkasm-sw_scale \
commit 982d3a98d0247e933783c14df5b3dfb4441769c6
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Wed Mar 19 02:54:22 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/x86: add SIMD backend
This covers most 8-bit and 16-bit ops, and some 32-bit ops. It also covers all
floating point operations. While this is not yet 100% coverage, it's good
enough for the vast majority of formats out there.
Of special note is the packed shuffle fast path, which uses pshufb at vector
sizes up to AVX512.
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 4c147f6467..e8c05587e1 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -29,9 +29,13 @@
extern const SwsOpBackend backend_c;
extern const SwsOpBackend backend_murder;
+extern const SwsOpBackend backend_x86;
const SwsOpBackend * const ff_sws_op_backends[] = {
&backend_murder,
+#if ARCH_X86_64
+ &backend_x86,
+#endif
&backend_c,
NULL
};
diff --git a/libswscale/x86/Makefile b/libswscale/x86/Makefile
index f00154941d..f1d6255549 100644
--- a/libswscale/x86/Makefile
+++ b/libswscale/x86/Makefile
@@ -16,3 +16,9 @@ X86ASM-OBJS += x86/input.o \
x86/rgb_2_rgb.o \
x86/yuv_2_rgb.o \
x86/yuv2yuvX.o \
+
+ifdef ARCH_X86_64
+X86ASM-OBJS += x86/ops_int.o \
+ x86/ops_float.o \
+ x86/ops.o
+endif
diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c
new file mode 100644
index 0000000000..836c5f0d13
--- /dev/null
+++ b/libswscale/x86/ops.c
@@ -0,0 +1,723 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 <float.h>
+
+#include <libavutil/avassert.h>
+#include <libavutil/mem.h>
+
+#include "../ops_chain.h"
+
+#define DECL_ENTRY(TYPE, NAME, ...) \
+ static const SwsOpEntry op_##NAME = { \
+ .type = SWS_PIXEL_##TYPE, \
+ __VA_ARGS__ \
+ }
+
+#define DECL_ASM(TYPE, NAME, ...) \
+ void ff_##NAME(void); \
+ DECL_ENTRY(TYPE, NAME, \
+ .func = ff_##NAME, \
+ __VA_ARGS__)
+
+#define DECL_PATTERN(TYPE, NAME, X, Y, Z, W, ...) \
+ DECL_ASM(TYPE, p##X##Y##Z##W##_##NAME, \
+ .unused = { !X, !Y, !Z, !W }, \
+ __VA_ARGS__ \
+ )
+
+#define REF_PATTERN(NAME, X, Y, Z, W) \
+ &op_p##X##Y##Z##W##_##NAME
+
+#define DECL_COMMON_PATTERNS(TYPE, NAME, ...) \
+ DECL_PATTERN(TYPE, NAME, 1, 0, 0, 0, __VA_ARGS__); \
+ DECL_PATTERN(TYPE, NAME, 1, 0, 0, 1, __VA_ARGS__); \
+ DECL_PATTERN(TYPE, NAME, 1, 1, 1, 0, __VA_ARGS__); \
+ DECL_PATTERN(TYPE, NAME, 1, 1, 1, 1, __VA_ARGS__) \
+
+#define REF_COMMON_PATTERNS(NAME) \
+ REF_PATTERN(NAME, 1, 0, 0, 0), \
+ REF_PATTERN(NAME, 1, 0, 0, 1), \
+ REF_PATTERN(NAME, 1, 1, 1, 0), \
+ REF_PATTERN(NAME, 1, 1, 1, 1)
+
+#define DECL_RW(EXT, TYPE, NAME, OP, ELEMS, PACKED, FRAC) \
+ DECL_ASM(TYPE, NAME##ELEMS##EXT, \
+ .op = SWS_OP_##OP, \
+ .rw = { .elems = ELEMS, .packed = PACKED, .frac = FRAC }, \
+ );
+
+#define DECL_PACKED_RW(EXT, DEPTH) \
+ DECL_RW(EXT, U##DEPTH, read##DEPTH##_packed, READ, 2, true, 0) \
+ DECL_RW(EXT, U##DEPTH, read##DEPTH##_packed, READ, 3, true, 0) \
+ DECL_RW(EXT, U##DEPTH, read##DEPTH##_packed, READ, 4, true, 0) \
+ DECL_RW(EXT, U##DEPTH, write##DEPTH##_packed, WRITE, 2, true, 0) \
+ DECL_RW(EXT, U##DEPTH, write##DEPTH##_packed, WRITE, 3, true, 0) \
+ DECL_RW(EXT, U##DEPTH, write##DEPTH##_packed, WRITE, 4, true, 0) \
+
+#define DECL_PACK_UNPACK(EXT, TYPE, X, Y, Z, W) \
+ DECL_ASM(TYPE, pack_##X##Y##Z##W##EXT, \
+ .op = SWS_OP_PACK, \
+ .pack.pattern = {X, Y, Z, W}, \
+ ); \
+ \
+ DECL_ASM(TYPE, unpack_##X##Y##Z##W##EXT, \
+ .op = SWS_OP_UNPACK, \
+ .pack.pattern = {X, Y, Z, W}, \
+ ); \
+
+static int setup_swap_bytes(const SwsOp *op, SwsOpPriv *out)
+{
+ const int mask = ff_sws_pixel_type_size(op->type) - 1;
+ for (int i = 0; i < 16; i++)
+ out->u8[i] = (i & ~mask) | (mask - (i & mask));
+ return 0;
+}
+
+#define DECL_SWAP_BYTES(EXT, TYPE, X, Y, Z, W) \
+ DECL_ENTRY(TYPE, p##X##Y##Z##W##_swap_bytes_##TYPE##EXT, \
+ .op = SWS_OP_SWAP_BYTES, \
+ .unused = { !X, !Y, !Z, !W }, \
+ .func = ff_p##X##Y##Z##W##_shuffle##EXT, \
+ .setup = setup_swap_bytes, \
+ );
+
+#define DECL_CLEAR_ALPHA(EXT, IDX) \
+ DECL_ASM(U8, clear_alpha##IDX##EXT, \
+ .op = SWS_OP_CLEAR, \
+ .clear_value = -1, \
+ .unused[IDX] = true, \
+ ); \
+
+#define DECL_CLEAR_ZERO(EXT, IDX) \
+ DECL_ASM(U8, clear_zero##IDX##EXT, \
+ .op = SWS_OP_CLEAR, \
+ .clear_value = 0, \
+ .unused[IDX] = true, \
+ );
+
+static int setup_clear(const SwsOp *op, SwsOpPriv *out)
+{
+ for (int i = 0; i < 4; i++)
+ out->u32[i] = (uint32_t) op->c.q4[i].num;
+ return 0;
+}
+
+#define DECL_CLEAR(EXT, X, Y, Z, W) \
+ DECL_PATTERN(U8, clear##EXT, X, Y, Z, W, \
+ .op = SWS_OP_CLEAR, \
+ .setup = setup_clear, \
+ .flexible = true, \
+ );
+
+#define DECL_SWIZZLE(EXT, X, Y, Z, W) \
+ DECL_ASM(U8, swizzle_##X##Y##Z##W##EXT, \
+ .op = SWS_OP_SWIZZLE, \
+ .swizzle = SWS_SWIZZLE( X, Y, Z, W ), \
+ );
+
+#define DECL_CONVERT(EXT, FROM, TO) \
+ DECL_COMMON_PATTERNS(FROM, convert_##FROM##_##TO##EXT, \
+ .op = SWS_OP_CONVERT, \
+ .convert.to = SWS_PIXEL_##TO, \
+ );
+
+#define DECL_EXPAND(EXT, FROM, TO) \
+ DECL_COMMON_PATTERNS(FROM, expand_##FROM##_##TO##EXT, \
+ .op = SWS_OP_CONVERT, \
+ .convert.to = SWS_PIXEL_##TO, \
+ .convert.expand = true, \
+ );
+
+static int setup_shift(const SwsOp *op, SwsOpPriv *out)
+{
+ out->u16[0] = op->c.u;
+ return 0;
+}
+
+#define DECL_SHIFT16(EXT) \
+ DECL_COMMON_PATTERNS(U16, lshift16##EXT, \
+ .op = SWS_OP_LSHIFT, \
+ .setup = setup_shift, \
+ .flexible = true, \
+ ); \
+ \
+ DECL_COMMON_PATTERNS(U16, rshift16##EXT, \
+ .op = SWS_OP_RSHIFT, \
+ .setup = setup_shift, \
+ .flexible = true, \
+ );
+
+#define DECL_MIN_MAX(EXT) \
+ DECL_COMMON_PATTERNS(F32, min##EXT, \
+ .op = SWS_OP_MIN, \
+ .setup = ff_sws_setup_q4, \
+ .flexible = true, \
+ ); \
+ \
+ DECL_COMMON_PATTERNS(F32, max##EXT, \
+ .op = SWS_OP_MAX, \
+ .setup = ff_sws_setup_q4, \
+ .flexible = true, \
+ );
+
+#define DECL_SCALE(EXT) \
+ DECL_COMMON_PATTERNS(F32, scale##EXT, \
+ .op = SWS_OP_SCALE, \
+ .setup = ff_sws_setup_q, \
+ );
+
+/* 2x2 matrix fits inside SwsOpPriv directly; save an indirect in this case */
+static_assert(sizeof(SwsOpPriv) >= sizeof(float[2][2]), "2x2 dither matrix too large");
+static int setup_dither(const SwsOp *op, SwsOpPriv *out)
+{
+ const int size = 1 << op->dither.size_log2;
+ float *matrix = out->f32;
+ if (size > 2) {
+ matrix = out->ptr = av_mallocz(size * size * sizeof(*matrix));
+ if (!matrix)
+ return AVERROR(ENOMEM);
+ }
+
+ for (int i = 0; i < size * size; i++)
+ matrix[i] = (float) op->dither.matrix[i].num / op->dither.matrix[i].den;
+
+ return 0;
+}
+
+#define DECL_DITHER(EXT, SIZE) \
+ DECL_COMMON_PATTERNS(F32, dither##SIZE##EXT, \
+ .op = SWS_OP_DITHER, \
+ .setup = setup_dither, \
+ .free = SIZE > 2 ? av_free : NULL, \
+ .dither_size = SIZE, \
+ );
+
+static int setup_linear(const SwsOp *op, SwsOpPriv *out)
+{
+ float *matrix = out->ptr = av_mallocz(sizeof(float[4][5]));
+ if (!matrix)
+ return AVERROR(ENOMEM);
+
+ for (int y = 0; y < 4; y++) {
+ for (int x = 0; x < 5; x++)
+ matrix[y * 5 + x] = (float) op->lin.m[y][x].num / op->lin.m[y][x].den;
+ }
+
+ return 0;
+}
+
+#define DECL_LINEAR(EXT, NAME, MASK) \
+ DECL_ASM(F32, NAME##EXT, \
+ .op = SWS_OP_LINEAR, \
+ .setup = setup_linear, \
+ .free = av_free, \
+ .linear_mask = (MASK), \
+ );
+
+#define DECL_FUNCS_8(SIZE, EXT, FLAG) \
+ DECL_RW(EXT, U8, read_planar, READ, 1, false, 0) \
+ DECL_RW(EXT, U8, read_planar, READ, 2, false, 0) \
+ DECL_RW(EXT, U8, read_planar, READ, 3, false, 0) \
+ DECL_RW(EXT, U8, read_planar, READ, 4, false, 0) \
+ DECL_RW(EXT, U8, write_planar, WRITE, 1, false, 0) \
+ DECL_RW(EXT, U8, write_planar, WRITE, 2, false, 0) \
+ DECL_RW(EXT, U8, write_planar, WRITE, 3, false, 0) \
+ DECL_RW(EXT, U8, write_planar, WRITE, 4, false, 0) \
+ DECL_RW(EXT, U8, read_nibbles, READ, 1, false, 1) \
+ DECL_RW(EXT, U8, read_bits, READ, 1, false, 3) \
+ DECL_RW(EXT, U8, write_bits, WRITE, 1, false, 3) \
+ DECL_PACKED_RW(EXT, 8) \
+ DECL_PACK_UNPACK(EXT, U8, 1, 2, 1, 0) \
+ DECL_PACK_UNPACK(EXT, U8, 3, 3, 2, 0) \
+ DECL_PACK_UNPACK(EXT, U8, 2, 3, 3, 0) \
+ void ff_p1000_shuffle##EXT(void); \
+ void ff_p1001_shuffle##EXT(void); \
+ void ff_p1110_shuffle##EXT(void); \
+ void ff_p1111_shuffle##EXT(void); \
+ DECL_SWIZZLE(EXT, 3, 0, 1, 2) \
+ DECL_SWIZZLE(EXT, 3, 0, 2, 1) \
+ DECL_SWIZZLE(EXT, 2, 1, 0, 3) \
+ DECL_SWIZZLE(EXT, 3, 2, 1, 0) \
+ DECL_SWIZZLE(EXT, 3, 1, 0, 2) \
+ DECL_SWIZZLE(EXT, 3, 2, 0, 1) \
+ DECL_SWIZZLE(EXT, 1, 2, 0, 3) \
+ DECL_SWIZZLE(EXT, 1, 0, 2, 3) \
+ DECL_SWIZZLE(EXT, 2, 0, 1, 3) \
+ DECL_SWIZZLE(EXT, 2, 3, 1, 0) \
+ DECL_SWIZZLE(EXT, 2, 1, 3, 0) \
+ DECL_SWIZZLE(EXT, 1, 2, 3, 0) \
+ DECL_SWIZZLE(EXT, 1, 3, 2, 0) \
+ DECL_SWIZZLE(EXT, 0, 2, 1, 3) \
+ DECL_SWIZZLE(EXT, 0, 2, 3, 1) \
+ DECL_SWIZZLE(EXT, 0, 3, 1, 2) \
+ DECL_SWIZZLE(EXT, 3, 1, 2, 0) \
+ DECL_SWIZZLE(EXT, 0, 3, 2, 1) \
+ DECL_SWIZZLE(EXT, 0, 0, 0, 3) \
+ DECL_SWIZZLE(EXT, 3, 0, 0, 0) \
+ DECL_SWIZZLE(EXT, 0, 0, 0, 1) \
+ DECL_SWIZZLE(EXT, 1, 0, 0, 0) \
+ DECL_CLEAR_ALPHA(EXT, 0) \
+ DECL_CLEAR_ALPHA(EXT, 1) \
+ DECL_CLEAR_ALPHA(EXT, 3) \
+ DECL_CLEAR_ZERO(EXT, 0) \
+ DECL_CLEAR_ZERO(EXT, 1) \
+ DECL_CLEAR_ZERO(EXT, 3) \
+ DECL_CLEAR(EXT, 1, 1, 1, 0) \
+ DECL_CLEAR(EXT, 0, 1, 1, 1) \
+ DECL_CLEAR(EXT, 0, 0, 1, 1) \
+ DECL_CLEAR(EXT, 1, 0, 0, 1) \
+ DECL_CLEAR(EXT, 1, 1, 0, 0) \
+ DECL_CLEAR(EXT, 0, 1, 0, 1) \
+ DECL_CLEAR(EXT, 1, 0, 1, 0) \
+ DECL_CLEAR(EXT, 1, 0, 0, 0) \
+ DECL_CLEAR(EXT, 0, 1, 0, 0) \
+ DECL_CLEAR(EXT, 0, 0, 1, 0) \
+ \
+static const SwsOpTable ops8##EXT = { \
+ .cpu_flags = AV_CPU_FLAG_##FLAG, \
+ .block_size = SIZE, \
+ .entries = { \
+ &op_read_planar1##EXT, \
+ &op_read_planar2##EXT, \
+ &op_read_planar3##EXT, \
+ &op_read_planar4##EXT, \
+ &op_write_planar1##EXT, \
+ &op_write_planar2##EXT, \
+ &op_write_planar3##EXT, \
+ &op_write_planar4##EXT, \
+ &op_read8_packed2##EXT, \
+ &op_read8_packed3##EXT, \
+ &op_read8_packed4##EXT, \
+ &op_write8_packed2##EXT, \
+ &op_write8_packed3##EXT, \
+ &op_write8_packed4##EXT, \
+ &op_read_nibbles1##EXT, \
+ &op_read_bits1##EXT, \
+ &op_write_bits1##EXT, \
+ &op_pack_1210##EXT, \
+ &op_pack_3320##EXT, \
+ &op_pack_2330##EXT, \
+ &op_unpack_1210##EXT, \
+ &op_unpack_3320##EXT, \
+ &op_unpack_2330##EXT, \
+ &op_swizzle_3012##EXT, \
+ &op_swizzle_3021##EXT, \
+ &op_swizzle_2103##EXT, \
+ &op_swizzle_3210##EXT, \
+ &op_swizzle_3102##EXT, \
+ &op_swizzle_3201##EXT, \
+ &op_swizzle_1203##EXT, \
+ &op_swizzle_1023##EXT, \
+ &op_swizzle_2013##EXT, \
+ &op_swizzle_2310##EXT, \
+ &op_swizzle_2130##EXT, \
+ &op_swizzle_1230##EXT, \
+ &op_swizzle_1320##EXT, \
+ &op_swizzle_0213##EXT, \
+ &op_swizzle_0231##EXT, \
+ &op_swizzle_0312##EXT, \
+ &op_swizzle_3120##EXT, \
+ &op_swizzle_0321##EXT, \
+ &op_swizzle_0003##EXT, \
+ &op_swizzle_0001##EXT, \
+ &op_swizzle_3000##EXT, \
+ &op_swizzle_1000##EXT, \
+ &op_clear_alpha0##EXT, \
+ &op_clear_alpha1##EXT, \
+ &op_clear_alpha3##EXT, \
+ &op_clear_zero0##EXT, \
+ &op_clear_zero1##EXT, \
+ &op_clear_zero3##EXT, \
+ REF_PATTERN(clear##EXT, 1, 1, 1, 0), \
+ REF_PATTERN(clear##EXT, 0, 1, 1, 1), \
+ REF_PATTERN(clear##EXT, 0, 0, 1, 1), \
+ REF_PATTERN(clear##EXT, 1, 0, 0, 1), \
+ REF_PATTERN(clear##EXT, 1, 1, 0, 0), \
+ REF_PATTERN(clear##EXT, 0, 1, 0, 1), \
+ REF_PATTERN(clear##EXT, 1, 0, 1, 0), \
+ REF_PATTERN(clear##EXT, 1, 0, 0, 0), \
+ REF_PATTERN(clear##EXT, 0, 1, 0, 0), \
+ REF_PATTERN(clear##EXT, 0, 0, 1, 0), \
+ NULL \
+ }, \
+};
+
+#define DECL_FUNCS_16(SIZE, EXT, FLAG) \
+ DECL_PACKED_RW(EXT, 16) \
+ DECL_PACK_UNPACK(EXT, U16, 4, 4, 4, 0) \
+ DECL_PACK_UNPACK(EXT, U16, 5, 5, 5, 0) \
+ DECL_PACK_UNPACK(EXT, U16, 5, 6, 5, 0) \
+ DECL_SWAP_BYTES(EXT, U16, 1, 0, 0, 0) \
+ DECL_SWAP_BYTES(EXT, U16, 1, 0, 0, 1) \
+ DECL_SWAP_BYTES(EXT, U16, 1, 1, 1, 0) \
+ DECL_SWAP_BYTES(EXT, U16, 1, 1, 1, 1) \
+ DECL_SHIFT16(EXT) \
+ DECL_CONVERT(EXT, U8, U16) \
+ DECL_CONVERT(EXT, U16, U8) \
+ DECL_EXPAND(EXT, U8, U16) \
+ \
+static const SwsOpTable ops16##EXT = { \
+ .cpu_flags = AV_CPU_FLAG_##FLAG, \
+ .block_size = SIZE, \
+ .entries = { \
+ &op_read16_packed2##EXT, \
+ &op_read16_packed3##EXT, \
+ &op_read16_packed4##EXT, \
+ &op_write16_packed2##EXT, \
+ &op_write16_packed3##EXT, \
+ &op_write16_packed4##EXT, \
+ &op_pack_4440##EXT, \
+ &op_pack_5550##EXT, \
+ &op_pack_5650##EXT, \
+ &op_unpack_4440##EXT, \
+ &op_unpack_5550##EXT, \
+ &op_unpack_5650##EXT, \
+ REF_COMMON_PATTERNS(swap_bytes_U16##EXT), \
+ REF_COMMON_PATTERNS(convert_U8_U16##EXT), \
+ REF_COMMON_PATTERNS(convert_U16_U8##EXT), \
+ REF_COMMON_PATTERNS(expand_U8_U16##EXT), \
+ REF_COMMON_PATTERNS(lshift16##EXT), \
+ REF_COMMON_PATTERNS(rshift16##EXT), \
+ NULL \
+ }, \
+};
+
+#define DECL_FUNCS_32(SIZE, EXT, FLAG) \
+ DECL_PACKED_RW(_m2##EXT, 32) \
+ DECL_PACK_UNPACK(_m2##EXT, U32, 10, 10, 10, 2) \
+ DECL_PACK_UNPACK(_m2##EXT, U32, 2, 10, 10, 10) \
+ DECL_SWAP_BYTES(_m2##EXT, U32, 1, 0, 0, 0) \
+ DECL_SWAP_BYTES(_m2##EXT, U32, 1, 0, 0, 1) \
+ DECL_SWAP_BYTES(_m2##EXT, U32, 1, 1, 1, 0) \
+ DECL_SWAP_BYTES(_m2##EXT, U32, 1, 1, 1, 1) \
+ DECL_CONVERT(EXT, U8, U32) \
+ DECL_CONVERT(EXT, U32, U8) \
+ DECL_CONVERT(EXT, U16, U32) \
+ DECL_CONVERT(EXT, U32, U16) \
+ DECL_CONVERT(EXT, U8, F32) \
+ DECL_CONVERT(EXT, F32, U8) \
+ DECL_CONVERT(EXT, U16, F32) \
+ DECL_CONVERT(EXT, F32, U16) \
+ DECL_EXPAND(EXT, U8, U32) \
+ DECL_MIN_MAX(EXT) \
+ DECL_SCALE(EXT) \
+ DECL_DITHER(EXT, 0) \
+ DECL_DITHER(EXT, 1) \
+ DECL_DITHER(EXT, 2) \
+ DECL_DITHER(EXT, 3) \
+ DECL_DITHER(EXT, 4) \
+ DECL_DITHER(EXT, 5) \
+ DECL_DITHER(EXT, 6) \
+ DECL_DITHER(EXT, 7) \
+ DECL_DITHER(EXT, 8) \
+ DECL_LINEAR(EXT, luma, SWS_MASK_LUMA) \
+ DECL_LINEAR(EXT, alpha, SWS_MASK_ALPHA) \
+ DECL_LINEAR(EXT, lumalpha, SWS_MASK_LUMA | SWS_MASK_ALPHA) \
+ DECL_LINEAR(EXT, dot3, 0x7) \
+ DECL_LINEAR(EXT, row0, SWS_MASK_ROW(0)) \
+ DECL_LINEAR(EXT, row0a, SWS_MASK_ROW(0) | SWS_MASK_ALPHA) \
+ DECL_LINEAR(EXT, diag3, SWS_MASK_DIAG3) \
+ DECL_LINEAR(EXT, diag4, SWS_MASK_DIAG4) \
+ DECL_LINEAR(EXT, diagoff3, SWS_MASK_DIAG3 | SWS_MASK_OFF3) \
+ DECL_LINEAR(EXT, matrix3, SWS_MASK_MAT3) \
+ DECL_LINEAR(EXT, affine3, SWS_MASK_MAT3 | SWS_MASK_OFF3) \
+ DECL_LINEAR(EXT, affine3a, SWS_MASK_MAT3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA) \
+ DECL_LINEAR(EXT, matrix4, SWS_MASK_MAT4) \
+ DECL_LINEAR(EXT, affine4, SWS_MASK_MAT4 | SWS_MASK_OFF4) \
+ \
+static const SwsOpTable ops32##EXT = { \
+ .cpu_flags = AV_CPU_FLAG_##FLAG, \
+ .block_size = SIZE, \
+ .entries = { \
+ &op_read32_packed2_m2##EXT, \
+ &op_read32_packed3_m2##EXT, \
+ &op_read32_packed4_m2##EXT, \
+ &op_write32_packed2_m2##EXT, \
+ &op_write32_packed3_m2##EXT, \
+ &op_write32_packed4_m2##EXT, \
+ &op_pack_1010102_m2##EXT, \
+ &op_pack_2101010_m2##EXT, \
+ &op_unpack_1010102_m2##EXT, \
+ &op_unpack_2101010_m2##EXT, \
+ REF_COMMON_PATTERNS(swap_bytes_U32_m2##EXT), \
+ REF_COMMON_PATTERNS(convert_U8_U32##EXT), \
+ REF_COMMON_PATTERNS(convert_U32_U8##EXT), \
+ REF_COMMON_PATTERNS(convert_U16_U32##EXT), \
+ REF_COMMON_PATTERNS(convert_U32_U16##EXT), \
+ REF_COMMON_PATTERNS(convert_U8_F32##EXT), \
+ REF_COMMON_PATTERNS(convert_F32_U8##EXT), \
+ REF_COMMON_PATTERNS(convert_U16_F32##EXT), \
+ REF_COMMON_PATTERNS(convert_F32_U16##EXT), \
+ REF_COMMON_PATTERNS(expand_U8_U32##EXT), \
+ REF_COMMON_PATTERNS(min##EXT), \
+ REF_COMMON_PATTERNS(max##EXT), \
+ REF_COMMON_PATTERNS(scale##EXT), \
+ REF_COMMON_PATTERNS(dither0##EXT), \
+ REF_COMMON_PATTERNS(dither1##EXT), \
+ REF_COMMON_PATTERNS(dither2##EXT), \
+ REF_COMMON_PATTERNS(dither3##EXT), \
+ REF_COMMON_PATTERNS(dither4##EXT), \
+ REF_COMMON_PATTERNS(dither5##EXT), \
+ REF_COMMON_PATTERNS(dither6##EXT), \
+ REF_COMMON_PATTERNS(dither7##EXT), \
+ REF_COMMON_PATTERNS(dither8##EXT), \
+ &op_luma##EXT, \
+ &op_alpha##EXT, \
+ &op_lumalpha##EXT, \
+ &op_dot3##EXT, \
+ &op_row0##EXT, \
+ &op_row0a##EXT, \
+ &op_diag3##EXT, \
+ &op_diag4##EXT, \
+ &op_diagoff3##EXT, \
+ &op_matrix3##EXT, \
+ &op_affine3##EXT, \
+ &op_affine3a##EXT, \
+ &op_matrix4##EXT, \
+ &op_affine4##EXT, \
+ NULL \
+ }, \
+};
+
+DECL_FUNCS_8(16, _m1_sse4, SSE4)
+DECL_FUNCS_8(32, _m1_avx2, AVX2)
+DECL_FUNCS_8(32, _m2_sse4, SSE4)
+DECL_FUNCS_8(64, _m2_avx2, AVX2)
+
+DECL_FUNCS_16(16, _m1_avx2, AVX2)
+DECL_FUNCS_16(32, _m2_avx2, AVX2)
+
+DECL_FUNCS_32(16, _avx2, AVX2)
+
+static av_const int get_mmsize(const int cpu_flags)
+{
+ if (cpu_flags & AV_CPU_FLAG_AVX512)
+ return 64;
+ else if (cpu_flags & AV_CPU_FLAG_AVX2)
+ return 32;
+ else if (cpu_flags & AV_CPU_FLAG_SSE4)
+ return 16;
+ else
+ return AVERROR(ENOTSUP);
+}
+
+/**
+ * Returns true if the operation's implementation only depends on the block
+ * size, and not the underlying pixel type
+ */
+static bool op_is_type_invariant(const SwsOp *op)
+{
+ switch (op->op) {
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ return !op->rw.packed && !op->rw.frac;
+ case SWS_OP_SWIZZLE:
+ case SWS_OP_CLEAR:
+ return true;
+ }
+
+ return false;
+}
+
+static int solve_shuffle(const SwsOpList *ops, int mmsize, SwsCompiledOp *out)
+{
+ uint8_t shuffle[16];
+ int read_bytes, write_bytes;
+ int pixels;
+
+ /* Solve the shuffle mask for one 128-bit lane only */
+ pixels = ff_sws_solve_shuffle(ops, shuffle, 16, 0x80, &read_bytes, &write_bytes);
+ if (pixels < 0)
+ return pixels;
+
+ /* We can't shuffle acress lanes, so restrict the vector size to XMM
+ * whenever the read/write size would be a subset of the full vector */
+ if (read_bytes < 16 || write_bytes < 16)
+ mmsize = 16;
+
+ const int num_lanes = mmsize / 16;
+ const int in_total = num_lanes * read_bytes;
+ const int out_total = num_lanes * write_bytes;
+ const int read_size = in_total <= 4 ? 4 : /* movd */
+ in_total <= 8 ? 8 : /* movq */
+ mmsize; /* movu */
+
+ *out = (SwsCompiledOp) {
+ .priv = av_memdup(shuffle, sizeof(shuffle)),
+ .free = av_free,
+ .block_size = pixels * num_lanes,
+ .over_read = read_size - in_total,
+ .over_write = mmsize - out_total,
+ .cpu_flags = mmsize > 32 ? AV_CPU_FLAG_AVX512 :
+ mmsize > 16 ? AV_CPU_FLAG_AVX2 :
+ AV_CPU_FLAG_SSE4,
+ };
+
+ if (!out->priv)
+ return AVERROR(ENOMEM);
+
+#define ASSIGN_SHUFFLE_FUNC(IN, OUT, EXT) \
+do { \
+ SWS_DECL_FUNC(ff_packed_shuffle##IN##_##OUT##_##EXT); \
+ if (in_total == IN && out_total == OUT) \
+ out->func = ff_packed_shuffle##IN##_##OUT##_##EXT; \
+} while (0)
+
+ ASSIGN_SHUFFLE_FUNC( 5, 15, sse4);
+ ASSIGN_SHUFFLE_FUNC( 4, 16, sse4);
+ ASSIGN_SHUFFLE_FUNC( 2, 12, sse4);
+ ASSIGN_SHUFFLE_FUNC(16, 8, sse4);
+ ASSIGN_SHUFFLE_FUNC(10, 15, sse4);
+ ASSIGN_SHUFFLE_FUNC( 8, 16, sse4);
+ ASSIGN_SHUFFLE_FUNC( 4, 12, sse4);
+ ASSIGN_SHUFFLE_FUNC(15, 15, sse4);
+ ASSIGN_SHUFFLE_FUNC(12, 16, sse4);
+ ASSIGN_SHUFFLE_FUNC( 6, 12, sse4);
+ ASSIGN_SHUFFLE_FUNC(16, 12, sse4);
+ ASSIGN_SHUFFLE_FUNC(16, 16, sse4);
+ ASSIGN_SHUFFLE_FUNC( 8, 12, sse4);
+ ASSIGN_SHUFFLE_FUNC(12, 12, sse4);
+ ASSIGN_SHUFFLE_FUNC(32, 32, avx2);
+ ASSIGN_SHUFFLE_FUNC(64, 64, avx512);
+ av_assert1(out->func);
+ return 0;
+}
+
+/* Normalize clear values into 32-bit integer constants */
+static void normalize_clear(SwsOp *op)
+{
+ static_assert(sizeof(uint32_t) == sizeof(int), "int size mismatch");
+ SwsOpPriv priv;
+ union {
+ uint32_t u32;
+ int i;
+ } c;
+
+ ff_sws_setup_q4(op, &priv);
+ for (int i = 0; i < 4; i++) {
+ if (!op->c.q4[i].den)
+ continue;
+ switch (ff_sws_pixel_type_size(op->type)) {
+ case 1: c.u32 = 0x1010101 * priv.u8[i]; break;
+ case 2: c.u32 = priv.u16[i] << 16 | priv.u16[i]; break;
+ case 4: c.u32 = priv.u32[i]; break;
+ }
+
+ op->c.q4[i].num = c.i;
+ op->c.q4[i].den = 1;
+ }
+}
+
+static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out)
+{
+ const int cpu_flags = av_get_cpu_flags();
+ const int mmsize = get_mmsize(cpu_flags);
+ if (mmsize < 0)
+ return mmsize;
+
+ av_assert1(ops->num_ops > 0);
+ const SwsOp read = ops->ops[0];
+ const SwsOp write = ops->ops[ops->num_ops - 1];
+ int ret;
+
+ /* Special fast path for in-place packed shuffle */
+ ret = solve_shuffle(ops, mmsize, out);
+ if (ret != AVERROR(ENOTSUP))
+ return ret;
+
+ SwsOpChain *chain = ff_sws_op_chain_alloc();
+ if (!chain)
+ return AVERROR(ENOMEM);
+
+ *out = (SwsCompiledOp) {
+ .priv = chain,
+ .free = (void (*)(void *)) ff_sws_op_chain_free,
+
+ /* Use at most two full YMM regs during the widest precision section */
+ .block_size = 2 * FFMIN(mmsize, 32) / ff_sws_op_list_max_size(ops),
+ };
+
+ /* 3-component reads/writes process one extra garbage word */
+ if (read.rw.packed && read.rw.elems == 3)
+ out->over_read = sizeof(uint32_t);
+ if (write.rw.packed && write.rw.elems == 3)
+ out->over_write = sizeof(uint32_t);
+
+ static const SwsOpTable *const tables[] = {
+ &ops8_m1_sse4,
+ &ops8_m1_avx2,
+ &ops8_m2_sse4,
+ &ops8_m2_avx2,
+ &ops16_m1_avx2,
+ &ops16_m2_avx2,
+ &ops32_avx2,
+ };
+
+ do {
+ int op_block_size = out->block_size;
+ SwsOp *op = &ops->ops[0];
+
+ if (op_is_type_invariant(op)) {
+ if (op->op == SWS_OP_CLEAR)
+ normalize_clear(op);
+ op_block_size *= ff_sws_pixel_type_size(op->type);
+ op->type = SWS_PIXEL_U8;
+ }
+
+ ret = ff_sws_op_compile_tables(tables, FF_ARRAY_ELEMS(tables), ops,
+ op_block_size, chain);
+ } while (ret == AVERROR(EAGAIN));
+ if (ret < 0) {
+ ff_sws_op_chain_free(chain);
+ return ret;
+ }
+
+#define ASSIGN_PROCESS_FUNC(NAME) \
+ do { \
+ SWS_DECL_FUNC(NAME); \
+ void NAME##_return(void); \
+ ret = ff_sws_op_chain_append(chain, NAME##_return, \
+ NULL, (SwsOpPriv) {0}); \
+ out->func = NAME; \
+ } while (0)
+
+ const int read_planes = read.rw.packed ? 1 : read.rw.elems;
+ const int write_planes = write.rw.packed ? 1 : write.rw.elems;
+ switch (FFMAX(read_planes, write_planes)) {
+ case 1: ASSIGN_PROCESS_FUNC(ff_sws_process1_x86); break;
+ case 2: ASSIGN_PROCESS_FUNC(ff_sws_process2_x86); break;
+ case 3: ASSIGN_PROCESS_FUNC(ff_sws_process3_x86); break;
+ case 4: ASSIGN_PROCESS_FUNC(ff_sws_process4_x86); break;
+ }
+
+ if (ret < 0) {
+ ff_sws_op_chain_free(chain);
+ return ret;
+ }
+
+ out->cpu_flags = chain->cpu_flags;
+ return 0;
+}
+
+const SwsOpBackend backend_x86 = {
+ .name = "x86",
+ .compile = compile,
+};
diff --git a/libswscale/x86/ops_common.asm b/libswscale/x86/ops_common.asm
new file mode 100644
index 0000000000..3c9154584a
--- /dev/null
+++ b/libswscale/x86/ops_common.asm
@@ -0,0 +1,305 @@
+;******************************************************************************
+;* Copyright (c) 2025 Niklas Haas
+;*
+;* 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 "libavutil/x86/x86util.asm"
+
+; High-level explanation of how the x86 backend works:
+;
+; sws_processN is the shared entry point for all operation chains. This
+; function is responsible for the block loop, as well as initializing the
+; plane pointers. It will jump directly into the first operation kernel,
+; and each operation kernel will jump directly into the next one, with the
+; final kernel jumping back into the sws_process return point. (See label
+; `sws_process.return` in ops_int.asm)
+;
+; To handle the jump back to the return point, we append an extra address
+; corresponding to the correct sws_process.return label into the SwsOpChain,
+; and have the WRITE kernel jump into it as usual. (See the FINISH macro)
+;
+; Inside an operation chain, we use a custom calling convention to preserve
+; registers between kernels. The exact register allocation is found further
+; below in this file, but we basically reserve (and share) the following
+; registers:
+;
+; - const execq (read-only, shared execution data, see SwsOpExec); stores the
+; static metadata for this call and describes the image layouts
+;
+; - implq (read-only, operation chain, see SwsOpChain); stores the private data
+; for each operation as well as the pointer to the next kernel in the sequence.
+; This register is automatically incremented by the CONTINUE macro, and will
+; be reset back to the first operation kernel by sws_process.
+;
+; - bxd, yd: current line and block number, used as loop counters in sws_process.
+; Also used by e.g. the dithering code to do position-dependent dithering.
+;
+; - tmp0, tmp1: two temporary registers which are NOT preserved between kernels
+;
+; - inNq, outNq: plane pointers. These are incremented automatically after the
+; corresponding read/write operation, by the read/write kernels themselves.
+; sws_process will take care of resetting these to the next line after the
+; block loop is done.
+;
+; Additionally, we pass data between kernels by directly keeping them inside
+; vector registers. For this, we reserve the following registers:
+;
+; - mx, my, mz, mw: low half of the X, Y, Z and W components
+; - mx2, my2, mz2, mw2: high half of the X, Y, Z and W components
+; (As well as sized variants for xmx, ymx, etc.)
+;
+; The "high half" registers are only sometimes used; in order to enable
+; processing more pixels at the same time. See `decl_v2` below, which allows
+; assembling the same operation twice, once with only the lower half (V2=0),
+; and once with both halves (V2=1). The remaining vectors are free for use
+; inside operation kernels, starting from m8.
+;
+; The basic rule is that we always use the full set of both vector registers
+; when processing the largest element size within a pixel chain. For example,
+; if we load 8-bit values and convert them to 32-bit floats internally, then
+; we would have an operation chain which combines an SSE4 V2=0 u8 kernel (128
+; bits = 16 pixels) with an AVX2 V2=1 f32 kernel (512 bits = 16 pixels). This
+; keeps the number of pixels being processed (the block size) constant. The
+; V2 setting is suffixed to the operation name (_m1 or _m2) during name
+; mangling.
+;
+; This design leaves us with the following set of possibilities:
+;
+; SSE4:
+; - max element is 32-bit: currently unsupported
+; - max element is 16-bit: currently unsupported
+; - max element is 8-bit: block size 32, u8_m2_sse4
+;
+; AVX2:
+; - max element is 32-bit: block size 16, u32_m2_avx2, u16_m1_avx2, u8_m1_sse4
+; - max element is 16-bit: block size 32, u16_m2_avx2, u8_m1_avx2
+; - max element is 8-bit: block size 64, u8_m2_avx2
+;
+; Meaning we need to cover the following code paths for each bit depth:
+;
+; - 8-bit kernels: m1_sse4, m2_sse4, m1_avx2, m2_avx2
+; - 16-bit kernels: m1_avx2, m2_avx2
+; - 32-bit kernels: m2_avx2
+;
+; This is achieved by macro'ing each operation kernel and declaring it once
+; per SIMD version, and (if needed) once per V2 setting using decl_v2. (See
+; the bottom of ops_int.asm for an example)
+;
+; Finally, we overload some operation kernel to different number of components,
+; using the `decl_pattern` and `decl_common_patterns` macros. Inside these
+; kernels, the variables X, Y, Z and W will be set to 0 or 1 respectively,
+; depending on which components are active for this particular kernel instance.
+; They will receive the _pXYZW prefix during name mangling.
+
+struc SwsOpExec
+ .in0 resq 1
+ .in1 resq 1
+ .in2 resq 1
+ .in3 resq 1
+ .out0 resq 1
+ .out1 resq 1
+ .out2 resq 1
+ .out3 resq 1
+ .in_stride0 resq 1
+ .in_stride1 resq 1
+ .in_stride2 resq 1
+ .in_stride3 resq 1
+ .out_stride0 resq 1
+ .out_stride1 resq 1
+ .out_stride2 resq 1
+ .out_stride3 resq 1
+ .in_bump0 resq 1
+ .in_bump1 resq 1
+ .in_bump2 resq 1
+ .in_bump3 resq 1
+ .out_bump0 resq 1
+ .out_bump1 resq 1
+ .out_bump2 resq 1
+ .out_bump3 resq 1
+ .width resd 1
+ .height resd 1
+ .slice_y resd 1
+ .slice_h resd 1
+ .block_size_in resd 1
+ .block_size_out resd 1
+endstruc
+
+struc SwsOpImpl
+ .cont resb 16
+ .priv resb 16
+ .next resb 0
+endstruc
+
+;---------------------------------------------------------
+; Common macros for declaring operations
+
+; Declare an operation kernel with the correct name mangling.
+%macro op 1 ; name
+ %ifdef X
+ %define ADD_PAT(name) p %+ X %+ Y %+ Z %+ W %+ _ %+ name
+ %else
+ %define ADD_PAT(name) name
+ %endif
+
+ %ifdef V2
+ %if V2
+ %define ADD_MUL(name) name %+ _m2
+ %else
+ %define ADD_MUL(name) name %+ _m1
+ %endif
+ %else
+ %define ADD_MUL(name) name
+ %endif
+
+ cglobal ADD_PAT(ADD_MUL(%1)), 0, 0, 0 ; already allocated by entry point
+
+ %undef ADD_PAT
+ %undef ADD_MUL
+%endmacro
+
+; Declare an operation kernel twice, once with V2=0 and once with V2=1
+%macro decl_v2 2+ ; v2, func
+ %xdefine V2 %1
+ %2
+ %undef V2
+%endmacro
+
+; Declare an operation kernel specialized to a given subset of active components
+%macro decl_pattern 5+ ; X, Y, Z, W, func
+ %xdefine X %1
+ %xdefine Y %2
+ %xdefine Z %3
+ %xdefine W %4
+ %5
+ %undef X
+ %undef Y
+ %undef Z
+ %undef W
+%endmacro
+
+; Declare an operation kernel specialized to each common component pattern
+%macro decl_common_patterns 1+ ; func
+ decl_pattern 1, 0, 0, 0, %1 ; y
+ decl_pattern 1, 0, 0, 1, %1 ; ya
+ decl_pattern 1, 1, 1, 0, %1 ; yuv
+ decl_pattern 1, 1, 1, 1, %1 ; yuva
+%endmacro
+
+;---------------------------------------------------------
+; Common names for the internal calling convention
+%define mx m0
+%define my m1
+%define mz m2
+%define mw m3
+
+%define xmx xm0
+%define xmy xm1
+%define xmz xm2
+%define xmw xm3
+
+%define ymx ym0
+%define ymy ym1
+%define ymz ym2
+%define ymw ym3
+
+%define mx2 m4
+%define my2 m5
+%define mz2 m6
+%define mw2 m7
+
+%define xmx2 xm4
+%define xmy2 xm5
+%define xmz2 xm6
+%define xmw2 xm7
+
+%define ymx2 ym4
+%define ymy2 ym5
+%define ymz2 ym6
+%define ymw2 ym7
+
+; Reserved in this order by the signature of SwsOpFunc
+%define execq r0q
+%define implq r1q
+%define bxd r2d
+%define yd r3d
+
+; Extra registers for free use by kernels, not saved between ops
+%define tmp0q r4q
+%define tmp1q r5q
+
+%define tmp0d r4d
+%define tmp1d r5d
+
+; Registers for plane pointers; put at the end (and in ascending plane order)
+; so that we can avoid reserving them when not necessary
+%define out0q r6q
+%define in0q r7q
+%define out1q r8q
+%define in1q r9q
+%define out2q r10q
+%define in2q r11q
+%define out3q r12q
+%define in3q r13q
+
+;---------------------------------------------------------
+; Common macros for linking together different kernels
+
+; Load the next operation kernel's address to a register
+%macro LOAD_CONT 1 ; reg
+ mov %1, [implq + SwsOpImpl.cont]
+%endmacro
+
+; Tail call into the next operation kernel, given that kernel's address
+%macro CONTINUE 1 ; reg
+ add implq, SwsOpImpl.next
+ jmp %1
+ annotate_function_size
+%endmacro
+
+; Convenience macro to load and continue to the next kernel in one step
+%macro CONTINUE 0
+ LOAD_CONT tmp0q
+ CONTINUE tmp0q
+%endmacro
+
+; Final macro to end the operation chain, used by WRITE kernels to jump back
+; to the process function return point. Very similar to CONTINUE, but skips
+; incrementing the implq pointer, and also clears AVX registers to avoid
+; phantom dependencies between loop iterations.
+%macro FINISH 1 ; reg
+ %if vzeroupper_required
+ ; we may jump back into an SSE read, so always zero upper regs here
+ vzeroupper
+ %endif
+ jmp %1
+ annotate_function_size
+%endmacro
+
+; Helper for inline conditionals; used to conditionally include single lines
+%macro IF 2+ ; cond, body
+ %if %1
+ %2
+ %endif
+%endmacro
+
+; Alternate name; for nested usage (to work around NASM limitations)
+%macro IF1 2+
+ %if %1
+ %2
+ %endif
+%endmacro
diff --git a/libswscale/x86/ops_float.asm b/libswscale/x86/ops_float.asm
new file mode 100644
index 0000000000..6bdbd1b74e
--- /dev/null
+++ b/libswscale/x86/ops_float.asm
@@ -0,0 +1,389 @@
+;******************************************************************************
+;* Copyright (c) 2025 Niklas Haas
+;*
+;* 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 "ops_common.asm"
+
+SECTION .text
+
+;---------------------------------------------------------
+; Pixel type conversions
+
+%macro conv8to32f 0
+op convert_U8_F32
+ LOAD_CONT tmp0q
+IF X, vpsrldq xmx2, xmx, 8
+IF Y, vpsrldq xmy2, xmy, 8
+IF Z, vpsrldq xmz2, xmz, 8
+IF W, vpsrldq xmw2, xmw, 8
+IF X, pmovzxbd mx, xmx
+IF Y, pmovzxbd my, xmy
+IF Z, pmovzxbd mz, xmz
+IF W, pmovzxbd mw, xmw
+IF X, pmovzxbd mx2, xmx2
+IF Y, pmovzxbd my2, xmy2
+IF Z, pmovzxbd mz2, xmz2
+IF W, pmovzxbd mw2, xmw2
+IF X, vcvtdq2ps mx, mx
+IF Y, vcvtdq2ps my, my
+IF Z, vcvtdq2ps mz, mz
+IF W, vcvtdq2ps mw, mw
+IF X, vcvtdq2ps mx2, mx2
+IF Y, vcvtdq2ps my2, my2
+IF Z, vcvtdq2ps mz2, mz2
+IF W, vcvtdq2ps mw2, mw2
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv16to32f 0
+op convert_U16_F32
+ LOAD_CONT tmp0q
+IF X, vextracti128 xmx2, mx, 1
+IF Y, vextracti128 xmy2, my, 1
+IF Z, vextracti128 xmz2, mz, 1
+IF W, vextracti128 xmw2, mw, 1
+IF X, pmovzxwd mx, xmx
+IF Y, pmovzxwd my, xmy
+IF Z, pmovzxwd mz, xmz
+IF W, pmovzxwd mw, xmw
+IF X, pmovzxwd mx2, xmx2
+IF Y, pmovzxwd my2, xmy2
+IF Z, pmovzxwd mz2, xmz2
+IF W, pmovzxwd mw2, xmw2
+IF X, vcvtdq2ps mx, mx
+IF Y, vcvtdq2ps my, my
+IF Z, vcvtdq2ps mz, mz
+IF W, vcvtdq2ps mw, mw
+IF X, vcvtdq2ps mx2, mx2
+IF Y, vcvtdq2ps my2, my2
+IF Z, vcvtdq2ps mz2, mz2
+IF W, vcvtdq2ps mw2, mw2
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv32fto8 0
+op convert_F32_U8
+ LOAD_CONT tmp0q
+IF X, cvttps2dq mx, mx
+IF Y, cvttps2dq my, my
+IF Z, cvttps2dq mz, mz
+IF W, cvttps2dq mw, mw
+IF X, cvttps2dq mx2, mx2
+IF Y, cvttps2dq my2, my2
+IF Z, cvttps2dq mz2, mz2
+IF W, cvttps2dq mw2, mw2
+IF X, packusdw mx, mx2
+IF Y, packusdw my, my2
+IF Z, packusdw mz, mz2
+IF W, packusdw mw, mw2
+IF X, vextracti128 xmx2, mx, 1
+IF Y, vextracti128 xmy2, my, 1
+IF Z, vextracti128 xmz2, mz, 1
+IF W, vextracti128 xmw2, mw, 1
+ vzeroupper
+IF X, packuswb xmx, xmx2
+IF Y, packuswb xmy, xmy2
+IF Z, packuswb xmz, xmz2
+IF W, packuswb xmw, xmw2
+IF X, vpshufd xmx, xmx, q3120
+IF Y, vpshufd xmy, xmy, q3120
+IF Z, vpshufd xmz, xmz, q3120
+IF W, vpshufd xmw, xmw, q3120
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv32fto16 0
+op convert_F32_U16
+ LOAD_CONT tmp0q
+IF X, cvttps2dq mx, mx
+IF Y, cvttps2dq my, my
+IF Z, cvttps2dq mz, mz
+IF W, cvttps2dq mw, mw
+IF X, cvttps2dq mx2, mx2
+IF Y, cvttps2dq my2, my2
+IF Z, cvttps2dq mz2, mz2
+IF W, cvttps2dq mw2, mw2
+IF X, packusdw mx, mx2
+IF Y, packusdw my, my2
+IF Z, packusdw mz, mz2
+IF W, packusdw mw, mw2
+IF X, vpermq mx, mx, q3120
+IF Y, vpermq my, my, q3120
+IF Z, vpermq mz, mz, q3120
+IF W, vpermq mw, mw, q3120
+ CONTINUE tmp0q
+%endmacro
+
+%macro min_max 0
+op min
+IF X, vbroadcastss m8, [implq + SwsOpImpl.priv + 0]
+IF Y, vbroadcastss m9, [implq + SwsOpImpl.priv + 4]
+IF Z, vbroadcastss m10, [implq + SwsOpImpl.priv + 8]
+IF W, vbroadcastss m11, [implq + SwsOpImpl.priv + 12]
+ LOAD_CONT tmp0q
+IF X, minps mx, mx, m8
+IF Y, minps my, my, m9
+IF Z, minps mz, mz, m10
+IF W, minps mw, mw, m11
+IF X, minps mx2, m8
+IF Y, minps my2, m9
+IF Z, minps mz2, m10
+IF W, minps mw2, m11
+ CONTINUE tmp0q
+
+op max
+IF X, vbroadcastss m8, [implq + SwsOpImpl.priv + 0]
+IF Y, vbroadcastss m9, [implq + SwsOpImpl.priv + 4]
+IF Z, vbroadcastss m10, [implq + SwsOpImpl.priv + 8]
+IF W, vbroadcastss m11, [implq + SwsOpImpl.priv + 12]
+ LOAD_CONT tmp0q
+IF X, maxps mx, m8
+IF Y, maxps my, m9
+IF Z, maxps mz, m10
+IF W, maxps mw, m11
+IF X, maxps mx2, m8
+IF Y, maxps my2, m9
+IF Z, maxps mz2, m10
+IF W, maxps mw2, m11
+ CONTINUE tmp0q
+%endmacro
+
+%macro scale 0
+op scale
+ vbroadcastss m8, [implq + SwsOpImpl.priv]
+ LOAD_CONT tmp0q
+IF X, mulps mx, m8
+IF Y, mulps my, m8
+IF Z, mulps mz, m8
+IF W, mulps mw, m8
+IF X, mulps mx2, m8
+IF Y, mulps my2, m8
+IF Z, mulps mz2, m8
+IF W, mulps mw2, m8
+ CONTINUE tmp0q
+%endmacro
+
+%macro load_dither_row 5 ; size_log2, y, addr, out, out2
+ lea tmp0q, %2
+ and tmp0q, (1 << %1) - 1
+ shl tmp0q, %1+2
+%if %1 == 2
+ VBROADCASTI128 %4, [%3 + tmp0q]
+%else
+ mova %4, [%3 + tmp0q]
+ %if (4 << %1) > mmsize
+ mova %5, [%3 + tmp0q + mmsize]
+ %endif
+%endif
+%endmacro
+
+%macro dither 1 ; size_log2
+op dither%1
+ %define DX m8
+ %define DY m9
+ %define DZ m10
+ %define DW m11
+ %define DX2 DX
+ %define DY2 DY
+ %define DZ2 DZ
+ %define DW2 DW
+%if %1 == 0
+ ; constant offset for all channels
+ vbroadcastss DX, [implq + SwsOpImpl.priv]
+ %define DY DX
+ %define DZ DX
+ %define DW DX
+%elif %1 == 1
+ ; 2x2 matrix, only sign of y matters
+ mov tmp0d, yd
+ and tmp0d, 1
+ shl tmp0d, 3
+ %if X || Z
+ ; dither matrix is stored directly in the private data
+ vbroadcastsd DX, [implq + SwsOpImpl.priv + tmp0q]
+ %endif
+ %if Y || W
+ xor tmp0d, 8
+ vbroadcastsd DY, [implq + SwsOpImpl.priv + tmp0q]
+ %endif
+ %define DZ DX
+ %define DW DY
+%else
+ ; matrix is at least 4x4, load all four channels with custom offset
+ %if (4 << %1) > mmsize
+ %define DX2 m12
+ %define DY2 m13
+ %define DZ2 m14
+ %define DW2 m15
+ %endif
+ ; dither matrix is stored indirectly at the private data address
+ mov tmp1q, [implq + SwsOpImpl.priv]
+ %if (4 << %1) > 2 * mmsize
+ ; need to add in x offset
+ mov tmp0d, bxd
+ shl tmp0d, 6 ; sizeof(float[16])
+ and tmp0d, (4 << %1) - 1
+ add tmp1q, tmp0q
+ %endif
+IF X, load_dither_row %1, [yd + 0], tmp1q, DX, DX2
+IF Y, load_dither_row %1, [yd + 3], tmp1q, DY, DY2
+IF Z, load_dither_row %1, [yd + 2], tmp1q, DZ, DZ2
+IF W, load_dither_row %1, [yd + 5], tmp1q, DW, DW2
+%endif
+ LOAD_CONT tmp0q
+IF X, addps mx, DX
+IF Y, addps my, DY
+IF Z, addps mz, DZ
+IF W, addps mw, DW
+IF X, addps mx2, DX2
+IF Y, addps my2, DY2
+IF Z, addps mz2, DZ2
+IF W, addps mw2, DW2
+ CONTINUE tmp0q
+%endmacro
+
+%macro dither_fns 0
+ dither 0
+ dither 1
+ dither 2
+ dither 3
+ dither 4
+ dither 5
+ dither 6
+ dither 7
+ dither 8
+%endmacro
+
+%xdefine MASK(I, J) (1 << (5 * (I) + (J)))
+%xdefine MASK_OFF(I) MASK(I, 4)
+%xdefine MASK_ROW(I) (0x1F << (5 * (I)))
+%xdefine MASK_COL(J) (0x8421 << J)
+%xdefine MASK_ALL (1 << 20) - 1
+%xdefine MASK_LUMA MASK(0, 0) | MASK_OFF(0)
+%xdefine MASK_ALPHA MASK(3, 3) | MASK_OFF(3)
+%xdefine MASK_DIAG3 MASK(0, 0) | MASK(1, 1) | MASK(2, 2)
+%xdefine MASK_OFF3 MASK_OFF(0) | MASK_OFF(1) | MASK_OFF(2)
+%xdefine MASK_MAT3 MASK(0, 0) | MASK(0, 1) | MASK(0, 2) |\
+ MASK(1, 0) | MASK(1, 1) | MASK(1, 2) |\
+ MASK(2, 0) | MASK(2, 1) | MASK(2, 2)
+%xdefine MASK_DIAG4 MASK_DIAG3 | MASK(3, 3)
+%xdefine MASK_OFF4 MASK_OFF3 | MASK_OFF(3)
+%xdefine MASK_MAT4 MASK_ALL & ~MASK_OFF4
+
+%macro linear_row 7 ; res, x, y, z, w, row, mask
+%define COL(J) ((%7) & MASK(%6, J)) ; true if mask contains component J
+%define NOP(J) (J == %6 && !COL(J)) ; true if J is untouched input component
+
+ ; load weights
+ IF COL(0), vbroadcastss m12, [tmp0q + %6 * 20 + 0]
+ IF COL(1), vbroadcastss m13, [tmp0q + %6 * 20 + 4]
+ IF COL(2), vbroadcastss m14, [tmp0q + %6 * 20 + 8]
+ IF COL(3), vbroadcastss m15, [tmp0q + %6 * 20 + 12]
+
+ ; initialize result vector as appropriate
+ %if COL(4) ; offset
+ vbroadcastss %1, [tmp0q + %6 * 20 + 16]
+ %elif NOP(0)
+ ; directly reuse first component vector if possible
+ mova %1, %2
+ %else
+ xorps %1, %1
+ %endif
+
+ IF COL(0), mulps m12, %2
+ IF COL(1), mulps m13, %3
+ IF COL(2), mulps m14, %4
+ IF COL(3), mulps m15, %5
+ IF COL(0), addps %1, m12
+ IF NOP(0) && COL(4), addps %1, %3 ; first vector was not reused
+ IF COL(1), addps %1, m13
+ IF NOP(1), addps %1, %3
+ IF COL(2), addps %1, m14
+ IF NOP(2), addps %1, %4
+ IF COL(3), addps %1, m15
+ IF NOP(3), addps %1, %5
+%endmacro
+
+%macro linear_inner 5 ; x, y, z, w, mask
+ %define ROW(I) ((%5) & MASK_ROW(I))
+ IF1 ROW(0), linear_row m8, %1, %2, %3, %4, 0, %5
+ IF1 ROW(1), linear_row m9, %1, %2, %3, %4, 1, %5
+ IF1 ROW(2), linear_row m10, %1, %2, %3, %4, 2, %5
+ IF1 ROW(3), linear_row m11, %1, %2, %3, %4, 3, %5
+ IF ROW(0), mova %1, m8
+ IF ROW(1), mova %2, m9
+ IF ROW(2), mova %3, m10
+ IF ROW(3), mova %4, m11
+%endmacro
+
+%macro linear_mask 2 ; name, mask
+op %1
+ mov tmp0q, [implq + SwsOpImpl.priv] ; address of matrix
+ linear_inner mx, my, mz, mw, %2
+ linear_inner mx2, my2, mz2, mw2, %2
+ CONTINUE
+%endmacro
+
+; specialized functions for very simple cases
+%macro linear_dot3 0
+op dot3
+ mov tmp0q, [implq + SwsOpImpl.priv]
+ vbroadcastss m12, [tmp0q + 0]
+ vbroadcastss m13, [tmp0q + 4]
+ vbroadcastss m14, [tmp0q + 8]
+ LOAD_CONT tmp0q
+ mulps mx, m12
+ mulps m8, my, m13
+ mulps m9, mz, m14
+ addps mx, m8
+ addps mx, m9
+ mulps mx2, m12
+ mulps m10, my2, m13
+ mulps m11, mz2, m14
+ addps mx2, m10
+ addps mx2, m11
+ CONTINUE tmp0q
+%endmacro
+
+%macro linear_fns 0
+ linear_dot3
+ linear_mask luma, MASK_LUMA
+ linear_mask alpha, MASK_ALPHA
+ linear_mask lumalpha, MASK_LUMA | MASK_ALPHA
+ linear_mask row0, MASK_ROW(0)
+ linear_mask row0a, MASK_ROW(0) | MASK_ALPHA
+ linear_mask diag3, MASK_DIAG3
+ linear_mask diag4, MASK_DIAG4
+ linear_mask diagoff3, MASK_DIAG3 | MASK_OFF3
+ linear_mask matrix3, MASK_MAT3
+ linear_mask affine3, MASK_MAT3 | MASK_OFF3
+ linear_mask affine3a, MASK_MAT3 | MASK_OFF3 | MASK_ALPHA
+ linear_mask matrix4, MASK_MAT4
+ linear_mask affine4, MASK_MAT4 | MASK_OFF4
+%endmacro
+
+INIT_YMM avx2
+decl_common_patterns conv8to32f
+decl_common_patterns conv16to32f
+decl_common_patterns conv32fto8
+decl_common_patterns conv32fto16
+decl_common_patterns min_max
+decl_common_patterns scale
+decl_common_patterns dither_fns
+linear_fns
diff --git a/libswscale/x86/ops_int.asm b/libswscale/x86/ops_int.asm
new file mode 100644
index 0000000000..fa7b13cd25
--- /dev/null
+++ b/libswscale/x86/ops_int.asm
@@ -0,0 +1,1062 @@
+;******************************************************************************
+;* Copyright (c) 2025 Niklas Haas
+;*
+;* 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 "ops_common.asm"
+
+SECTION_RODATA
+
+align 16
+expand16_shuf: db 0, 0, 2, 2, 4, 4, 6, 6, 8, 8, 10, 10, 12, 12, 14, 14
+expand32_shuf: db 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12
+
+read8_unpack2: db 0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15
+read8_unpack3: db 0, 3, 6, 9, 1, 4, 7, 10, 2, 5, 8, 11, -1, -1, -1, -1
+read8_unpack4: db 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15
+read16_unpack2: db 0, 1, 4, 5, 8, 9, 12, 13, 2, 3, 6, 7, 10, 11, 14, 15
+read16_unpack3: db 0, 1, 6, 7, 2, 3, 8, 9, 4, 5, 10, 11, -1, -1, -1, -1
+read16_unpack4: db 0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15
+write8_pack2: db 0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15
+write8_pack3: db 0, 4, 8, 1, 5, 9, 2, 6, 10, 3, 7, 11, -1, -1, -1, -1
+write16_pack3: db 0, 1, 4, 5, 8, 9, 2, 3, 6, 7, 10, 11, -1, -1, -1, -1
+
+%define write8_pack4 read8_unpack4
+%define write16_pack4 read16_unpack2
+%define write16_pack2 read16_unpack4
+
+align 32
+bits_shuf: db 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, \
+ 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3
+bits_mask: db 128, 64, 32, 16, 8, 4, 2, 1,128, 64, 32, 16, 8, 4, 2, 1
+bits_reverse: db 7, 6, 5, 4, 3, 2, 1, 0, 15, 14, 13, 12, 11, 10, 9, 8,
+
+align 32
+mask1: times 32 db 0x01
+mask2: times 32 db 0x03
+mask3: times 32 db 0x07
+mask4: times 32 db 0x0F
+
+SECTION .text
+
+;---------------------------------------------------------
+; Global entry point. See `ops_common.asm` for info.
+
+%macro process_fn 1 ; number of planes
+cglobal sws_process%1_x86, 6, 6 + 2 * %1, 16
+ ; Args:
+ ; execq, implq, bxd, yd as defined in ops_common.int
+ ; bx_end and y_end are initially in tmp0d / tmp1d
+ ; (see SwsOpFunc signature)
+ ;
+ ; Stack layout:
+ ; [rsp + 0] = [qword] impl->cont (address of first kernel)
+ ; [rsp + 8] = [qword] &impl[1] (restore implq after chain)
+ ; [rsp + 16] = [dword] bx start (restore after line finish)
+ ; [rsp + 20] = [dword] bx end (loop counter limit)
+ ; [rsp + 24] = [dword] y end (loop counter limit)
+ sub rsp, 32
+ mov [rsp + 16], bxd
+ mov [rsp + 20], tmp0d ; bx_end
+ mov [rsp + 24], tmp1d ; y_end
+ mov tmp0q, [implq + SwsOpImpl.cont]
+ add implq, SwsOpImpl.next
+ mov [rsp + 0], tmp0q
+ mov [rsp + 8], implq
+
+ ; load plane pointers
+ mov in0q, [execq + SwsOpExec.in0]
+IF %1 > 1, mov in1q, [execq + SwsOpExec.in1]
+IF %1 > 2, mov in2q, [execq + SwsOpExec.in2]
+IF %1 > 3, mov in3q, [execq + SwsOpExec.in3]
+ mov out0q, [execq + SwsOpExec.out0]
+IF %1 > 1, mov out1q, [execq + SwsOpExec.out1]
+IF %1 > 2, mov out2q, [execq + SwsOpExec.out2]
+IF %1 > 3, mov out3q, [execq + SwsOpExec.out3]
+ jmp [rsp] ; call into op chain
+
+; Declare a separate global label for the return point, so that we can append
+; it to the list of op function pointers from the C code, effectively ensuring
+; that we end up here again after the op chain finishes processing a line.
+; (See also: cglobal_label in x86inc.asm)
+%if FORMAT_ELF
+ global current_function %+ _return:function hidden
+%elif FORMAT_MACHO && HAVE_PRIVATE_EXTERN
+ global current_function %+ _return:private_extern
+%else
+ global current_function %+ _return
+%endif
+align function_align
+current_function %+ _return:
+
+ ; op chain always returns back here
+ mov implq, [rsp + 8]
+ inc bxd
+ cmp bxd, [rsp + 20]
+ jne .continue
+ ; end of line
+ inc yd
+ cmp yd, [rsp + 24]
+ je .end
+ ; bump addresses to point to start of next line
+ add in0q, [execq + SwsOpExec.in_bump0]
+IF %1 > 1, add in1q, [execq + SwsOpExec.in_bump1]
+IF %1 > 2, add in2q, [execq + SwsOpExec.in_bump2]
+IF %1 > 3, add in3q, [execq + SwsOpExec.in_bump3]
+ add out0q, [execq + SwsOpExec.out_bump0]
+IF %1 > 1, add out1q, [execq + SwsOpExec.out_bump1]
+IF %1 > 2, add out2q, [execq + SwsOpExec.out_bump2]
+IF %1 > 3, add out3q, [execq + SwsOpExec.out_bump3]
+ mov bxd, [rsp + 16]
+.continue:
+ jmp [rsp]
+.end:
+ add rsp, 32
+ RET
+%endmacro
+
+process_fn 1
+process_fn 2
+process_fn 3
+process_fn 4
+
+;---------------------------------------------------------
+; Packed shuffle fast-path
+
+; This is a special entry point for handling a subset of operation chains
+; that can be reduced down to a single `pshufb` shuffle mask. For more details
+; about when this works, refer to the documentation of `ff_sws_solve_shuffle`.
+;
+; We specialize this function for every possible combination of pixel strides.
+; For example, gray -> gray16 is classified as an "8, 16" operation because it
+; takes 8 bytes and expands them out to 16 bytes in each application of the
+; 128-bit shuffle mask.
+;
+; Since pshufb can't shuffle across lanes, we only instantiate SSE4 versions for
+; all shuffles that are not a clean multiple of 128 bits (e.g. rgb24 -> rgb0).
+; For the clean multiples (e.g. rgba -> argb), we also define AVX2 and AVX512
+; versions that can handle a larger number of bytes at once.
+
+%macro packed_shuffle 2 ; size_in, size_out
+cglobal packed_shuffle%1_%2, 6, 10, 2, \
+ exec, shuffle, bx, y, bxend, yend, src, dst, src_stride, dst_stride
+ mov srcq, [execq + SwsOpExec.in0]
+ mov dstq, [execq + SwsOpExec.out0]
+ mov src_strideq, [execq + SwsOpExec.in_stride0]
+ mov dst_strideq, [execq + SwsOpExec.out_stride0]
+ VBROADCASTI128 m1, [shuffleq]
+ sub bxendd, bxd
+ sub yendd, yd
+ ; reuse now-unneeded regs
+ %define srcidxq execq
+ imul srcidxq, bxendq, -%1
+%if %1 = %2
+ %define dstidxq srcidxq
+%else
+ %define dstidxq shuffleq ; no longer needed reg
+ imul dstidxq, bxendq, -%2
+%endif
+ sub srcq, srcidxq
+ sub dstq, dstidxq
+.loop:
+ %if %1 <= 4
+ movd m0, [srcq + srcidxq]
+ %elif %1 <= 8
+ movq m0, [srcq + srcidxq]
+ %else
+ movu m0, [srcq + srcidxq]
+ %endif
+ pshufb m0, m1
+ movu [dstq + dstidxq], m0
+ add srcidxq, %1
+IF %1 != %2,add dstidxq, %2
+ jnz .loop
+ add srcq, src_strideq
+ add dstq, dst_strideq
+ imul srcidxq, bxendq, -%1
+IF %1 != %2,imul dstidxq, bxendq, -%2
+ dec yendd
+ jnz .loop
+ RET
+%endmacro
+
+INIT_XMM sse4
+packed_shuffle 5, 15 ; 8 -> 24
+packed_shuffle 4, 16 ; 8 -> 32, 16 -> 64
+packed_shuffle 2, 12 ; 8 -> 48
+packed_shuffle 16, 8 ; 16 -> 8
+packed_shuffle 10, 15 ; 16 -> 24
+packed_shuffle 8, 16 ; 16 -> 32, 32 -> 64
+packed_shuffle 4, 12 ; 16 -> 48
+packed_shuffle 15, 15 ; 24 -> 24
+packed_shuffle 12, 16 ; 24 -> 32
+packed_shuffle 6, 12 ; 24 -> 48
+packed_shuffle 16, 12 ; 32 -> 24, 64 -> 48
+packed_shuffle 16, 16 ; 32 -> 32, 64 -> 64
+packed_shuffle 8, 12 ; 32 -> 48
+packed_shuffle 12, 12 ; 48 -> 48
+
+INIT_YMM avx2
+packed_shuffle 32, 32
+
+INIT_ZMM avx512
+packed_shuffle 64, 64
+
+;---------------------------------------------------------
+; Planar reads / writes
+
+%macro read_planar 1 ; elems
+op read_planar%1
+ movu mx, [in0q]
+IF %1 > 1, movu my, [in1q]
+IF %1 > 2, movu mz, [in2q]
+IF %1 > 3, movu mw, [in3q]
+%if V2
+ movu mx2, [in0q + mmsize]
+IF %1 > 1, movu my2, [in1q + mmsize]
+IF %1 > 2, movu mz2, [in2q + mmsize]
+IF %1 > 3, movu mw2, [in3q + mmsize]
+%endif
+ LOAD_CONT tmp0q
+ add in0q, mmsize * (1 + V2)
+IF %1 > 1, add in1q, mmsize * (1 + V2)
+IF %1 > 2, add in2q, mmsize * (1 + V2)
+IF %1 > 3, add in3q, mmsize * (1 + V2)
+ CONTINUE tmp0q
+%endmacro
+
+%macro write_planar 1 ; elems
+op write_planar%1
+ LOAD_CONT tmp0q
+ movu [out0q], mx
+IF %1 > 1, movu [out1q], my
+IF %1 > 2, movu [out2q], mz
+IF %1 > 3, movu [out3q], mw
+%if V2
+ movu [out0q + mmsize], mx2
+IF %1 > 1, movu [out1q + mmsize], my2
+IF %1 > 2, movu [out2q + mmsize], mz2
+IF %1 > 3, movu [out3q + mmsize], mw2
+%endif
+ add out0q, mmsize * (1 + V2)
+IF %1 > 1, add out1q, mmsize * (1 + V2)
+IF %1 > 2, add out2q, mmsize * (1 + V2)
+IF %1 > 3, add out3q, mmsize * (1 + V2)
+ FINISH tmp0q
+%endmacro
+
+%macro read_packed2 1 ; depth
+op read%1_packed2
+ movu m8, [in0q + 0*mmsize]
+ movu m9, [in0q + 1*mmsize]
+ IF V2, movu m10, [in0q + 2*mmsize]
+ IF V2, movu m11, [in0q + 3*mmsize]
+IF %1 < 32, VBROADCASTI128 m12, [read%1_unpack2]
+ LOAD_CONT tmp0q
+ add in0q, mmsize * (2 + V2 * 2)
+%if %1 == 32
+ shufps m8, m8, q3120
+ shufps m9, m9, q3120
+ IF V2, shufps m10, m10, q3120
+ IF V2, shufps m11, m11, q3120
+%else
+ pshufb m8, m12 ; { X0 Y0 | X1 Y1 }
+ pshufb m9, m12 ; { X2 Y2 | X3 Y3 }
+ IF V2, pshufb m10, m12
+ IF V2, pshufb m11, m12
+%endif
+ unpcklpd mx, m8, m9 ; { X0 X2 | X1 X3 }
+ unpckhpd my, m8, m9 ; { Y0 Y2 | Y1 Y3 }
+ IF V2, unpcklpd mx2, m10, m11
+ IF V2, unpckhpd my2, m10, m11
+%if avx_enabled
+ vpermq mx, mx, q3120 ; { X0 X1 | X2 X3 }
+ vpermq my, my, q3120 ; { Y0 Y1 | Y2 Y3 }
+ IF V2, vpermq mx2, mx2, q3120
+ IF V2, vpermq my2, my2, q3120
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+%macro write_packed2 1 ; depth
+op write%1_packed2
+IF %1 < 32, VBROADCASTI128 m12, [write%1_pack2]
+ LOAD_CONT tmp0q
+%if avx_enabled
+ vpermq mx, mx, q3120 ; { X0 X2 | X1 X3 }
+ vpermq my, my, q3120 ; { Y0 Y2 | Y1 Y3 }
+ IF V2, vpermq mx2, mx2, q3120
+ IF V2, vpermq my2, my2, q3120
+%endif
+ unpcklpd m8, mx, my ; { X0 Y0 | X1 Y1 }
+ unpckhpd m9, mx, my ; { X2 Y2 | X3 Y3 }
+ IF V2, unpcklpd m10, mx2, my2
+ IF V2, unpckhpd m11, mx2, my2
+%if %1 == 32
+ shufps m8, m8, q3120
+ shufps m9, m9, q3120
+ IF V2, shufps m10, m10, q3120
+ IF V2, shufps m11, m11, q3120
+%else
+ pshufb m8, m12
+ pshufb m9, m12
+ IF V2, pshufb m10, m12
+ IF V2, pshufb m11, m12
+%endif
+ movu [out0q + 0*mmsize], m8
+ movu [out0q + 1*mmsize], m9
+IF V2, movu [out0q + 2*mmsize], m10
+IF V2, movu [out0q + 3*mmsize], m11
+ add out0q, mmsize * (2 + V2 * 2)
+ FINISH tmp0q
+%endmacro
+
+; helper macro reused for both 3 and 4 component packed reads
+%macro read_packed_inner 7 ; x, y, z, w, addr, num, depth
+ movu xm8, [%5 + 0 * %6]
+ movu xm9, [%5 + 4 * %6]
+ movu xm10, [%5 + 8 * %6]
+ movu xm11, [%5 + 12 * %6]
+ %if avx_enabled
+ vinserti128 m8, m8, [%5 + 16 * %6], 1
+ vinserti128 m9, m9, [%5 + 20 * %6], 1
+ vinserti128 m10, m10, [%5 + 24 * %6], 1
+ vinserti128 m11, m11, [%5 + 28 * %6], 1
+ %endif
+ %if %7 == 32
+ mova %1, m8
+ mova %2, m9
+ mova %3, m10
+ mova %4, m11
+ %else
+ pshufb %1, m8, m12 ; { X0 Y0 Z0 W0 | X4 Y4 Z4 W4 }
+ pshufb %2, m9, m12 ; { X1 Y1 Z1 W1 | X5 Y5 Z5 W5 }
+ pshufb %3, m10, m12 ; { X2 Y2 Z2 W2 | X6 Y6 Z6 W6 }
+ pshufb %4, m11, m12 ; { X3 Y3 Z3 W3 | X7 Y7 Z7 W7 }
+ %endif
+ punpckldq m8, %1, %2 ; { X0 X1 Y0 Y1 | X4 X5 Y4 Y5 }
+ punpckldq m9, %3, %4 ; { X2 X3 Y2 Y3 | X6 X7 Y6 Y7 }
+ punpckhdq m10, %1, %2 ; { Z0 Z1 W0 W1 | Z4 Z5 W4 W5 }
+ punpckhdq m11, %3, %4 ; { Z2 Z3 W2 W3 | Z6 Z7 W6 W7 }
+ punpcklqdq %1, m8, m9 ; { X0 X1 X2 X3 | X4 X5 X6 X7 }
+ punpckhqdq %2, m8, m9 ; { Y0 Y1 Y2 Y3 | Y4 Y5 Y6 Y7 }
+ punpcklqdq %3, m10, m11 ; { Z0 Z1 Z2 Z3 | Z4 Z5 Z6 Z7 }
+IF %6 > 3, punpckhqdq %4, m10, m11 ; { W0 W1 W2 W3 | W4 W5 W6 W7 }
+%endmacro
+
+%macro read_packed 2 ; num, depth
+op read%2_packed%1
+IF %2 < 32, VBROADCASTI128 m12, [read%2_unpack%1]
+ LOAD_CONT tmp0q
+ read_packed_inner mx, my, mz, mw, in0q, %1, %2
+IF1 V2, read_packed_inner mx2, my2, mz2, mw2, in0q + %1 * mmsize, %1, %2
+ add in0q, %1 * mmsize * (1 + V2)
+ CONTINUE tmp0q
+%endmacro
+
+%macro write_packed_inner 7 ; x, y, z, w, addr, num, depth
+ punpckldq m8, %1, %2 ; { X0 Y0 X1 Y1 | X4 Y4 X5 Y5 }
+ punpckldq m9, %3, %4 ; { Z0 W0 Z1 W1 | Z4 W4 Z5 W5 }
+ punpckhdq m10, %1, %2 ; { X2 Y2 X3 Y3 | X6 Y6 X7 Y7 }
+ punpckhdq m11, %3, %4 ; { Z2 W2 Z3 W3 | Z6 W6 Z7 W7 }
+ punpcklqdq %1, m8, m9 ; { X0 Y0 Z0 W0 | X4 Y4 Z4 W4 }
+ punpckhqdq %2, m8, m9 ; { X1 Y1 Z1 W1 | X5 Y5 Z5 W5 }
+ punpcklqdq %3, m10, m11 ; { X2 Y2 Z2 W2 | X6 Y6 Z6 W6 }
+ punpckhqdq %4, m10, m11 ; { X3 Y3 Z3 W3 | X7 Y7 Z7 W7 }
+ %if %7 == 32
+ mova m8, %1
+ mova m9, %2
+ mova m10, %3
+ mova m11, %4
+ %else
+ pshufb m8, %1, m12
+ pshufb m9, %2, m12
+ pshufb m10, %3, m12
+ pshufb m11, %4, m12
+ %endif
+ movu [%5 + 0*%6], xm8
+ movu [%5 + 4*%6], xm9
+ movu [%5 + 8*%6], xm10
+ movu [%5 + 12*%6], xm11
+ %if avx_enabled
+ vextracti128 [%5 + 16*%6], m8, 1
+ vextracti128 [%5 + 20*%6], m9, 1
+ vextracti128 [%5 + 24*%6], m10, 1
+ vextracti128 [%5 + 28*%6], m11, 1
+ %endif
+%endmacro
+
+%macro write_packed 2 ; num, depth
+op write%2_packed%1
+IF %2 < 32, VBROADCASTI128 m12, [write%2_pack%1]
+ LOAD_CONT tmp0q
+ write_packed_inner mx, my, mz, mw, out0q, %1, %2
+IF1 V2, write_packed_inner mx2, my2, mz2, mw2, out0q + %1 * mmsize, %1, %2
+ add out0q, %1 * mmsize * (1 + V2)
+ FINISH tmp0q
+%endmacro
+
+%macro rw_packed 1 ; depth
+ read_packed2 %1
+ read_packed 3, %1
+ read_packed 4, %1
+ write_packed2 %1
+ write_packed 3, %1
+ write_packed 4, %1
+%endmacro
+
+%macro read_nibbles 0
+op read_nibbles1
+%if avx_enabled
+ movu xmx, [in0q]
+IF V2, movu xmx2, [in0q + 16]
+%else
+ movq xmx, [in0q]
+IF V2, movq xmx2, [in0q + 8]
+%endif
+ VBROADCASTI128 m8, [mask4]
+ LOAD_CONT tmp0q
+ add in0q, (mmsize >> 1) * (1 + V2)
+ pmovzxbw mx, xmx
+IF V2, pmovzxbw mx2, xmx2
+ psllw my, mx, 8
+IF V2, psllw my2, mx2, 8
+ psrlw mx, 4
+IF V2, psrlw mx2, 4
+ pand my, m8
+IF V2, pand my2, m8
+ por mx, my
+IF V2, por mx2, my2
+ CONTINUE tmp0q
+%endmacro
+
+%macro read_bits 0
+op read_bits1
+%if avx_enabled
+ vpbroadcastd mx, [in0q]
+IF V2, vpbroadcastd mx2, [in0q + 4]
+%else
+ movd mx, [in0q]
+IF V2, movd mx2, [in0q + 2]
+%endif
+ mova m8, [bits_shuf]
+ VBROADCASTI128 m9, [bits_mask]
+ VBROADCASTI128 m10, [mask1]
+ LOAD_CONT tmp0q
+ add in0q, (mmsize >> 3) * (1 + V2)
+ pshufb mx, m8
+IF V2, pshufb mx2, m8
+ pand mx, m9
+IF V2, pand mx2, m9
+ pcmpeqb mx, m9
+IF V2, pcmpeqb mx2, m9
+ pand mx, m10
+IF V2, pand mx2, m10
+ CONTINUE tmp0q
+%endmacro
+
+; TODO: write_nibbles
+
+%macro write_bits 0
+op write_bits1
+ VBROADCASTI128 m8, [bits_reverse]
+ psllw mx, 7
+IF V2, psllw mx2, 7
+ pshufb mx, m8
+IF V2, pshufb mx2, m8
+ pmovmskb tmp0d, mx
+IF V2, pmovmskb tmp1d, mx2
+%if avx_enabled
+ mov [out0q], tmp0d
+IF V2, mov [out0q + 4], tmp1d
+%else
+ mov [out0q], tmp0d
+IF V2, mov [out0q + 2], tmp1d
+%endif
+ LOAD_CONT tmp0q
+ add out0q, (mmsize >> 3) * (1 + V2)
+ FINISH tmp0q
+%endmacro
+
+;--------------------------
+; Pixel packing / unpacking
+
+%macro pack_generic 3-4 0 ; x, y, z, w
+op pack_%1%2%3%4
+ ; pslld works for all sizes because the input should not overflow
+IF %2, pslld mx, %4+%3+%2
+IF %3, pslld my, %4+%3
+IF %4, pslld mz, %4
+IF %2, por mx, my
+IF %3, por mx, mz
+IF %4, por mx, mw
+ %if V2
+IF %2, pslld mx2, %4+%3+%2
+IF %3, pslld my2, %4+%3
+IF %4, pslld mz2, %4
+IF %2, por mx2, my2
+IF %3, por mx2, mz2
+IF %4, por mx2, mw2
+ %endif
+ CONTINUE
+%endmacro
+
+%macro unpack 5-6 0 ; type, bits, x, y, z, w
+op unpack_%3%4%5%6
+ ; clear high bits by shifting left
+IF %6, vpsll%1 mw, mx, %2 - (%6)
+IF %5, vpsll%1 mz, mx, %2 - (%6+%5)
+IF %4, vpsll%1 my, mx, %2 - (%6+%5+%4)
+ psrl%1 mx, %4+%5+%6
+IF %4, psrl%1 my, %2 - %4
+IF %5, psrl%1 mz, %2 - %5
+IF %6, psrl%1 mw, %2 - %6
+ %if V2
+IF %6, vpsll%1 mw2, mx2, %2 - (%6)
+IF %5, vpsll%1 mz2, mx2, %2 - (%6+%5)
+IF %4, vpsll%1 my2, mx2, %2 - (%6+%5+%4)
+ psrl%1 mx2, %4+%5+%6
+IF %4, psrl%1 my2, %2 - %4
+IF %5, psrl%1 mz2, %2 - %5
+IF %6, psrl%1 mw2, %2 - %6
+ %endif
+ CONTINUE
+%endmacro
+
+%macro unpack8 3 ; x, y, z
+op unpack_%1%2%3 %+ 0
+ pand mz, mx, [mask%3]
+ psrld my, mx, %3
+ psrld mx, %3+%2
+ pand my, [mask%2]
+ pand mx, [mask%1]
+ %if V2
+ pand mz2, mx2, [mask%3]
+ psrld my2, mx2, %3
+ psrld mx2, %3+%2
+ pand my2, [mask%2]
+ pand mx2, [mask%1]
+ %endif
+ CONTINUE
+%endmacro
+
+;---------------------------------------------------------
+; Generic byte order shuffle (packed swizzle, endian, etc)
+
+%macro shuffle 0
+op shuffle
+ VBROADCASTI128 m8, [implq + SwsOpImpl.priv]
+ LOAD_CONT tmp0q
+IF X, pshufb mx, m8
+IF Y, pshufb my, m8
+IF Z, pshufb mz, m8
+IF W, pshufb mw, m8
+%if V2
+IF X, pshufb mx2, m8
+IF Y, pshufb my2, m8
+IF Z, pshufb mz2, m8
+IF W, pshufb mw2, m8
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+;---------------------------------------------------------
+; Clearing
+
+%macro clear_alpha 3 ; idx, vreg, vreg2
+op clear_alpha%1
+ LOAD_CONT tmp0q
+ pcmpeqb %2, %2
+IF V2, mova %3, %2
+ CONTINUE tmp0q
+%endmacro
+
+%macro clear_zero 3 ; idx, vreg, vreg2
+op clear_zero%1
+ LOAD_CONT tmp0q
+ pxor %2, %2
+IF V2, mova %3, %2
+ CONTINUE tmp0q
+%endmacro
+
+; note: the pattern is inverted for these functions; i.e. X=1 implies that we
+; *keep* the X component, not that we clear it
+%macro clear_generic 0
+op clear
+ LOAD_CONT tmp0q
+%if avx_enabled
+ IF !X, vpbroadcastd mx, [implq + SwsOpImpl.priv + 0]
+ IF !Y, vpbroadcastd my, [implq + SwsOpImpl.priv + 4]
+ IF !Z, vpbroadcastd mz, [implq + SwsOpImpl.priv + 8]
+ IF !W, vpbroadcastd mw, [implq + SwsOpImpl.priv + 12]
+%else ; !avx_enabled
+ IF !X, movd mx, [implq + SwsOpImpl.priv + 0]
+ IF !Y, movd my, [implq + SwsOpImpl.priv + 4]
+ IF !Z, movd mz, [implq + SwsOpImpl.priv + 8]
+ IF !W, movd mw, [implq + SwsOpImpl.priv + 12]
+ IF !X, pshufd mx, mx, 0
+ IF !Y, pshufd my, my, 0
+ IF !Z, pshufd mz, mz, 0
+ IF !W, pshufd mw, mw, 0
+%endif
+%if V2
+ IF !X, mova mx2, mx
+ IF !Y, mova my2, my
+ IF !Z, mova mz2, mz
+ IF !W, mova mw2, mw
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+%macro clear_funcs 0
+ decl_pattern 1, 1, 1, 0, clear_generic
+ decl_pattern 0, 1, 1, 1, clear_generic
+ decl_pattern 0, 0, 1, 1, clear_generic
+ decl_pattern 1, 0, 0, 1, clear_generic
+ decl_pattern 1, 1, 0, 0, clear_generic
+ decl_pattern 0, 1, 0, 1, clear_generic
+ decl_pattern 1, 0, 1, 0, clear_generic
+ decl_pattern 1, 0, 0, 0, clear_generic
+ decl_pattern 0, 1, 0, 0, clear_generic
+ decl_pattern 0, 0, 1, 0, clear_generic
+%endmacro
+
+;---------------------------------------------------------
+; Swizzling and duplicating
+
+; mA := mB, mB := mC, ... mX := mA across both halves
+%macro vrotate 2-* ; A, B, C, ...
+ %rep %0
+ %assign rot_a %1 + 4
+ %assign rot_b %2 + 4
+ mova m%1, m%2
+ IF V2, mova m%[rot_a], m%[rot_b]
+ %rotate 1
+ %endrep
+ %undef rot_a
+ %undef rot_b
+%endmacro
+
+%macro swizzle_funcs 0
+op swizzle_3012
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 3, 2, 1
+ CONTINUE tmp0q
+
+op swizzle_3021
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 3, 1
+ CONTINUE tmp0q
+
+op swizzle_2103
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 2
+ CONTINUE tmp0q
+
+op swizzle_3210
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 3
+ vrotate 8, 1, 2
+ CONTINUE tmp0q
+
+op swizzle_3102
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 3, 2
+ CONTINUE tmp0q
+
+op swizzle_3201
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 3, 1, 2
+ CONTINUE tmp0q
+
+op swizzle_1203
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 1, 2
+ CONTINUE tmp0q
+
+op swizzle_1023
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 1
+ CONTINUE tmp0q
+
+op swizzle_2013
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 2, 1
+ CONTINUE tmp0q
+
+op swizzle_2310
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 2, 1, 3
+ CONTINUE tmp0q
+
+op swizzle_2130
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 2, 3
+ CONTINUE tmp0q
+
+op swizzle_1230
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 1, 2, 3
+ CONTINUE tmp0q
+
+op swizzle_1320
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 1, 3
+ CONTINUE tmp0q
+
+op swizzle_0213
+ LOAD_CONT tmp0q
+ vrotate 8, 1, 2
+ CONTINUE tmp0q
+
+op swizzle_0231
+ LOAD_CONT tmp0q
+ vrotate 8, 1, 2, 3
+ CONTINUE tmp0q
+
+op swizzle_0312
+ LOAD_CONT tmp0q
+ vrotate 8, 1, 3, 2
+ CONTINUE tmp0q
+
+op swizzle_3120
+ LOAD_CONT tmp0q
+ vrotate 8, 0, 3
+ CONTINUE tmp0q
+
+op swizzle_0321
+ LOAD_CONT tmp0q
+ vrotate 8, 1, 3
+ CONTINUE tmp0q
+
+op swizzle_0003
+ LOAD_CONT tmp0q
+ mova my, mx
+ mova mz, mx
+%if V2
+ mova my2, mx2
+ mova mz2, mx2
+%endif
+ CONTINUE tmp0q
+
+op swizzle_0001
+ LOAD_CONT tmp0q
+ mova mw, my
+ mova mz, mx
+ mova my, mx
+%if V2
+ mova mw2, my2
+ mova mz2, mx2
+ mova my2, mx2
+%endif
+ CONTINUE tmp0q
+
+op swizzle_3000
+ LOAD_CONT tmp0q
+ mova my, mx
+ mova mz, mx
+ mova mx, mw
+ mova mw, my
+%if V2
+ mova my2, mx2
+ mova mz2, mx2
+ mova mx2, mw2
+ mova mw2, my2
+%endif
+ CONTINUE tmp0q
+
+op swizzle_1000
+ LOAD_CONT tmp0q
+ mova mz, mx
+ mova mw, mx
+ mova mx, my
+ mova my, mz
+%if V2
+ mova mz2, mx2
+ mova mw2, mx2
+ mova mx2, my2
+ mova my2, mz2
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+;---------------------------------------------------------
+; Pixel type conversions
+
+%macro conv8to16 1 ; type
+op %1_U8_U16
+ LOAD_CONT tmp0q
+%if V2
+ %if avx_enabled
+ IF X, vextracti128 xmx2, mx, 1
+ IF Y, vextracti128 xmy2, my, 1
+ IF Z, vextracti128 xmz2, mz, 1
+ IF W, vextracti128 xmw2, mw, 1
+ %else
+ IF X, psrldq xmx2, mx, 8
+ IF Y, psrldq xmy2, my, 8
+ IF Z, psrldq xmz2, mz, 8
+ IF W, psrldq xmw2, mw, 8
+ %endif
+ IF X, pmovzxbw mx2, xmx2
+ IF Y, pmovzxbw my2, xmy2
+ IF Z, pmovzxbw mz2, xmz2
+ IF W, pmovzxbw mw2, xmw2
+%endif ; V2
+ IF X, pmovzxbw mx, xmx
+ IF Y, pmovzxbw my, xmy
+ IF Z, pmovzxbw mz, xmz
+ IF W, pmovzxbw mw, xmw
+
+%ifidn %1, expand
+ VBROADCASTI128 m8, [expand16_shuf]
+ %if V2
+ IF X, pshufb mx2, m8
+ IF Y, pshufb my2, m8
+ IF Z, pshufb mz2, m8
+ IF W, pshufb mw2, m8
+ %endif
+ IF X, pshufb mx, m8
+ IF Y, pshufb my, m8
+ IF Z, pshufb mz, m8
+ IF W, pshufb mw, m8
+%endif ; expand
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv16to8 0
+op convert_U16_U8
+ LOAD_CONT tmp0q
+%if V2
+ ; this code technically works for the !V2 case as well, but slower
+IF X, packuswb mx, mx2
+IF Y, packuswb my, my2
+IF Z, packuswb mz, mz2
+IF W, packuswb mw, mw2
+IF X, vpermq mx, mx, q3120
+IF Y, vpermq my, my, q3120
+IF Z, vpermq mz, mz, q3120
+IF W, vpermq mw, mw, q3120
+%else
+IF X, vextracti128 xm8, mx, 1
+IF Y, vextracti128 xm9, my, 1
+IF Z, vextracti128 xm10, mz, 1
+IF W, vextracti128 xm11, mw, 1
+ vzeroupper
+IF X, packuswb xmx, xm8
+IF Y, packuswb xmy, xm9
+IF Z, packuswb xmz, xm10
+IF W, packuswb xmw, xm11
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv8to32 1 ; type
+op %1_U8_U32
+ LOAD_CONT tmp0q
+IF X, psrldq xmx2, xmx, 8
+IF Y, psrldq xmy2, xmy, 8
+IF Z, psrldq xmz2, xmz, 8
+IF W, psrldq xmw2, xmw, 8
+IF X, pmovzxbd mx, xmx
+IF Y, pmovzxbd my, xmy
+IF Z, pmovzxbd mz, xmz
+IF W, pmovzxbd mw, xmw
+IF X, pmovzxbd mx2, xmx2
+IF Y, pmovzxbd my2, xmy2
+IF Z, pmovzxbd mz2, xmz2
+IF W, pmovzxbd mw2, xmw2
+%ifidn %1, expand
+ VBROADCASTI128 m8, [expand32_shuf]
+IF X, pshufb mx, m8
+IF Y, pshufb my, m8
+IF Z, pshufb mz, m8
+IF W, pshufb mw, m8
+IF X, pshufb mx2, m8
+IF Y, pshufb my2, m8
+IF Z, pshufb mz2, m8
+IF W, pshufb mw2, m8
+%endif ; expand
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv32to8 0
+op convert_U32_U8
+ LOAD_CONT tmp0q
+IF X, packusdw mx, mx2
+IF Y, packusdw my, my2
+IF Z, packusdw mz, mz2
+IF W, packusdw mw, mw2
+IF X, vextracti128 xmx2, mx, 1
+IF Y, vextracti128 xmy2, my, 1
+IF Z, vextracti128 xmz2, mz, 1
+IF W, vextracti128 xmw2, mw, 1
+ vzeroupper
+IF X, packuswb xmx, xmx2
+IF Y, packuswb xmy, xmy2
+IF Z, packuswb xmz, xmz2
+IF W, packuswb xmw, xmw2
+IF X, vpshufd xmx, xmx, q3120
+IF Y, vpshufd xmy, xmy, q3120
+IF Z, vpshufd xmz, xmz, q3120
+IF W, vpshufd xmw, xmw, q3120
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv16to32 0
+op convert_U16_U32
+ LOAD_CONT tmp0q
+IF X, vextracti128 xmx2, mx, 1
+IF Y, vextracti128 xmy2, my, 1
+IF Z, vextracti128 xmz2, mz, 1
+IF W, vextracti128 xmw2, mw, 1
+IF X, pmovzxwd mx, xmx
+IF Y, pmovzxwd my, xmy
+IF Z, pmovzxwd mz, xmz
+IF W, pmovzxwd mw, xmw
+IF X, pmovzxwd mx2, xmx2
+IF Y, pmovzxwd my2, xmy2
+IF Z, pmovzxwd mz2, xmz2
+IF W, pmovzxwd mw2, xmw2
+ CONTINUE tmp0q
+%endmacro
+
+%macro conv32to16 0
+op convert_U32_U16
+ LOAD_CONT tmp0q
+IF X, packusdw mx, mx2
+IF Y, packusdw my, my2
+IF Z, packusdw mz, mz2
+IF W, packusdw mw, mw2
+IF X, vpermq mx, mx, q3120
+IF Y, vpermq my, my, q3120
+IF Z, vpermq mz, mz, q3120
+IF W, vpermq mw, mw, q3120
+ CONTINUE tmp0q
+%endmacro
+
+;---------------------------------------------------------
+; Shifting
+
+%macro lshift16 0
+op lshift16
+ vmovq xm8, [implq + SwsOpImpl.priv]
+ LOAD_CONT tmp0q
+IF X, psllw mx, xm8
+IF Y, psllw my, xm8
+IF Z, psllw mz, xm8
+IF W, psllw mw, xm8
+%if V2
+IF X, psllw mx2, xm8
+IF Y, psllw my2, xm8
+IF Z, psllw mz2, xm8
+IF W, psllw mw2, xm8
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+%macro rshift16 0
+op rshift16
+ vmovq xm8, [implq + SwsOpImpl.priv]
+ LOAD_CONT tmp0q
+IF X, psrlw mx, xm8
+IF Y, psrlw my, xm8
+IF Z, psrlw mz, xm8
+IF W, psrlw mw, xm8
+%if V2
+IF X, psrlw mx2, xm8
+IF Y, psrlw my2, xm8
+IF Z, psrlw mz2, xm8
+IF W, psrlw mw2, xm8
+%endif
+ CONTINUE tmp0q
+%endmacro
+
+;---------------------------------------------------------
+; Macro instantiations for kernel functions
+
+%macro funcs_u8 0
+ read_planar 1
+ read_planar 2
+ read_planar 3
+ read_planar 4
+ write_planar 1
+ write_planar 2
+ write_planar 3
+ write_planar 4
+
+ rw_packed 8
+ read_nibbles
+ read_bits
+ write_bits
+
+ pack_generic 1, 2, 1
+ pack_generic 3, 3, 2
+ pack_generic 2, 3, 3
+ unpack8 1, 2, 1
+ unpack8 3, 3, 2
+ unpack8 2, 3, 3
+
+ clear_alpha 0, mx, mx2
+ clear_alpha 1, my, my2
+ clear_alpha 3, mw, mw2
+ clear_zero 0, mx, mx2
+ clear_zero 1, my, my2
+ clear_zero 3, mw, mw2
+ clear_funcs
+ swizzle_funcs
+
+ decl_common_patterns shuffle
+%endmacro
+
+%macro funcs_u16 0
+ rw_packed 16
+ pack_generic 4, 4, 4
+ pack_generic 5, 5, 5
+ pack_generic 5, 6, 5
+ unpack w, 16, 4, 4, 4
+ unpack w, 16, 5, 5, 5
+ unpack w, 16, 5, 6, 5
+ decl_common_patterns conv8to16 convert
+ decl_common_patterns conv8to16 expand
+ decl_common_patterns conv16to8
+ decl_common_patterns lshift16
+ decl_common_patterns rshift16
+%endmacro
+
+INIT_XMM sse4
+decl_v2 0, funcs_u8
+decl_v2 1, funcs_u8
+
+INIT_YMM avx2
+decl_v2 0, funcs_u8
+decl_v2 1, funcs_u8
+decl_v2 0, funcs_u16
+decl_v2 1, funcs_u16
+
+INIT_YMM avx2
+decl_v2 1, rw_packed 32
+decl_v2 1, pack_generic 10, 10, 10, 2
+decl_v2 1, pack_generic 2, 10, 10, 10
+decl_v2 1, unpack d, 32, 10, 10, 10, 2
+decl_v2 1, unpack d, 32, 2, 10, 10, 10
+decl_common_patterns conv8to32 convert
+decl_common_patterns conv8to32 expand
+decl_common_patterns conv32to8
+decl_common_patterns conv16to32
+decl_common_patterns conv32to16
commit a151b426f9eff9ba802cd1e7f8674c6d910186e7
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Thu May 8 19:04:49 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/ops_memcpy: add 'memcpy' backend for plane->plane copies
Provides a generic fast path for any operation list that can be decomposed
into a series of memcpy and memset operations.
25% faster than the x86 backend for yuv444p -> yuva444p
33% faster than the x86 backend for gray -> yuvj444p
diff --git a/libswscale/Makefile b/libswscale/Makefile
index c6e32638e6..d3009c7f21 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -18,6 +18,7 @@ OBJS = alphablend.o \
ops.o \
ops_backend.o \
ops_chain.o \
+ ops_memcpy.o \
ops_optimizer.o \
options.o \
output.o \
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 10463513f2..4c147f6467 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -27,9 +27,11 @@
#include "ops.h"
#include "ops_internal.h"
-const extern SwsOpBackend backend_c;
+extern const SwsOpBackend backend_c;
+extern const SwsOpBackend backend_murder;
const SwsOpBackend * const ff_sws_op_backends[] = {
+ &backend_murder,
&backend_c,
NULL
};
diff --git a/libswscale/ops_memcpy.c b/libswscale/ops_memcpy.c
new file mode 100644
index 0000000000..15002ae5ff
--- /dev/null
+++ b/libswscale/ops_memcpy.c
@@ -0,0 +1,132 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "libavutil/avassert.h"
+
+#include "ops_backend.h"
+
+typedef struct MemcpyPriv {
+ int num_planes;
+ int index[4]; /* or -1 to clear plane */
+ uint8_t clear_value[4];
+} MemcpyPriv;
+
+/* Memcpy backend for trivial cases */
+
+static void process(const SwsOpExec *exec, const void *priv,
+ int x_start, int y_start, int x_end, int y_end)
+{
+ const MemcpyPriv *p = priv;
+ const int lines = y_end - y_start;
+ av_assert1(x_start == 0 && x_end == exec->width);
+
+ for (int i = 0; i < p->num_planes; i++) {
+ uint8_t *out = exec->out[i];
+ const int idx = p->index[i];
+ if (idx < 0) {
+ memset(out, p->clear_value[i], exec->out_stride[i] * lines);
+ } else if (exec->out_stride[i] == exec->in_stride[idx]) {
+ memcpy(out, exec->in[idx], exec->out_stride[i] * lines);
+ } else {
+ const int bytes = x_end * exec->block_size_out;
+ const uint8_t *in = exec->in[idx];
+ for (int y = y_start; y < y_end; y++) {
+ memcpy(out, in, bytes);
+ out += exec->out_stride[i];
+ in += exec->in_stride[idx];
+ }
+ }
+ }
+}
+
+static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out)
+{
+ MemcpyPriv p = {0};
+
+ for (int n = 0; n < ops->num_ops; n++) {
+ const SwsOp *op = &ops->ops[n];
+ switch (op->op) {
+ case SWS_OP_READ:
+ if ((op->rw.packed && op->rw.elems != 1) || op->rw.frac)
+ return AVERROR(ENOTSUP);
+ for (int i = 0; i < op->rw.elems; i++)
+ p.index[i] = i;
+ break;
+
+ case SWS_OP_SWIZZLE: {
+ const MemcpyPriv orig = p;
+ for (int i = 0; i < 4; i++) {
+ /* Explicitly exclude swizzle masks that contain duplicates,
+ * because these are wasteful to implement as a memcpy */
+ for (int j = 0; j < i; j++) {
+ if (op->swizzle.in[i] == op->swizzle.in[j])
+ return AVERROR(ENOTSUP);
+ }
+ p.index[i] = orig.index[op->swizzle.in[i]];
+ }
+ break;
+ }
+
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (!op->c.q4[i].den)
+ continue;
+ if (op->c.q4[i].den != 1)
+ return AVERROR(ENOTSUP);
+
+ /* Ensure all bytes to be cleared are the same, because we
+ * can't memset on multi-byte sequences */
+ uint8_t val = op->c.q4[i].num & 0xFF;
+ uint32_t ref = val;
+ switch (ff_sws_pixel_type_size(op->type)) {
+ case 2: ref *= 0x101; break;
+ case 4: ref *= 0x1010101; break;
+ }
+ if (ref != op->c.q4[i].num)
+ return AVERROR(ENOTSUP);
+ p.clear_value[i] = val;
+ p.index[i] = -1;
+ }
+ break;
+
+ case SWS_OP_WRITE:
+ if ((op->rw.packed && op->rw.elems != 1) || op->rw.frac)
+ return AVERROR(ENOTSUP);
+ p.num_planes = op->rw.elems;
+ break;
+
+ default:
+ return AVERROR(ENOTSUP);
+ }
+ }
+
+ *out = (SwsCompiledOp) {
+ .block_size = 1,
+ .func = process,
+ .priv = av_memdup(&p, sizeof(p)),
+ .free = av_free,
+ };
+ return out->priv ? 0 : AVERROR(ENOMEM);
+}
+
+const SwsOpBackend backend_murder = {
+ .name = "memcpy",
+ .compile = compile,
+};
commit 5aef513fb4408c06d52a96ab01e8902171ce00a1
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Thu Mar 13 18:12:17 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/ops_backend: add reference backend basend on C templates
This will serve as a reference for the SIMD backends to come. That said,
with auto-vectorization enabled, the performance of this is not atrocious.
It easily beats the old C code and sometimes even the old SIMD.
In theory, we can dramatically speed it up by using GCC vectors instead of
arrays, but the performance gains from this are too dependent on exact GCC
versions and flags, so it practice it's not a substitute for a SIMD
implementation.
diff --git a/libswscale/Makefile b/libswscale/Makefile
index c9dfa78c89..c6e32638e6 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -16,6 +16,7 @@ OBJS = alphablend.o \
input.o \
lut3d.o \
ops.o \
+ ops_backend.o \
ops_chain.o \
ops_optimizer.o \
options.o \
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 7c5f315c8f..10463513f2 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -27,7 +27,10 @@
#include "ops.h"
#include "ops_internal.h"
+const extern SwsOpBackend backend_c;
+
const SwsOpBackend * const ff_sws_op_backends[] = {
+ &backend_c,
NULL
};
diff --git a/libswscale/ops_backend.c b/libswscale/ops_backend.c
new file mode 100644
index 0000000000..ac76dedbee
--- /dev/null
+++ b/libswscale/ops_backend.c
@@ -0,0 +1,109 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "ops_backend.h"
+
+#if AV_GCC_VERSION_AT_LEAST(4, 4)
+#pragma GCC optimize ("finite-math-only")
+#endif
+
+/* Array-based reference implementation */
+
+#ifndef SWS_BLOCK_SIZE
+# define SWS_BLOCK_SIZE 32
+#endif
+
+typedef uint8_t u8block_t[SWS_BLOCK_SIZE];
+typedef uint16_t u16block_t[SWS_BLOCK_SIZE];
+typedef uint32_t u32block_t[SWS_BLOCK_SIZE];
+typedef float f32block_t[SWS_BLOCK_SIZE];
+
+#define BIT_DEPTH 8
+# include "ops_tmpl_int.c"
+#undef BIT_DEPTH
+
+#define BIT_DEPTH 16
+# include "ops_tmpl_int.c"
+#undef BIT_DEPTH
+
+#define BIT_DEPTH 32
+# include "ops_tmpl_int.c"
+# include "ops_tmpl_float.c"
+#undef BIT_DEPTH
+
+static void process(const SwsOpExec *exec, const void *priv,
+ const int bx_start, const int y_start, int bx_end, int y_end)
+{
+ const SwsOpChain *chain = priv;
+ const SwsOpImpl *impl = chain->impl;
+ SwsOpIter iter;
+
+ for (iter.y = y_start; iter.y < y_end; iter.y++) {
+ for (int i = 0; i < 4; i++) {
+ iter.in[i] = exec->in[i] + (iter.y - y_start) * exec->in_stride[i];
+ iter.out[i] = exec->out[i] + (iter.y - y_start) * exec->out_stride[i];
+ }
+
+ for (int block = bx_start; block < bx_end; block++) {
+ iter.x = block * SWS_BLOCK_SIZE;
+ ((void (*)(SwsOpIter *, const SwsOpImpl *)) impl->cont)
+ (&iter, &impl[1]);
+ }
+ }
+}
+
+static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out)
+{
+ int ret;
+
+ SwsOpChain *chain = ff_sws_op_chain_alloc();
+ if (!chain)
+ return AVERROR(ENOMEM);
+
+ static const SwsOpTable *const tables[] = {
+ &bitfn(op_table_int, u8),
+ &bitfn(op_table_int, u16),
+ &bitfn(op_table_int, u32),
+ &bitfn(op_table_float, f32),
+ };
+
+ do {
+ ret = ff_sws_op_compile_tables(tables, FF_ARRAY_ELEMS(tables), ops,
+ SWS_BLOCK_SIZE, chain);
+ } while (ret == AVERROR(EAGAIN));
+ if (ret < 0) {
+ ff_sws_op_chain_free(chain);
+ return ret;
+ }
+
+ *out = (SwsCompiledOp) {
+ .func = process,
+ .block_size = SWS_BLOCK_SIZE,
+ .cpu_flags = chain->cpu_flags,
+ .priv = chain,
+ .free = (void (*)(void *)) ff_sws_op_chain_free,
+ };
+ return 0;
+}
+
+const SwsOpBackend backend_c = {
+ .name = "c",
+ .compile = compile,
+};
diff --git a/libswscale/ops_backend.h b/libswscale/ops_backend.h
new file mode 100644
index 0000000000..7880bb608e
--- /dev/null
+++ b/libswscale/ops_backend.h
@@ -0,0 +1,167 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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
+ */
+
+#ifndef SWSCALE_OPS_BACKEND_H
+#define SWSCALE_OPS_BACKEND_H
+
+/**
+ * Helper macros for the C-based backend.
+ *
+ * To use these macros, the following types must be defined:
+ * - PIXEL_TYPE should be one of SWS_PIXEL_*
+ * - pixel_t should be the type of pixels
+ * - block_t should be the type of blocks (groups of pixels)
+ */
+
+#include <assert.h>
+#include <float.h>
+#include <stdint.h>
+
+#include "libavutil/attributes.h"
+#include "libavutil/mem.h"
+
+#include "ops_chain.h"
+
+/**
+ * Internal context holding per-iter execution data. The data pointers will be
+ * directly incremented by the corresponding read/write functions.
+ */
+typedef struct SwsOpIter {
+ const uint8_t *in[4];
+ uint8_t *out[4];
+ int x, y;
+} SwsOpIter;
+
+#ifdef __clang__
+# define SWS_FUNC
+# define SWS_LOOP AV_PRAGMA(clang loop vectorize(assume_safety))
+#elif defined(__GNUC__)
+# define SWS_FUNC __attribute__((optimize("tree-vectorize")))
+# define SWS_LOOP AV_PRAGMA(GCC ivdep)
+#else
+# define SWS_FUNC
+# define SWS_LOOP
+#endif
+
+/* Miscellaneous helpers */
+#define bitfn2(name, ext) name ## _ ## ext
+#define bitfn(name, ext) bitfn2(name, ext)
+
+#define FN_SUFFIX AV_JOIN(FMT_CHAR, BIT_DEPTH)
+#define fn(name) bitfn(name, FN_SUFFIX)
+
+#define av_q2pixel(q) ((q).den ? (pixel_t) (q).num / (q).den : 0)
+
+/* Helper macros to make writing common function signatures less painful */
+#define DECL_FUNC(NAME, ...) \
+ static av_always_inline void fn(NAME)(SwsOpIter *restrict iter, \
+ const SwsOpImpl *restrict impl, \
+ block_t x, block_t y, \
+ block_t z, block_t w, \
+ __VA_ARGS__)
+
+#define DECL_READ(NAME, ...) \
+ static av_always_inline void fn(NAME)(SwsOpIter *restrict iter, \
+ const SwsOpImpl *restrict impl, \
+ const pixel_t *restrict in0, \
+ const pixel_t *restrict in1, \
+ const pixel_t *restrict in2, \
+ const pixel_t *restrict in3, \
+ __VA_ARGS__)
+
+#define DECL_WRITE(NAME, ...) \
+ DECL_FUNC(NAME, pixel_t *restrict out0, pixel_t *restrict out1, \
+ pixel_t *restrict out2, pixel_t *restrict out3, \
+ __VA_ARGS__)
+
+/* Helper macros to call into functions declared with DECL_FUNC_* */
+#define CALL(FUNC, ...) \
+ fn(FUNC)(iter, impl, x, y, z, w, __VA_ARGS__)
+
+#define CALL_READ(FUNC, ...) \
+ fn(FUNC)(iter, impl, (const pixel_t *) iter->in[0], \
+ (const pixel_t *) iter->in[1], \
+ (const pixel_t *) iter->in[2], \
+ (const pixel_t *) iter->in[3], __VA_ARGS__)
+
+#define CALL_WRITE(FUNC, ...) \
+ CALL(FUNC, (pixel_t *) iter->out[0], (pixel_t *) iter->out[1], \
+ (pixel_t *) iter->out[2], (pixel_t *) iter->out[3], __VA_ARGS__)
+
+/* Helper macros to declare continuation functions */
+#define DECL_IMPL(NAME) \
+ static SWS_FUNC void fn(NAME)(SwsOpIter *restrict iter, \
+ const SwsOpImpl *restrict impl, \
+ block_t x, block_t y, \
+ block_t z, block_t w) \
+
+/* Helper macro to call into the next continuation with a given type */
+#define CONTINUE(TYPE, ...) \
+ ((void (*)(SwsOpIter *, const SwsOpImpl *, \
+ TYPE x, TYPE y, TYPE z, TYPE w)) impl->cont) \
+ (iter, &impl[1], __VA_ARGS__)
+
+/* Helper macros for common op setup code */
+#define DECL_SETUP(NAME) \
+ static int fn(NAME)(const SwsOp *op, SwsOpPriv *out)
+
+#define SETUP_MEMDUP(c) ff_setup_memdup(&(c), sizeof(c), out)
+static inline int ff_setup_memdup(const void *c, size_t size, SwsOpPriv *out)
+{
+ out->ptr = av_memdup(c, size);
+ return out->ptr ? 0 : AVERROR(ENOMEM);
+}
+
+/* Helper macro for declaring op table entries */
+#define DECL_ENTRY(NAME, ...) \
+ static const SwsOpEntry fn(op_##NAME) = { \
+ .func = (SwsFuncPtr) fn(NAME), \
+ .type = PIXEL_TYPE, \
+ __VA_ARGS__ \
+ }
+
+/* Helpers to define functions for common subsets of components */
+#define DECL_PATTERN(NAME) \
+ DECL_FUNC(NAME, const bool X, const bool Y, const bool Z, const bool W)
+
+#define WRAP_PATTERN(FUNC, X, Y, Z, W, ...) \
+ DECL_IMPL(FUNC##_##X##Y##Z##W) \
+ { \
+ CALL(FUNC, X, Y, Z, W); \
+ } \
+ \
+ DECL_ENTRY(FUNC##_##X##Y##Z##W, \
+ .unused = { !X, !Y, !Z, !W }, \
+ __VA_ARGS__ \
+ )
+
+#define WRAP_COMMON_PATTERNS(FUNC, ...) \
+ WRAP_PATTERN(FUNC, 1, 0, 0, 0, __VA_ARGS__); \
+ WRAP_PATTERN(FUNC, 1, 0, 0, 1, __VA_ARGS__); \
+ WRAP_PATTERN(FUNC, 1, 1, 1, 0, __VA_ARGS__); \
+ WRAP_PATTERN(FUNC, 1, 1, 1, 1, __VA_ARGS__)
+
+#define REF_COMMON_PATTERNS(NAME) \
+ &fn(op_##NAME##_1000), \
+ &fn(op_##NAME##_1001), \
+ &fn(op_##NAME##_1110), \
+ &fn(op_##NAME##_1111)
+
+#endif
diff --git a/libswscale/ops_tmpl_common.c b/libswscale/ops_tmpl_common.c
new file mode 100644
index 0000000000..9490be0313
--- /dev/null
+++ b/libswscale/ops_tmpl_common.c
@@ -0,0 +1,176 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "ops_backend.h"
+
+#ifndef BIT_DEPTH
+# error Should only be included from ops_tmpl_*.c!
+#endif
+
+#define WRAP_CONVERT_UINT(N) \
+DECL_PATTERN(convert_uint##N) \
+{ \
+ u##N##block_t xu, yu, zu, wu; \
+ \
+ SWS_LOOP \
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) { \
+ if (X) \
+ xu[i] = x[i]; \
+ if (Y) \
+ yu[i] = y[i]; \
+ if (Z) \
+ zu[i] = z[i]; \
+ if (W) \
+ wu[i] = w[i]; \
+ } \
+ \
+ CONTINUE(u##N##block_t, xu, yu, zu, wu); \
+} \
+ \
+WRAP_COMMON_PATTERNS(convert_uint##N, \
+ .op = SWS_OP_CONVERT, \
+ .convert.to = SWS_PIXEL_U##N, \
+);
+
+#if BIT_DEPTH != 8
+WRAP_CONVERT_UINT(8)
+#endif
+
+#if BIT_DEPTH != 16
+WRAP_CONVERT_UINT(16)
+#endif
+
+#if BIT_DEPTH != 32 || defined(IS_FLOAT)
+WRAP_CONVERT_UINT(32)
+#endif
+
+DECL_PATTERN(clear)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ if (!X)
+ x[i] = impl->priv.px[0];
+ if (!Y)
+ y[i] = impl->priv.px[1];
+ if (!Z)
+ z[i] = impl->priv.px[2];
+ if (!W)
+ w[i] = impl->priv.px[3];
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+#define WRAP_CLEAR(X, Y, Z, W) \
+DECL_IMPL(clear##_##X##Y##Z##W) \
+{ \
+ CALL(clear, X, Y, Z, W); \
+} \
+ \
+DECL_ENTRY(clear##_##X##Y##Z##W, \
+ .setup = ff_sws_setup_q4, \
+ .op = SWS_OP_CLEAR, \
+ .flexible = true, \
+ .unused = { !X, !Y, !Z, !W }, \
+);
+
+WRAP_CLEAR(1, 1, 1, 0) /* rgba alpha */
+WRAP_CLEAR(0, 1, 1, 1) /* argb alpha */
+
+WRAP_CLEAR(0, 0, 1, 1) /* vuya chroma */
+WRAP_CLEAR(1, 0, 0, 1) /* yuva chroma */
+WRAP_CLEAR(1, 1, 0, 0) /* ayuv chroma */
+WRAP_CLEAR(0, 1, 0, 1) /* uyva chroma */
+WRAP_CLEAR(1, 0, 1, 0) /* xvyu chroma */
+
+WRAP_CLEAR(1, 0, 0, 0) /* gray -> yuva */
+WRAP_CLEAR(0, 1, 0, 0) /* gray -> ayuv */
+WRAP_CLEAR(0, 0, 1, 0) /* gray -> vuya */
+
+DECL_PATTERN(min)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ if (X)
+ x[i] = FFMIN(x[i], impl->priv.px[0]);
+ if (Y)
+ y[i] = FFMIN(y[i], impl->priv.px[1]);
+ if (Z)
+ z[i] = FFMIN(z[i], impl->priv.px[2]);
+ if (W)
+ w[i] = FFMIN(w[i], impl->priv.px[3]);
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+DECL_PATTERN(max)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ if (X)
+ x[i] = FFMAX(x[i], impl->priv.px[0]);
+ if (Y)
+ y[i] = FFMAX(y[i], impl->priv.px[1]);
+ if (Z)
+ z[i] = FFMAX(z[i], impl->priv.px[2]);
+ if (W)
+ w[i] = FFMAX(w[i], impl->priv.px[3]);
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+WRAP_COMMON_PATTERNS(min,
+ .op = SWS_OP_MIN,
+ .setup = ff_sws_setup_q4,
+ .flexible = true,
+);
+
+WRAP_COMMON_PATTERNS(max,
+ .op = SWS_OP_MAX,
+ .setup = ff_sws_setup_q4,
+ .flexible = true,
+);
+
+DECL_PATTERN(scale)
+{
+ const pixel_t scale = impl->priv.px[0];
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ if (X)
+ x[i] *= scale;
+ if (Y)
+ y[i] *= scale;
+ if (Z)
+ z[i] *= scale;
+ if (W)
+ w[i] *= scale;
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+WRAP_COMMON_PATTERNS(scale,
+ .op = SWS_OP_SCALE,
+ .setup = ff_sws_setup_q,
+ .flexible = true,
+);
diff --git a/libswscale/ops_tmpl_float.c b/libswscale/ops_tmpl_float.c
new file mode 100644
index 0000000000..345f9dd57b
--- /dev/null
+++ b/libswscale/ops_tmpl_float.c
@@ -0,0 +1,257 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "libavutil/avassert.h"
+
+#include "ops_backend.h"
+
+#ifndef BIT_DEPTH
+# define BIT_DEPTH 32
+#endif
+
+#if BIT_DEPTH == 32
+# define PIXEL_TYPE SWS_PIXEL_F32
+# define PIXEL_MAX FLT_MAX
+# define PIXEL_MIN FLT_MIN
+# define pixel_t float
+# define block_t f32block_t
+# define px f32
+#else
+# error Invalid BIT_DEPTH
+#endif
+
+#define IS_FLOAT 1
+#define FMT_CHAR f
+#include "ops_tmpl_common.c"
+
+DECL_SETUP(setup_dither)
+{
+ const int size = 1 << op->dither.size_log2;
+ if (!size) {
+ /* We special case this value */
+ av_assert1(!av_cmp_q(op->dither.matrix[0], av_make_q(1, 2)));
+ out->ptr = NULL;
+ return 0;
+ }
+
+ const int width = FFMAX(size, SWS_BLOCK_SIZE);
+ pixel_t *matrix = out->ptr = av_malloc(sizeof(pixel_t) * size * width);
+ if (!matrix)
+ return AVERROR(ENOMEM);
+
+ for (int y = 0; y < size; y++) {
+ for (int x = 0; x < size; x++)
+ matrix[y * width + x] = av_q2pixel(op->dither.matrix[y * size + x]);
+ for (int x = size; x < width; x++) /* pad to block size */
+ matrix[y * width + x] = matrix[y * width + (x % size)];
+ }
+
+ return 0;
+}
+
+DECL_FUNC(dither, const int size_log2)
+{
+ const pixel_t *restrict matrix = impl->priv.ptr;
+ const int mask = (1 << size_log2) - 1;
+ const int y_line = iter->y;
+ const int row0 = (y_line + 0) & mask;
+ const int row1 = (y_line + 3) & mask;
+ const int row2 = (y_line + 2) & mask;
+ const int row3 = (y_line + 5) & mask;
+ const int size = 1 << size_log2;
+ const int width = FFMAX(size, SWS_BLOCK_SIZE);
+ const int base = iter->x & ~(SWS_BLOCK_SIZE - 1) & (size - 1);
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ x[i] += size_log2 ? matrix[row0 * width + base + i] : (pixel_t) 0.5;
+ y[i] += size_log2 ? matrix[row1 * width + base + i] : (pixel_t) 0.5;
+ z[i] += size_log2 ? matrix[row2 * width + base + i] : (pixel_t) 0.5;
+ w[i] += size_log2 ? matrix[row3 * width + base + i] : (pixel_t) 0.5;
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+#define WRAP_DITHER(N) \
+DECL_IMPL(dither##N) \
+{ \
+ CALL(dither, N); \
+} \
+ \
+DECL_ENTRY(dither##N, \
+ .op = SWS_OP_DITHER, \
+ .dither_size = N, \
+ .setup = fn(setup_dither), \
+ .free = av_free, \
+);
+
+WRAP_DITHER(0)
+WRAP_DITHER(1)
+WRAP_DITHER(2)
+WRAP_DITHER(3)
+WRAP_DITHER(4)
+WRAP_DITHER(5)
+WRAP_DITHER(6)
+WRAP_DITHER(7)
+WRAP_DITHER(8)
+
+typedef struct {
+ /* Stored in split form for convenience */
+ pixel_t m[4][4];
+ pixel_t k[4];
+} fn(LinCoeffs);
+
+DECL_SETUP(setup_linear)
+{
+ fn(LinCoeffs) c;
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++)
+ c.m[i][j] = av_q2pixel(op->lin.m[i][j]);
+ c.k[i] = av_q2pixel(op->lin.m[i][4]);
+ }
+
+ return SETUP_MEMDUP(c);
+}
+
+/**
+ * Fully general case for a 5x5 linear affine transformation. Should never be
+ * called without constant `mask`. This function will compile down to the
+ * appropriately optimized version for the required subset of operations when
+ * called with a constant mask.
+ */
+DECL_FUNC(linear_mask, const uint32_t mask)
+{
+ const fn(LinCoeffs) c = *(const fn(LinCoeffs) *) impl->priv.ptr;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ const pixel_t xx = x[i];
+ const pixel_t yy = y[i];
+ const pixel_t zz = z[i];
+ const pixel_t ww = w[i];
+
+ x[i] = (mask & SWS_MASK_OFF(0)) ? c.k[0] : 0;
+ x[i] += (mask & SWS_MASK(0, 0)) ? c.m[0][0] * xx : xx;
+ x[i] += (mask & SWS_MASK(0, 1)) ? c.m[0][1] * yy : 0;
+ x[i] += (mask & SWS_MASK(0, 2)) ? c.m[0][2] * zz : 0;
+ x[i] += (mask & SWS_MASK(0, 3)) ? c.m[0][3] * ww : 0;
+
+ y[i] = (mask & SWS_MASK_OFF(1)) ? c.k[1] : 0;
+ y[i] += (mask & SWS_MASK(1, 0)) ? c.m[1][0] * xx : 0;
+ y[i] += (mask & SWS_MASK(1, 1)) ? c.m[1][1] * yy : yy;
+ y[i] += (mask & SWS_MASK(1, 2)) ? c.m[1][2] * zz : 0;
+ y[i] += (mask & SWS_MASK(1, 3)) ? c.m[1][3] * ww : 0;
+
+ z[i] = (mask & SWS_MASK_OFF(2)) ? c.k[2] : 0;
+ z[i] += (mask & SWS_MASK(2, 0)) ? c.m[2][0] * xx : 0;
+ z[i] += (mask & SWS_MASK(2, 1)) ? c.m[2][1] * yy : 0;
+ z[i] += (mask & SWS_MASK(2, 2)) ? c.m[2][2] * zz : zz;
+ z[i] += (mask & SWS_MASK(2, 3)) ? c.m[2][3] * ww : 0;
+
+ w[i] = (mask & SWS_MASK_OFF(3)) ? c.k[3] : 0;
+ w[i] += (mask & SWS_MASK(3, 0)) ? c.m[3][0] * xx : 0;
+ w[i] += (mask & SWS_MASK(3, 1)) ? c.m[3][1] * yy : 0;
+ w[i] += (mask & SWS_MASK(3, 2)) ? c.m[3][2] * zz : 0;
+ w[i] += (mask & SWS_MASK(3, 3)) ? c.m[3][3] * ww : ww;
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+#define WRAP_LINEAR(NAME, MASK) \
+DECL_IMPL(linear_##NAME) \
+{ \
+ CALL(linear_mask, MASK); \
+} \
+ \
+DECL_ENTRY(linear_##NAME, \
+ .op = SWS_OP_LINEAR, \
+ .setup = fn(setup_linear), \
+ .free = av_free, \
+ .linear_mask = (MASK), \
+);
+
+WRAP_LINEAR(luma, SWS_MASK_LUMA)
+WRAP_LINEAR(alpha, SWS_MASK_ALPHA)
+WRAP_LINEAR(lumalpha, SWS_MASK_LUMA | SWS_MASK_ALPHA)
+WRAP_LINEAR(dot3, 0x7)
+WRAP_LINEAR(row0, SWS_MASK_ROW(0))
+WRAP_LINEAR(row0a, SWS_MASK_ROW(0) | SWS_MASK_ALPHA)
+WRAP_LINEAR(diag3, SWS_MASK_DIAG3)
+WRAP_LINEAR(diag4, SWS_MASK_DIAG4)
+WRAP_LINEAR(diagoff3, SWS_MASK_DIAG3 | SWS_MASK_OFF3)
+WRAP_LINEAR(matrix3, SWS_MASK_MAT3)
+WRAP_LINEAR(affine3, SWS_MASK_MAT3 | SWS_MASK_OFF3)
+WRAP_LINEAR(affine3a, SWS_MASK_MAT3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA)
+WRAP_LINEAR(matrix4, SWS_MASK_MAT4)
+WRAP_LINEAR(affine4, SWS_MASK_MAT4 | SWS_MASK_OFF4)
+
+static const SwsOpTable fn(op_table_float) = {
+ .block_size = SWS_BLOCK_SIZE,
+ .entries = {
+ REF_COMMON_PATTERNS(convert_uint8),
+ REF_COMMON_PATTERNS(convert_uint16),
+ REF_COMMON_PATTERNS(convert_uint32),
+
+ &fn(op_clear_1110),
+ REF_COMMON_PATTERNS(min),
+ REF_COMMON_PATTERNS(max),
+ REF_COMMON_PATTERNS(scale),
+
+ &fn(op_dither0),
+ &fn(op_dither1),
+ &fn(op_dither2),
+ &fn(op_dither3),
+ &fn(op_dither4),
+ &fn(op_dither5),
+ &fn(op_dither6),
+ &fn(op_dither7),
+ &fn(op_dither8),
+
+ &fn(op_linear_luma),
+ &fn(op_linear_alpha),
+ &fn(op_linear_lumalpha),
+ &fn(op_linear_dot3),
+ &fn(op_linear_row0),
+ &fn(op_linear_row0a),
+ &fn(op_linear_diag3),
+ &fn(op_linear_diag4),
+ &fn(op_linear_diagoff3),
+ &fn(op_linear_matrix3),
+ &fn(op_linear_affine3),
+ &fn(op_linear_affine3a),
+ &fn(op_linear_matrix4),
+ &fn(op_linear_affine4),
+
+ NULL
+ },
+};
+
+#undef PIXEL_TYPE
+#undef PIXEL_MAX
+#undef PIXEL_MIN
+#undef pixel_t
+#undef block_t
+#undef px
+
+#undef FMT_CHAR
+#undef IS_FLOAT
diff --git a/libswscale/ops_tmpl_int.c b/libswscale/ops_tmpl_int.c
new file mode 100644
index 0000000000..0337ae5708
--- /dev/null
+++ b/libswscale/ops_tmpl_int.c
@@ -0,0 +1,603 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "libavutil/avassert.h"
+#include "libavutil/bswap.h"
+
+#include "ops_backend.h"
+
+#ifndef BIT_DEPTH
+# define BIT_DEPTH 8
+#endif
+
+#if BIT_DEPTH == 32
+# define PIXEL_TYPE SWS_PIXEL_U32
+# define PIXEL_MAX 0xFFFFFFFFu
+# define SWAP_BYTES av_bswap32
+# define pixel_t uint32_t
+# define block_t u32block_t
+# define px u32
+#elif BIT_DEPTH == 16
+# define PIXEL_TYPE SWS_PIXEL_U16
+# define PIXEL_MAX 0xFFFFu
+# define SWAP_BYTES av_bswap16
+# define pixel_t uint16_t
+# define block_t u16block_t
+# define px u16
+#elif BIT_DEPTH == 8
+# define PIXEL_TYPE SWS_PIXEL_U8
+# define PIXEL_MAX 0xFFu
+# define pixel_t uint8_t
+# define block_t u8block_t
+# define px u8
+#else
+# error Invalid BIT_DEPTH
+#endif
+
+#define IS_FLOAT 0
+#define FMT_CHAR u
+#define PIXEL_MIN 0
+#include "ops_tmpl_common.c"
+
+DECL_READ(read_planar, const int elems)
+{
+ block_t x, y, z, w;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ x[i] = in0[i];
+ if (elems > 1)
+ y[i] = in1[i];
+ if (elems > 2)
+ z[i] = in2[i];
+ if (elems > 3)
+ w[i] = in3[i];
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+DECL_READ(read_packed, const int elems)
+{
+ block_t x, y, z, w;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ x[i] = in0[elems * i + 0];
+ if (elems > 1)
+ y[i] = in0[elems * i + 1];
+ if (elems > 2)
+ z[i] = in0[elems * i + 2];
+ if (elems > 3)
+ w[i] = in0[elems * i + 3];
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+DECL_WRITE(write_planar, const int elems)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ out0[i] = x[i];
+ if (elems > 1)
+ out1[i] = y[i];
+ if (elems > 2)
+ out2[i] = z[i];
+ if (elems > 3)
+ out3[i] = w[i];
+ }
+}
+
+DECL_WRITE(write_packed, const int elems)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ out0[elems * i + 0] = x[i];
+ if (elems > 1)
+ out0[elems * i + 1] = y[i];
+ if (elems > 2)
+ out0[elems * i + 2] = z[i];
+ if (elems > 3)
+ out0[elems * i + 3] = w[i];
+ }
+}
+
+#define WRAP_READ(FUNC, ELEMS, FRAC, PACKED) \
+DECL_IMPL(FUNC##ELEMS) \
+{ \
+ CALL_READ(FUNC, ELEMS); \
+ for (int i = 0; i < (PACKED ? 1 : ELEMS); i++) \
+ iter->in[i] += sizeof(block_t) * (PACKED ? ELEMS : 1) >> FRAC; \
+} \
+ \
+DECL_ENTRY(FUNC##ELEMS, \
+ .op = SWS_OP_READ, \
+ .rw = { \
+ .elems = ELEMS, \
+ .packed = PACKED, \
+ .frac = FRAC, \
+ }, \
+);
+
+WRAP_READ(read_planar, 1, 0, false)
+WRAP_READ(read_planar, 2, 0, false)
+WRAP_READ(read_planar, 3, 0, false)
+WRAP_READ(read_planar, 4, 0, false)
+WRAP_READ(read_packed, 2, 0, true)
+WRAP_READ(read_packed, 3, 0, true)
+WRAP_READ(read_packed, 4, 0, true)
+
+#define WRAP_WRITE(FUNC, ELEMS, FRAC, PACKED) \
+DECL_IMPL(FUNC##ELEMS) \
+{ \
+ CALL_WRITE(FUNC, ELEMS); \
+ for (int i = 0; i < (PACKED ? 1 : ELEMS); i++) \
+ iter->out[i] += sizeof(block_t) * (PACKED ? ELEMS : 1) >> FRAC; \
+} \
+ \
+DECL_ENTRY(FUNC##ELEMS, \
+ .op = SWS_OP_WRITE, \
+ .rw = { \
+ .elems = ELEMS, \
+ .packed = PACKED, \
+ .frac = FRAC, \
+ }, \
+);
+
+WRAP_WRITE(write_planar, 1, 0, false)
+WRAP_WRITE(write_planar, 2, 0, false)
+WRAP_WRITE(write_planar, 3, 0, false)
+WRAP_WRITE(write_planar, 4, 0, false)
+WRAP_WRITE(write_packed, 2, 0, true)
+WRAP_WRITE(write_packed, 3, 0, true)
+WRAP_WRITE(write_packed, 4, 0, true)
+
+#if BIT_DEPTH == 8
+DECL_READ(read_nibbles, const int elems)
+{
+ block_t x, y, z, w;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i += 2) {
+ const pixel_t val = ((const pixel_t *) in0)[i >> 1];
+ x[i + 0] = val >> 4; /* high nibble */
+ x[i + 1] = val & 0xF; /* low nibble */
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+DECL_READ(read_bits, const int elems)
+{
+ block_t x, y, z, w;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i += 8) {
+ const pixel_t val = ((const pixel_t *) in0)[i >> 3];
+ x[i + 0] = (val >> 7) & 1;
+ x[i + 1] = (val >> 6) & 1;
+ x[i + 2] = (val >> 5) & 1;
+ x[i + 3] = (val >> 4) & 1;
+ x[i + 4] = (val >> 3) & 1;
+ x[i + 5] = (val >> 2) & 1;
+ x[i + 6] = (val >> 1) & 1;
+ x[i + 7] = (val >> 0) & 1;
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+WRAP_READ(read_nibbles, 1, 1, false)
+WRAP_READ(read_bits, 1, 3, false)
+
+DECL_WRITE(write_nibbles, const int elems)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i += 2)
+ out0[i >> 1] = x[i] << 4 | x[i + 1];
+}
+
+DECL_WRITE(write_bits, const int elems)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i += 8) {
+ out0[i >> 3] = x[i + 0] << 7 |
+ x[i + 1] << 6 |
+ x[i + 2] << 5 |
+ x[i + 3] << 4 |
+ x[i + 4] << 3 |
+ x[i + 5] << 2 |
+ x[i + 6] << 1 |
+ x[i + 7];
+ }
+}
+
+WRAP_WRITE(write_nibbles, 1, 1, false)
+WRAP_WRITE(write_bits, 1, 3, false)
+#endif /* BIT_DEPTH == 8 */
+
+#ifdef SWAP_BYTES
+DECL_PATTERN(swap_bytes)
+{
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ if (X)
+ x[i] = SWAP_BYTES(x[i]);
+ if (Y)
+ y[i] = SWAP_BYTES(y[i]);
+ if (Z)
+ z[i] = SWAP_BYTES(z[i]);
+ if (W)
+ w[i] = SWAP_BYTES(w[i]);
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+WRAP_COMMON_PATTERNS(swap_bytes, .op = SWS_OP_SWAP_BYTES);
+#endif /* SWAP_BYTES */
+
+#if BIT_DEPTH == 8
+DECL_PATTERN(expand16)
+{
+ u16block_t x16, y16, z16, w16;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ if (X)
+ x16[i] = x[i] << 8 | x[i];
+ if (Y)
+ y16[i] = y[i] << 8 | y[i];
+ if (Z)
+ z16[i] = z[i] << 8 | z[i];
+ if (W)
+ w16[i] = w[i] << 8 | w[i];
+ }
+
+ CONTINUE(u16block_t, x16, y16, z16, w16);
+}
+
+WRAP_COMMON_PATTERNS(expand16,
+ .op = SWS_OP_CONVERT,
+ .convert.to = SWS_PIXEL_U16,
+ .convert.expand = true,
+);
+
+DECL_PATTERN(expand32)
+{
+ u32block_t x32, y32, z32, w32;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ x32[i] = x[i] << 24 | x[i] << 16 | x[i] << 8 | x[i];
+ y32[i] = y[i] << 24 | y[i] << 16 | y[i] << 8 | y[i];
+ z32[i] = z[i] << 24 | z[i] << 16 | z[i] << 8 | z[i];
+ w32[i] = w[i] << 24 | w[i] << 16 | w[i] << 8 | w[i];
+ }
+
+ CONTINUE(u32block_t, x32, y32, z32, w32);
+}
+
+WRAP_COMMON_PATTERNS(expand32,
+ .op = SWS_OP_CONVERT,
+ .convert.to = SWS_PIXEL_U32,
+ .convert.expand = true,
+);
+#endif
+
+#define WRAP_PACK_UNPACK(X, Y, Z, W) \
+inline DECL_IMPL(pack_##X##Y##Z##W) \
+{ \
+ SWS_LOOP \
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) { \
+ x[i] = x[i] << (Y+Z+W); \
+ if (Y) \
+ x[i] |= y[i] << (Z+W); \
+ if (Z) \
+ x[i] |= z[i] << W; \
+ if (W) \
+ x[i] |= w[i]; \
+ } \
+ \
+ CONTINUE(block_t, x, y, z, w); \
+} \
+ \
+DECL_ENTRY(pack_##X##Y##Z##W, \
+ .op = SWS_OP_PACK, \
+ .pack.pattern = { X, Y, Z, W }, \
+); \
+ \
+inline DECL_IMPL(unpack_##X##Y##Z##W) \
+{ \
+ SWS_LOOP \
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) { \
+ const pixel_t val = x[i]; \
+ x[i] = val >> (Y+Z+W); \
+ if (Y) \
+ y[i] = (val >> (Z+W)) & ((1 << Y) - 1); \
+ if (Z) \
+ z[i] = (val >> W) & ((1 << Z) - 1); \
+ if (W) \
+ w[i] = val & ((1 << W) - 1); \
+ } \
+ \
+ CONTINUE(block_t, x, y, z, w); \
+} \
+ \
+DECL_ENTRY(unpack_##X##Y##Z##W, \
+ .op = SWS_OP_UNPACK, \
+ .pack.pattern = { X, Y, Z, W }, \
+);
+
+WRAP_PACK_UNPACK( 3, 3, 2, 0)
+WRAP_PACK_UNPACK( 2, 3, 3, 0)
+WRAP_PACK_UNPACK( 1, 2, 1, 0)
+WRAP_PACK_UNPACK( 5, 6, 5, 0)
+WRAP_PACK_UNPACK( 5, 5, 5, 0)
+WRAP_PACK_UNPACK( 4, 4, 4, 0)
+WRAP_PACK_UNPACK( 2, 10, 10, 10)
+WRAP_PACK_UNPACK(10, 10, 10, 2)
+
+#if BIT_DEPTH != 8
+DECL_PATTERN(lshift)
+{
+ const uint8_t amount = impl->priv.u8[0];
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ x[i] <<= amount;
+ y[i] <<= amount;
+ z[i] <<= amount;
+ w[i] <<= amount;
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+DECL_PATTERN(rshift)
+{
+ const uint8_t amount = impl->priv.u8[0];
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ x[i] >>= amount;
+ y[i] >>= amount;
+ z[i] >>= amount;
+ w[i] >>= amount;
+ }
+
+ CONTINUE(block_t, x, y, z, w);
+}
+
+WRAP_COMMON_PATTERNS(lshift,
+ .op = SWS_OP_LSHIFT,
+ .setup = ff_sws_setup_u8,
+ .flexible = true,
+);
+
+WRAP_COMMON_PATTERNS(rshift,
+ .op = SWS_OP_RSHIFT,
+ .setup = ff_sws_setup_u8,
+ .flexible = true,
+);
+#endif /* BIT_DEPTH != 8 */
+
+DECL_PATTERN(convert_float)
+{
+ f32block_t xf, yf, zf, wf;
+
+ SWS_LOOP
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) {
+ xf[i] = x[i];
+ yf[i] = y[i];
+ zf[i] = z[i];
+ wf[i] = w[i];
+ }
+
+ CONTINUE(f32block_t, xf, yf, zf, wf);
+}
+
+WRAP_COMMON_PATTERNS(convert_float,
+ .op = SWS_OP_CONVERT,
+ .convert.to = SWS_PIXEL_F32,
+);
+
+/**
+ * Swizzle by directly swapping the order of arguments to the continuation.
+ * Note that this is only safe to do if no arguments are duplicated.
+ */
+#define DECL_SWIZZLE(X, Y, Z, W) \
+static SWS_FUNC void \
+fn(swizzle_##X##Y##Z##W)(SwsOpIter *restrict iter, \
+ const SwsOpImpl *restrict impl, \
+ block_t c0, block_t c1, block_t c2, block_t c3) \
+{ \
+ CONTINUE(block_t, c##X, c##Y, c##Z, c##W); \
+} \
+ \
+DECL_ENTRY(swizzle_##X##Y##Z##W, \
+ .op = SWS_OP_SWIZZLE, \
+ .swizzle.in = { X, Y, Z, W }, \
+);
+
+DECL_SWIZZLE(3, 0, 1, 2)
+DECL_SWIZZLE(3, 0, 2, 1)
+DECL_SWIZZLE(2, 1, 0, 3)
+DECL_SWIZZLE(3, 2, 1, 0)
+DECL_SWIZZLE(3, 1, 0, 2)
+DECL_SWIZZLE(3, 2, 0, 1)
+DECL_SWIZZLE(1, 2, 0, 3)
+DECL_SWIZZLE(1, 0, 2, 3)
+DECL_SWIZZLE(2, 0, 1, 3)
+DECL_SWIZZLE(2, 3, 1, 0)
+DECL_SWIZZLE(2, 1, 3, 0)
+DECL_SWIZZLE(1, 2, 3, 0)
+DECL_SWIZZLE(1, 3, 2, 0)
+DECL_SWIZZLE(0, 2, 1, 3)
+DECL_SWIZZLE(0, 2, 3, 1)
+DECL_SWIZZLE(0, 3, 1, 2)
+DECL_SWIZZLE(3, 1, 2, 0)
+DECL_SWIZZLE(0, 3, 2, 1)
+
+/* Broadcast luma -> rgb (only used for y(a) -> rgb(a)) */
+#define DECL_EXPAND_LUMA(X, W, T0, T1) \
+static SWS_FUNC void \
+fn(expand_luma_##X##W)(SwsOpIter *restrict iter, \
+ const SwsOpImpl *restrict impl, \
+ block_t c0, block_t c1, block_t c2, block_t c3) \
+{ \
+ SWS_LOOP \
+ for (int i = 0; i < SWS_BLOCK_SIZE; i++) \
+ T0[i] = T1[i] = c0[i]; \
+ \
+ CONTINUE(block_t, c##X, T0, T1, c##W); \
+} \
+ \
+DECL_ENTRY(expand_luma_##X##W, \
+ .op = SWS_OP_SWIZZLE, \
+ .swizzle.in = { X, 0, 0, W }, \
+);
+
+DECL_EXPAND_LUMA(0, 3, c1, c2)
+DECL_EXPAND_LUMA(3, 0, c1, c2)
+DECL_EXPAND_LUMA(1, 0, c2, c3)
+DECL_EXPAND_LUMA(0, 1, c2, c3)
+
+static const SwsOpTable fn(op_table_int) = {
+ .block_size = SWS_BLOCK_SIZE,
+ .entries = {
+ &fn(op_read_planar1),
+ &fn(op_read_planar2),
+ &fn(op_read_planar3),
+ &fn(op_read_planar4),
+ &fn(op_read_packed2),
+ &fn(op_read_packed3),
+ &fn(op_read_packed4),
+
+ &fn(op_write_planar1),
+ &fn(op_write_planar2),
+ &fn(op_write_planar3),
+ &fn(op_write_planar4),
+ &fn(op_write_packed2),
+ &fn(op_write_packed3),
+ &fn(op_write_packed4),
+
+#if BIT_DEPTH == 8
+ &fn(op_read_bits1),
+ &fn(op_read_nibbles1),
+ &fn(op_write_bits1),
+ &fn(op_write_nibbles1),
+
+ &fn(op_pack_1210),
+ &fn(op_pack_2330),
+ &fn(op_pack_3320),
+
+ &fn(op_unpack_1210),
+ &fn(op_unpack_2330),
+ &fn(op_unpack_3320),
+
+ REF_COMMON_PATTERNS(expand16),
+ REF_COMMON_PATTERNS(expand32),
+#elif BIT_DEPTH == 16
+ &fn(op_pack_4440),
+ &fn(op_pack_5550),
+ &fn(op_pack_5650),
+ &fn(op_unpack_4440),
+ &fn(op_unpack_5550),
+ &fn(op_unpack_5650),
+#elif BIT_DEPTH == 32
+ &fn(op_pack_2101010),
+ &fn(op_pack_1010102),
+ &fn(op_unpack_2101010),
+ &fn(op_unpack_1010102),
+#endif
+
+#ifdef SWAP_BYTES
+ REF_COMMON_PATTERNS(swap_bytes),
+#endif
+
+ REF_COMMON_PATTERNS(min),
+ REF_COMMON_PATTERNS(max),
+ REF_COMMON_PATTERNS(scale),
+ REF_COMMON_PATTERNS(convert_float),
+
+ &fn(op_clear_1110),
+ &fn(op_clear_0111),
+ &fn(op_clear_0011),
+ &fn(op_clear_1001),
+ &fn(op_clear_1100),
+ &fn(op_clear_0101),
+ &fn(op_clear_1010),
+ &fn(op_clear_1000),
+ &fn(op_clear_0100),
+ &fn(op_clear_0010),
+
+ &fn(op_swizzle_3012),
+ &fn(op_swizzle_3021),
+ &fn(op_swizzle_2103),
+ &fn(op_swizzle_3210),
+ &fn(op_swizzle_3102),
+ &fn(op_swizzle_3201),
+ &fn(op_swizzle_1203),
+ &fn(op_swizzle_1023),
+ &fn(op_swizzle_2013),
+ &fn(op_swizzle_2310),
+ &fn(op_swizzle_2130),
+ &fn(op_swizzle_1230),
+ &fn(op_swizzle_1320),
+ &fn(op_swizzle_0213),
+ &fn(op_swizzle_0231),
+ &fn(op_swizzle_0312),
+ &fn(op_swizzle_3120),
+ &fn(op_swizzle_0321),
+
+ &fn(op_expand_luma_03),
+ &fn(op_expand_luma_30),
+ &fn(op_expand_luma_10),
+ &fn(op_expand_luma_01),
+
+#if BIT_DEPTH != 8
+ REF_COMMON_PATTERNS(lshift),
+ REF_COMMON_PATTERNS(rshift),
+ REF_COMMON_PATTERNS(convert_uint8),
+#endif /* BIT_DEPTH != 8 */
+
+#if BIT_DEPTH != 16
+ REF_COMMON_PATTERNS(convert_uint16),
+#endif
+#if BIT_DEPTH != 32
+ REF_COMMON_PATTERNS(convert_uint32),
+#endif
+
+ NULL
+ },
+};
+
+#undef PIXEL_TYPE
+#undef PIXEL_MAX
+#undef PIXEL_MIN
+#undef SWAP_BYTES
+#undef pixel_t
+#undef block_t
+#undef px
+
+#undef FMT_CHAR
+#undef IS_FLOAT
commit 99d73064f5ad08bfa25e5de531024f9890a7c9a3
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Sun May 18 16:58:11 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/ops_chain: add internal abstraction for kernel linking
See doc/swscale-v2.txt for design details.
diff --git a/libswscale/Makefile b/libswscale/Makefile
index 810c9dee78..c9dfa78c89 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -16,6 +16,7 @@ OBJS = alphablend.o \
input.o \
lut3d.o \
ops.o \
+ ops_chain.o \
ops_optimizer.o \
options.o \
output.o \
diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c
new file mode 100644
index 0000000000..5d138a9e46
--- /dev/null
+++ b/libswscale/ops_chain.c
@@ -0,0 +1,291 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "libavutil/avassert.h"
+#include "libavutil/mem.h"
+#include "libavutil/rational.h"
+
+#include "ops_chain.h"
+
+#define Q(N) ((AVRational) { N, 1 })
+
+SwsOpChain *ff_sws_op_chain_alloc(void)
+{
+ return av_mallocz(sizeof(SwsOpChain));
+}
+
+void ff_sws_op_chain_free(SwsOpChain *chain)
+{
+ if (!chain)
+ return;
+
+ for (int i = 0; i < chain->num_impl + 1; i++) {
+ if (chain->free[i])
+ chain->free[i](chain->impl[i].priv.ptr);
+ }
+
+ av_free(chain);
+}
+
+int ff_sws_op_chain_append(SwsOpChain *chain, SwsFuncPtr func,
+ void (*free)(void *), SwsOpPriv priv)
+{
+ const int idx = chain->num_impl;
+ if (idx == SWS_MAX_OPS)
+ return AVERROR(EINVAL);
+
+ av_assert1(func);
+ chain->impl[idx].cont = func;
+ chain->impl[idx + 1].priv = priv;
+ chain->free[idx + 1] = free;
+ chain->num_impl++;
+ return 0;
+}
+
+/**
+ * Match an operation against a reference operation. Returns a score for how
+ * well the reference matches the operation, or 0 if there is no match.
+ *
+ * If `ref->comps` has any flags set, they must be set in `op` as well.
+ * Likewise, if `ref->comps` has any components marked as unused, they must be
+ * marked as as unused in `ops` as well.
+ *
+ * For SWS_OP_LINEAR, `ref->linear.mask` must be a strict superset of
+ * `op->linear.mask`, but may not contain any columns explicitly ignored by
+ * `op->comps.unused`.
+ *
+ * For SWS_OP_READ, SWS_OP_WRITE, SWS_OP_SWAP_BYTES and SWS_OP_SWIZZLE, the
+ * exact type is not checked, just the size.
+ *
+ * Components set in `next.unused` are ignored when matching. If `flexible`
+ * is true, the op body is ignored - only the operation, pixel type, and
+ * component masks are checked.
+ */
+static int op_match(const SwsOp *op, const SwsOpEntry *entry, const SwsComps next)
+{
+ int score = 10;
+ if (op->op != entry->op)
+ return 0;
+
+ switch (op->op) {
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ case SWS_OP_SWAP_BYTES:
+ case SWS_OP_SWIZZLE:
+ /* Only the size matters for these operations */
+ if (ff_sws_pixel_type_size(op->type) != ff_sws_pixel_type_size(entry->type))
+ return 0;
+ break;
+ default:
+ if (op->type != entry->type)
+ return 0;
+ break;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (entry->unused[i]) {
+ if (op->comps.unused[i])
+ score += 1; /* Operating on fewer components is better .. */
+ else
+ return 0; /* .. but not too few! */
+ }
+ }
+
+ if (op->op == SWS_OP_CLEAR) {
+ /* Clear pattern must match exactly, regardless of `entry->flexible` */
+ for (int i = 0; i < 4; i++) {
+ if (!next.unused[i] && entry->unused[i] != !!op->c.q4[i].den)
+ return 0;
+ }
+ }
+
+ /* Flexible variants always match, but lower the score to prioritize more
+ * specific implementations if they exist */
+ if (entry->flexible)
+ return score - 5;
+
+ switch (op->op) {
+ case SWS_OP_INVALID:
+ return 0;
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ if (op->rw.elems != entry->rw.elems ||
+ op->rw.frac != entry->rw.frac ||
+ (op->rw.elems > 1 && op->rw.packed != entry->rw.packed))
+ return 0;
+ return score;
+ case SWS_OP_SWAP_BYTES:
+ return score;
+ case SWS_OP_PACK:
+ case SWS_OP_UNPACK:
+ for (int i = 0; i < 4 && op->pack.pattern[i]; i++) {
+ if (op->pack.pattern[i] != entry->pack.pattern[i])
+ return 0;
+ }
+ return score;
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (!op->c.q4[i].den)
+ continue;
+ if (av_cmp_q(op->c.q4[i], Q(entry->clear_value)) && !next.unused[i])
+ return 0;
+ }
+ return score;
+ case SWS_OP_LSHIFT:
+ case SWS_OP_RSHIFT:
+ av_assert1(entry->flexible);
+ return score;
+ case SWS_OP_SWIZZLE:
+ for (int i = 0; i < 4; i++) {
+ if (op->swizzle.in[i] != entry->swizzle.in[i] && !next.unused[i])
+ return 0;
+ }
+ return score;
+ case SWS_OP_CONVERT:
+ if (op->convert.to != entry->convert.to ||
+ op->convert.expand != entry->convert.expand)
+ return 0;
+ return score;
+ case SWS_OP_DITHER:
+ return op->dither.size_log2 == entry->dither_size ? score : 0;
+ case SWS_OP_MIN:
+ case SWS_OP_MAX:
+ av_assert1(entry->flexible);
+ return score;
+ case SWS_OP_LINEAR:
+ /* All required elements must be present */
+ if (op->lin.mask & ~entry->linear_mask)
+ return 0;
+ /* To avoid operating on possibly undefined memory, filter out
+ * implementations that operate on more input components */
+ for (int i = 0; i < 4; i++) {
+ if ((entry->linear_mask & SWS_MASK_COL(i)) && op->comps.unused[i])
+ return 0;
+ }
+ /* Prioritize smaller implementations */
+ score += av_popcount(SWS_MASK_ALL ^ entry->linear_mask);
+ return score;
+ case SWS_OP_SCALE:
+ return score;
+ case SWS_OP_TYPE_NB:
+ break;
+ }
+
+ av_unreachable("Invalid operation type!");
+ return 0;
+}
+
+int ff_sws_op_compile_tables(const SwsOpTable *const tables[], int num_tables,
+ SwsOpList *ops, const int block_size,
+ SwsOpChain *chain)
+{
+ static const SwsOp dummy = { .comps.unused = { true, true, true, true }};
+ const SwsOp *next = ops->num_ops > 1 ? &ops->ops[1] : &dummy;
+ const unsigned cpu_flags = av_get_cpu_flags();
+ const SwsOpEntry *best = NULL;
+ const SwsOp *op = &ops->ops[0];
+ int ret, best_score = 0, best_cpu_flags;
+ SwsOpPriv priv = {0};
+
+ for (int n = 0; n < num_tables; n++) {
+ const SwsOpTable *table = tables[n];
+ if (table->block_size && table->block_size != block_size ||
+ table->cpu_flags & ~cpu_flags)
+ continue;
+
+ for (int i = 0; table->entries[i]; i++) {
+ const SwsOpEntry *entry = table->entries[i];
+ int score = op_match(op, entry, next->comps);
+ if (score > best_score) {
+ best_score = score;
+ best_cpu_flags = table->cpu_flags;
+ best = entry;
+ }
+ }
+ }
+
+ if (!best)
+ return AVERROR(ENOTSUP);
+
+ if (best->setup) {
+ ret = best->setup(op, &priv);
+ if (ret < 0)
+ return ret;
+ }
+
+ chain->cpu_flags |= best_cpu_flags;
+ ret = ff_sws_op_chain_append(chain, best->func, best->free, priv);
+ if (ret < 0) {
+ if (best->free)
+ best->free(&priv);
+ return ret;
+ }
+
+ ops->ops++;
+ ops->num_ops--;
+ return ops->num_ops ? AVERROR(EAGAIN) : 0;
+}
+
+#define q2pixel(type, q) ((q).den ? (type) (q).num / (q).den : 0)
+
+int ff_sws_setup_u8(const SwsOp *op, SwsOpPriv *out)
+{
+ out->u8[0] = op->c.u;
+ return 0;
+}
+
+int ff_sws_setup_u(const SwsOp *op, SwsOpPriv *out)
+{
+ switch (op->type) {
+ case SWS_PIXEL_U8: out->u8[0] = op->c.u; return 0;
+ case SWS_PIXEL_U16: out->u16[0] = op->c.u; return 0;
+ case SWS_PIXEL_U32: out->u32[0] = op->c.u; return 0;
+ case SWS_PIXEL_F32: out->f32[0] = op->c.u; return 0;
+ default: return AVERROR(EINVAL);
+ }
+}
+
+int ff_sws_setup_q(const SwsOp *op, SwsOpPriv *out)
+{
+ switch (op->type) {
+ case SWS_PIXEL_U8: out->u8[0] = q2pixel(uint8_t, op->c.q); return 0;
+ case SWS_PIXEL_U16: out->u16[0] = q2pixel(uint16_t, op->c.q); return 0;
+ case SWS_PIXEL_U32: out->u32[0] = q2pixel(uint32_t, op->c.q); return 0;
+ case SWS_PIXEL_F32: out->f32[0] = q2pixel(float, op->c.q); return 0;
+ default: return AVERROR(EINVAL);
+ }
+
+ return 0;
+}
+
+int ff_sws_setup_q4(const SwsOp *op, SwsOpPriv *out)
+{
+ for (int i = 0; i < 4; i++) {
+ switch (op->type) {
+ case SWS_PIXEL_U8: out->u8[i] = q2pixel(uint8_t, op->c.q4[i]); break;
+ case SWS_PIXEL_U16: out->u16[i] = q2pixel(uint16_t, op->c.q4[i]); break;
+ case SWS_PIXEL_U32: out->u32[i] = q2pixel(uint32_t, op->c.q4[i]); break;
+ case SWS_PIXEL_F32: out->f32[i] = q2pixel(float, op->c.q4[i]); break;
+ default: return AVERROR(EINVAL);
+ }
+ }
+
+ return 0;
+}
diff --git a/libswscale/ops_chain.h b/libswscale/ops_chain.h
new file mode 100644
index 0000000000..777d8e13c1
--- /dev/null
+++ b/libswscale/ops_chain.h
@@ -0,0 +1,134 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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
+ */
+
+#ifndef SWSCALE_OPS_CHAIN_H
+#define SWSCALE_OPS_CHAIN_H
+
+#include "libavutil/cpu.h"
+
+#include "ops_internal.h"
+
+/**
+ * Helpers for SIMD implementations based on chained kernels, using a
+ * continuation passing style to link them together.
+ *
+ * The basic idea here is to "link" together a series of different operation
+ * kernels by constructing a list of kernel addresses into an SwsOpChain. Each
+ * kernel will load the address of the next kernel (the "continuation") from
+ * this struct, and jump directly into it; using an internal function signature
+ * that is an implementation detail of the specific backend.
+ */
+
+/**
+ * Private data for each kernel.
+ */
+typedef union SwsOpPriv {
+ DECLARE_ALIGNED_16(char, data)[16];
+
+ /* Common types */
+ void *ptr;
+ uint8_t u8[16];
+ uint16_t u16[8];
+ uint32_t u32[4];
+ float f32[4];
+} SwsOpPriv;
+
+static_assert(sizeof(SwsOpPriv) == 16, "SwsOpPriv size mismatch");
+
+/* Setup helpers */
+int ff_sws_setup_u(const SwsOp *op, SwsOpPriv *out);
+int ff_sws_setup_u8(const SwsOp *op, SwsOpPriv *out);
+int ff_sws_setup_q(const SwsOp *op, SwsOpPriv *out);
+int ff_sws_setup_q4(const SwsOp *op, SwsOpPriv *out);
+
+/**
+ * Per-kernel execution context.
+ *
+ * Note: This struct is hard-coded in assembly, so do not change the layout.
+ */
+typedef void (*SwsFuncPtr)(void);
+typedef struct SwsOpImpl {
+ SwsFuncPtr cont; /* [offset = 0] Continuation for this operation. */
+ SwsOpPriv priv; /* [offset = 16] Private data for this operation. */
+} SwsOpImpl;
+
+static_assert(sizeof(SwsOpImpl) == 32, "SwsOpImpl layout mismatch");
+static_assert(offsetof(SwsOpImpl, priv) == 16, "SwsOpImpl layout mismatch");
+
+/**
+ * Compiled "chain" of operations, which can be dispatched efficiently.
+ * Effectively just a list of function pointers, alongside a small amount of
+ * private data for each operation.
+ */
+typedef struct SwsOpChain {
+#define SWS_MAX_OPS 16
+ SwsOpImpl impl[SWS_MAX_OPS + 1]; /* reserve extra space for the entrypoint */
+ void (*free[SWS_MAX_OPS + 1])(void *);
+ int num_impl;
+ int cpu_flags; /* set of all used CPU flags */
+} SwsOpChain;
+
+SwsOpChain *ff_sws_op_chain_alloc(void);
+void ff_sws_op_chain_free(SwsOpChain *chain);
+
+/* Returns 0 on success, or a negative error code. */
+int ff_sws_op_chain_append(SwsOpChain *chain, SwsFuncPtr func,
+ void (*free)(void *), SwsOpPriv priv);
+
+typedef struct SwsOpEntry {
+ /* Kernel metadata; reduced size subset of SwsOp */
+ SwsOpType op;
+ SwsPixelType type;
+ bool flexible; /* if true, only the type and op are matched */
+ bool unused[4]; /* for kernels which operate on a subset of components */
+
+ union { /* extra data defining the operation, unless `flexible` is true */
+ SwsReadWriteOp rw;
+ SwsPackOp pack;
+ SwsSwizzleOp swizzle;
+ SwsConvertOp convert;
+ uint32_t linear_mask; /* subset of SwsLinearOp */
+ int dither_size; /* subset of SwsDitherOp */
+ int clear_value; /* clear value for integer clears */
+ };
+
+ /* Kernel implementation */
+ SwsFuncPtr func;
+ int (*setup)(const SwsOp *op, SwsOpPriv *out); /* optional */
+ void (*free)(void *priv);
+} SwsOpEntry;
+
+typedef struct SwsOpTable {
+ unsigned cpu_flags; /* required CPU flags for this table */
+ int block_size; /* fixed block size of this table */
+ const SwsOpEntry *entries[]; /* terminated by NULL */
+} SwsOpTable;
+
+/**
+ * "Compile" a single op by looking it up in a list of fixed size op tables.
+ * See `op_match` in `ops.c` for details on how the matching works.
+ *
+ * Returns 0, AVERROR(EAGAIN), or a negative error code.
+ */
+int ff_sws_op_compile_tables(const SwsOpTable *const tables[], int num_tables,
+ SwsOpList *ops, const int block_size,
+ SwsOpChain *chain);
+
+#endif
commit 696f3be322ae7d758a8e52e771bade54774c9f54
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Wed May 21 13:49:37 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/optimizer: add packed shuffle solver
This can turn any compatible sequence of operations into a single packed
shuffle, including packed swizzling, grayscale->RGB conversion, endianness
swapping, RGB bit depth conversions, rgb24->rgb0 alpha clearing and more.
diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h
index 62c0f46bc6..0071f78558 100644
--- a/libswscale/ops_internal.h
+++ b/libswscale/ops_internal.h
@@ -130,4 +130,32 @@ int ff_sws_ops_compile_backend(SwsContext *ctx, const SwsOpBackend *backend,
*/
int ff_sws_ops_compile(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out);
+/**
+ * "Solve" an op list into a fixed shuffle mask, with an optional ability to
+ * also directly clear the output value (for e.g. rgb24 -> rgb0). This can
+ * accept any operation chain that only consists of the following operations:
+ *
+ * - SWS_OP_READ (non-planar, non-fractional)
+ * - SWS_OP_SWIZZLE
+ * - SWS_OP_SWAP_BYTES
+ * - SWS_OP_CLEAR to zero (when clear_val is specified)
+ * - SWS_OP_CONVERT (integer expand)
+ * - SWS_OP_WRITE (non-planar, non-fractional)
+ *
+ * Basically, any operation that purely consists of moving around and reordering
+ * bytes within a single plane, can be turned into a shuffle mask.
+ *
+ * @param ops The operation list to decompose.
+ * @param shuffle The output shuffle mask.
+ * @param size The size (in bytes) of the output shuffle mask.
+ * @param clear_val If nonzero, this index will be used to clear the output.
+ * @param read_bytes Returns the number of bytes read per shuffle iteration.
+ * @param write_bytes Returns the number of bytes written per shuffle iteration.
+ *
+ * @return The number of pixels processed per iteration, or a negative error
+ code; in particular AVERROR(ENOTSUP) for unsupported operations.
+ */
+int ff_sws_solve_shuffle(const SwsOpList *ops, uint8_t shuffle[], int size,
+ uint8_t clear_val, int *read_bytes, int *write_bytes);
+
#endif /* SWSCALE_OPS_INTERNAL_H */
diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c
index 0a744a0c49..c28c2c36bb 100644
--- a/libswscale/ops_optimizer.c
+++ b/libswscale/ops_optimizer.c
@@ -19,6 +19,7 @@
*/
#include "libavutil/avassert.h"
+#include <libavutil/bswap.h>
#include "libavutil/rational.h"
#include "ops.h"
@@ -769,3 +770,97 @@ retry:
return 0;
}
+
+int ff_sws_solve_shuffle(const SwsOpList *const ops, uint8_t shuffle[],
+ int size, uint8_t clear_val,
+ int *read_bytes, int *write_bytes)
+{
+ const SwsOp read = ops->ops[0];
+ const int read_size = ff_sws_pixel_type_size(read.type);
+ uint32_t mask[4] = {0};
+
+ if (!ops->num_ops || read.op != SWS_OP_READ)
+ return AVERROR(EINVAL);
+ if (read.rw.frac || (!read.rw.packed && read.rw.elems > 1))
+ return AVERROR(ENOTSUP);
+
+ for (int i = 0; i < read.rw.elems; i++)
+ mask[i] = 0x01010101 * i * read_size + 0x03020100;
+
+ for (int opidx = 1; opidx < ops->num_ops; opidx++) {
+ const SwsOp *op = &ops->ops[opidx];
+ switch (op->op) {
+ case SWS_OP_SWIZZLE: {
+ uint32_t orig[4] = { mask[0], mask[1], mask[2], mask[3] };
+ for (int i = 0; i < 4; i++)
+ mask[i] = orig[op->swizzle.in[i]];
+ break;
+ }
+
+ case SWS_OP_SWAP_BYTES:
+ for (int i = 0; i < 4; i++) {
+ switch (ff_sws_pixel_type_size(op->type)) {
+ case 2: mask[i] = av_bswap16(mask[i]); break;
+ case 4: mask[i] = av_bswap32(mask[i]); break;
+ }
+ }
+ break;
+
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (!op->c.q4[i].den)
+ continue;
+ if (op->c.q4[i].num != 0 || !clear_val)
+ return AVERROR(ENOTSUP);
+ mask[i] = 0x1010101ul * clear_val;
+ }
+ break;
+
+ case SWS_OP_CONVERT: {
+ if (!op->convert.expand)
+ return AVERROR(ENOTSUP);
+ for (int i = 0; i < 4; i++) {
+ switch (ff_sws_pixel_type_size(op->type)) {
+ case 1: mask[i] = 0x01010101 * (mask[i] & 0xFF); break;
+ case 2: mask[i] = 0x00010001 * (mask[i] & 0xFFFF); break;
+ }
+ }
+ break;
+ }
+
+ case SWS_OP_WRITE: {
+ if (op->rw.frac || (!op->rw.packed && op->rw.elems > 1))
+ return AVERROR(ENOTSUP);
+
+ /* Initialize to no-op */
+ memset(shuffle, clear_val, size);
+
+ const int write_size = ff_sws_pixel_type_size(op->type);
+ const int read_chunk = read.rw.elems * read_size;
+ const int write_chunk = op->rw.elems * write_size;
+ const int num_groups = size / FFMAX(read_chunk, write_chunk);
+ for (int n = 0; n < num_groups; n++) {
+ const int base_in = n * read_chunk;
+ const int base_out = n * write_chunk;
+ for (int i = 0; i < op->rw.elems; i++) {
+ const int offset = base_out + i * write_size;
+ for (int b = 0; b < write_size; b++) {
+ const uint8_t idx = mask[i] >> (b * 8);
+ if (idx != clear_val)
+ shuffle[offset + b] = base_in + idx;
+ }
+ }
+ }
+
+ *read_bytes = num_groups * read_chunk;
+ *write_bytes = num_groups * write_chunk;
+ return num_groups;
+ }
+
+ default:
+ return AVERROR(ENOTSUP);
+ }
+ }
+
+ return AVERROR(EINVAL);
+}
commit db2bc11a978fdafc1c60e1cfe63796789b46fdc4
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Sun May 18 16:46:21 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/ops: add dispatch layer
This handles the low-level execution of an op list, and integration into
the SwsGraph infrastructure. To handle frames with insufficient padding in
the stride (or a width smaller than one block size), we use a fallback loop
that pads the last column of pixels using `memcpy` into an appropriately
sized buffer.
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 3b39dc69bd..7c5f315c8f 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -572,3 +572,281 @@ int ff_sws_ops_compile(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out
ff_sws_op_list_print(ctx, AV_LOG_WARNING, ops);
return AVERROR(ENOTSUP);
}
+
+typedef struct SwsOpPass {
+ SwsCompiledOp comp;
+ SwsOpExec exec_base;
+ int num_blocks;
+ int tail_off_in;
+ int tail_off_out;
+ int tail_size_in;
+ int tail_size_out;
+ int planes_in;
+ int planes_out;
+ int pixel_bits_in;
+ int pixel_bits_out;
+ bool memcpy_in;
+ bool memcpy_out;
+} SwsOpPass;
+
+static void op_pass_free(void *ptr)
+{
+ SwsOpPass *p = ptr;
+ if (!p)
+ return;
+
+ if (p->comp.free)
+ p->comp.free(p->comp.priv);
+
+ av_free(p);
+}
+
+static void op_pass_setup(const SwsImg *out, const SwsImg *in, const SwsPass *pass)
+{
+ const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(in->fmt);
+ const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(out->fmt);
+
+ SwsOpPass *p = pass->priv;
+ SwsOpExec *exec = &p->exec_base;
+ const SwsCompiledOp *comp = &p->comp;
+ const int block_size = comp->block_size;
+ p->num_blocks = (pass->width + block_size - 1) / block_size;
+
+ /* Set up main loop parameters */
+ const int aligned_w = p->num_blocks * block_size;
+ const int safe_width = (p->num_blocks - 1) * block_size;
+ const int tail_size = pass->width - safe_width;
+ p->tail_off_in = safe_width * p->pixel_bits_in >> 3;
+ p->tail_off_out = safe_width * p->pixel_bits_out >> 3;
+ p->tail_size_in = tail_size * p->pixel_bits_in >> 3;
+ p->tail_size_out = tail_size * p->pixel_bits_out >> 3;
+ p->memcpy_in = false;
+ p->memcpy_out = false;
+
+ for (int i = 0; i < p->planes_in; i++) {
+ const int sub_x = (i == 1 || i == 2) ? indesc->log2_chroma_w : 0;
+ const int plane_w = (aligned_w + sub_x) >> sub_x;
+ const int plane_pad = (comp->over_read + sub_x) >> sub_x;
+ const int plane_size = plane_w * p->pixel_bits_in >> 3;
+ p->memcpy_in |= plane_size + plane_pad > in->linesize[i];
+ exec->in_stride[i] = in->linesize[i];
+ }
+
+ for (int i = 0; i < p->planes_out; i++) {
+ const int sub_x = (i == 1 || i == 2) ? outdesc->log2_chroma_w : 0;
+ const int plane_w = (aligned_w + sub_x) >> sub_x;
+ const int plane_pad = (comp->over_write + sub_x) >> sub_x;
+ const int plane_size = plane_w * p->pixel_bits_out >> 3;
+ p->memcpy_out |= plane_size + plane_pad > out->linesize[i];
+ exec->out_stride[i] = out->linesize[i];
+ }
+
+ /* Pre-fill pointer bump for the main section only; this value does not
+ * matter at all for the tail / last row handlers because they only ever
+ * process a single line */
+ const int blocks_main = p->num_blocks - p->memcpy_out;
+ for (int i = 0; i < 4; i++) {
+ exec->in_bump[i] = in->linesize[i] - blocks_main * exec->block_size_in;
+ exec->out_bump[i] = out->linesize[i] - blocks_main * exec->block_size_out;
+ }
+}
+
+/* Dispatch kernel over the last column of the image using memcpy */
+static av_always_inline void
+handle_tail(const SwsOpPass *p, SwsOpExec *exec,
+ const SwsImg *out_base, const bool copy_out,
+ const SwsImg *in_base, const bool copy_in,
+ int y, const int h)
+{
+ DECLARE_ALIGNED_64(uint8_t, tmp)[2][4][sizeof(uint32_t[128])];
+
+ const SwsCompiledOp *comp = &p->comp;
+ const int tail_size_in = p->tail_size_in;
+ const int tail_size_out = p->tail_size_out;
+ const int bx = p->num_blocks - 1;
+
+ SwsImg in = ff_sws_img_shift(in_base, y);
+ SwsImg out = ff_sws_img_shift(out_base, y);
+ for (int i = 0; i < p->planes_in; i++) {
+ in.data[i] += p->tail_off_in;
+ if (copy_in) {
+ exec->in[i] = (void *) tmp[0][i];
+ exec->in_stride[i] = sizeof(tmp[0][i]);
+ } else {
+ exec->in[i] = in.data[i];
+ }
+ }
+
+ for (int i = 0; i < p->planes_out; i++) {
+ out.data[i] += p->tail_off_out;
+ if (copy_out) {
+ exec->out[i] = (void *) tmp[1][i];
+ exec->out_stride[i] = sizeof(tmp[1][i]);
+ } else {
+ exec->out[i] = out.data[i];
+ }
+ }
+
+ for (int y_end = y + h; y < y_end; y++) {
+ if (copy_in) {
+ for (int i = 0; i < p->planes_in; i++) {
+ av_assert2(tmp[0][i] + tail_size_in < (uint8_t *) tmp[1]);
+ memcpy(tmp[0][i], in.data[i], tail_size_in);
+ in.data[i] += in.linesize[i];
+ }
+ }
+
+ comp->func(exec, comp->priv, bx, y, p->num_blocks, y + 1);
+
+ if (copy_out) {
+ for (int i = 0; i < p->planes_out; i++) {
+ av_assert2(tmp[1][i] + tail_size_out < (uint8_t *) tmp[2]);
+ memcpy(out.data[i], tmp[1][i], tail_size_out);
+ out.data[i] += out.linesize[i];
+ }
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if (!copy_in)
+ exec->in[i] += in.linesize[i];
+ if (!copy_out)
+ exec->out[i] += out.linesize[i];
+ }
+ }
+}
+
+static void op_pass_run(const SwsImg *out_base, const SwsImg *in_base,
+ const int y, const int h, const SwsPass *pass)
+{
+ const SwsOpPass *p = pass->priv;
+ const SwsCompiledOp *comp = &p->comp;
+ const SwsImg in = ff_sws_img_shift(in_base, y);
+ const SwsImg out = ff_sws_img_shift(out_base, y);
+
+ /* Fill exec metadata for this slice */
+ DECLARE_ALIGNED_32(SwsOpExec, exec) = p->exec_base;
+ exec.slice_y = y;
+ exec.slice_h = h;
+ for (int i = 0; i < 4; i++) {
+ exec.in[i] = in.data[i];
+ exec.out[i] = out.data[i];
+ }
+
+ /**
+ * To ensure safety, we need to consider the following:
+ *
+ * 1. We can overread the input, unless this is the last line of an
+ * unpadded buffer. All defined operations can handle arbitrary pixel
+ * input, so overread of arbitrary data is fine.
+ *
+ * 2. We can overwrite the output, as long as we don't write more than the
+ * amount of pixels that fit into one linesize. So we always need to
+ * memcpy the last column on the output side if unpadded.
+ *
+ * 3. For the last row, we also need to memcpy the remainder of the input,
+ * to avoid reading past the end of the buffer. Note that since we know
+ * the run() function is called on stripes of the same buffer, we don't
+ * need to worry about this for the end of a slice.
+ */
+
+ const int last_slice = y + h == pass->height;
+ const bool memcpy_in = last_slice && p->memcpy_in;
+ const bool memcpy_out = p->memcpy_out;
+ const int num_blocks = p->num_blocks;
+ const int blocks_main = num_blocks - memcpy_out;
+ const int h_main = h - memcpy_in;
+
+ /* Handle main section */
+ comp->func(&exec, comp->priv, 0, y, blocks_main, y + h_main);
+
+ if (memcpy_in) {
+ /* Safe part of last row */
+ for (int i = 0; i < 4; i++) {
+ exec.in[i] += h_main * in.linesize[i];
+ exec.out[i] += h_main * out.linesize[i];
+ }
+ comp->func(&exec, comp->priv, 0, y + h_main, num_blocks - 1, y + h);
+ }
+
+ /* Handle last column via memcpy, takes over `exec` so call these last */
+ if (memcpy_out)
+ handle_tail(p, &exec, out_base, true, in_base, false, y, h_main);
+ if (memcpy_in)
+ handle_tail(p, &exec, out_base, memcpy_out, in_base, true, y + h_main, 1);
+}
+
+static int rw_planes(const SwsOp *op)
+{
+ return op->rw.packed ? 1 : op->rw.elems;
+}
+
+static int rw_pixel_bits(const SwsOp *op)
+{
+ const int elems = op->rw.packed ? op->rw.elems : 1;
+ const int size = ff_sws_pixel_type_size(op->type);
+ const int bits = 8 >> op->rw.frac;
+ av_assert1(bits >= 1);
+ return elems * size * bits;
+}
+
+int ff_sws_compile_pass(SwsGraph *graph, SwsOpList *ops, int flags, SwsFormat dst,
+ SwsPass *input, SwsPass **output)
+{
+ SwsContext *ctx = graph->ctx;
+ SwsOpPass *p = NULL;
+ const SwsOp *read = &ops->ops[0];
+ const SwsOp *write = &ops->ops[ops->num_ops - 1];
+ SwsPass *pass;
+ int ret;
+
+ if (ops->num_ops < 2) {
+ av_log(ctx, AV_LOG_ERROR, "Need at least two operations.\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (read->op != SWS_OP_READ || write->op != SWS_OP_WRITE) {
+ av_log(ctx, AV_LOG_ERROR, "First and last operations must be a read "
+ "and write, respectively.\n");
+ return AVERROR(EINVAL);
+ }
+
+ if (flags & SWS_OP_FLAG_OPTIMIZE)
+ RET(ff_sws_op_list_optimize(ops));
+ else
+ ff_sws_op_list_update_comps(ops);
+
+ p = av_mallocz(sizeof(*p));
+ if (!p)
+ return AVERROR(ENOMEM);
+
+ ret = ff_sws_ops_compile(ctx, ops, &p->comp);
+ if (ret < 0)
+ goto fail;
+
+ p->planes_in = rw_planes(read);
+ p->planes_out = rw_planes(write);
+ p->pixel_bits_in = rw_pixel_bits(read);
+ p->pixel_bits_out = rw_pixel_bits(write);
+ p->exec_base = (SwsOpExec) {
+ .width = dst.width,
+ .height = dst.height,
+ .block_size_in = p->comp.block_size * p->pixel_bits_in >> 3,
+ .block_size_out = p->comp.block_size * p->pixel_bits_out >> 3,
+ };
+
+ pass = ff_sws_graph_add_pass(graph, dst.format, dst.width, dst.height, input,
+ 1, p, op_pass_run);
+ if (!pass) {
+ ret = AVERROR(ENOMEM);
+ goto fail;
+ }
+ pass->setup = op_pass_setup;
+ pass->free = op_pass_free;
+
+ *output = pass;
+ return 0;
+
+fail:
+ op_pass_free(p);
+ return ret;
+}
diff --git a/libswscale/ops.h b/libswscale/ops.h
index d11d7457f8..8f26ee832e 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -252,4 +252,18 @@ void ff_sws_op_list_update_comps(SwsOpList *ops);
*/
int ff_sws_op_list_optimize(SwsOpList *ops);
+enum SwsOpCompileFlags {
+ /* Automatically optimize the operations when compiling */
+ SWS_OP_FLAG_OPTIMIZE = 1 << 0,
+};
+
+/**
+ * Resolves an operation list to a graph pass. The first and last operations
+ * must be a read/write respectively. `flags` is a list of SwsOpCompileFlags.
+ *
+ * Note: `ops` may be modified by this function.
+ */
+int ff_sws_compile_pass(SwsGraph *graph, SwsOpList *ops, int flags, SwsFormat dst,
+ SwsPass *input, SwsPass **output);
+
#endif
diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h
index 7c3ae5b6c1..62c0f46bc6 100644
--- a/libswscale/ops_internal.h
+++ b/libswscale/ops_internal.h
@@ -55,21 +55,25 @@ static inline void ff_sws_pack_op_decode(const SwsOp *op, uint64_t mask[4], int
*/
typedef struct SwsOpExec {
/* The data pointers point to the first pixel to process */
- DECLARE_ALIGNED_32(const uint8_t, *in[4]);
- DECLARE_ALIGNED_32(uint8_t, *out[4]);
+ const uint8_t *in[4];
+ uint8_t *out[4];
/* Separation between lines in bytes */
- DECLARE_ALIGNED_32(ptrdiff_t, in_stride[4]);
- DECLARE_ALIGNED_32(ptrdiff_t, out_stride[4]);
+ ptrdiff_t in_stride[4];
+ ptrdiff_t out_stride[4];
+
+ /* Pointer bump, difference between stride and processed line size */
+ ptrdiff_t in_bump[4];
+ ptrdiff_t out_bump[4];
/* Extra metadata, may or may not be useful */
int32_t width, height; /* Overall image dimensions */
int32_t slice_y, slice_h; /* Start and height of current slice */
- int32_t pixel_bits_in; /* Bits per input pixel */
- int32_t pixel_bits_out; /* Bits per output pixel */
+ int32_t block_size_in; /* Size of a block of pixels in bytes */
+ int32_t block_size_out;
} SwsOpExec;
-static_assert(sizeof(SwsOpExec) == 16 * sizeof(void *) + 8 * sizeof(int32_t),
+static_assert(sizeof(SwsOpExec) == 24 * sizeof(void *) + 6 * sizeof(int32_t),
"SwsOpExec layout mismatch");
/**
commit d3ca0e300d86d8c49fd4f0a093126e153ecffe0c
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Sun May 18 16:50:13 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/ops_internal: add internal ops backend API
This adds an internal API for ops backends, which are responsible for
compiling op lists into executable functions.
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 5ee4b0fbfc..3b39dc69bd 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -27,6 +27,16 @@
#include "ops.h"
#include "ops_internal.h"
+const SwsOpBackend * const ff_sws_op_backends[] = {
+ NULL
+};
+
+#define RET(x) \
+ do { \
+ if ((ret = (x)) < 0) \
+ return ret; \
+ } while (0)
+
const char *ff_sws_pixel_type_name(SwsPixelType type)
{
switch (type) {
@@ -508,3 +518,57 @@ void ff_sws_op_list_print(void *log, int lev, const SwsOpList *ops)
av_log(log, lev, " (X = unused, + = exact, 0 = zero)\n");
}
+
+int ff_sws_ops_compile_backend(SwsContext *ctx, const SwsOpBackend *backend,
+ const SwsOpList *ops, SwsCompiledOp *out)
+{
+ SwsOpList *copy, rest;
+ SwsCompiledOp compiled = {0};
+ int ret = 0;
+
+ copy = ff_sws_op_list_duplicate(ops);
+ if (!copy)
+ return AVERROR(ENOMEM);
+
+ /* Ensure these are always set during compilation */
+ ff_sws_op_list_update_comps(copy);
+
+ /* Make an on-stack copy of `ops` to ensure we can still properly clean up
+ * the copy afterwards */
+ rest = *copy;
+
+ ret = backend->compile(ctx, &rest, &compiled);
+ if (ret < 0) {
+ int msg_lev = ret == AVERROR(ENOTSUP) ? AV_LOG_TRACE : AV_LOG_ERROR;
+ av_log(ctx, msg_lev, "Backend '%s' failed to compile operations: %s\n",
+ backend->name, av_err2str(ret));
+ if (rest.num_ops != ops->num_ops) {
+ av_log(ctx, msg_lev, "Uncompiled remainder:\n");
+ ff_sws_op_list_print(ctx, msg_lev, &rest);
+ }
+ } else {
+ *out = compiled;
+ }
+
+ ff_sws_op_list_free(©);
+ return ret;
+}
+
+int ff_sws_ops_compile(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out)
+{
+ for (int n = 0; ff_sws_op_backends[n]; n++) {
+ const SwsOpBackend *backend = ff_sws_op_backends[n];
+ if (ff_sws_ops_compile_backend(ctx, backend, ops, out) < 0)
+ continue;
+
+ av_log(ctx, AV_LOG_VERBOSE, "Compiled using backend '%s': "
+ "block size = %d, over-read = %d, over-write = %d, cpu flags = 0x%x\n",
+ backend->name, out->block_size, out->over_read, out->over_write,
+ out->cpu_flags);
+ return 0;
+ }
+
+ av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n");
+ ff_sws_op_list_print(ctx, AV_LOG_WARNING, ops);
+ return AVERROR(ENOTSUP);
+}
diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h
index 78dc5d7d0a..7c3ae5b6c1 100644
--- a/libswscale/ops_internal.h
+++ b/libswscale/ops_internal.h
@@ -21,6 +21,8 @@
#ifndef SWSCALE_OPS_INTERNAL_H
#define SWSCALE_OPS_INTERNAL_H
+#include "libavutil/mem_internal.h"
+
#include "ops.h"
#define Q(N) ((AVRational) { N, 1 })
@@ -45,4 +47,83 @@ static inline void ff_sws_pack_op_decode(const SwsOp *op, uint64_t mask[4], int
}
}
+/**
+ * Global execution context for all compiled functions.
+ *
+ * Note: This struct is hard-coded in assembly, so do not change the layout
+ * without updating the corresponding assembly definitions.
+ */
+typedef struct SwsOpExec {
+ /* The data pointers point to the first pixel to process */
+ DECLARE_ALIGNED_32(const uint8_t, *in[4]);
+ DECLARE_ALIGNED_32(uint8_t, *out[4]);
+
+ /* Separation between lines in bytes */
+ DECLARE_ALIGNED_32(ptrdiff_t, in_stride[4]);
+ DECLARE_ALIGNED_32(ptrdiff_t, out_stride[4]);
+
+ /* Extra metadata, may or may not be useful */
+ int32_t width, height; /* Overall image dimensions */
+ int32_t slice_y, slice_h; /* Start and height of current slice */
+ int32_t pixel_bits_in; /* Bits per input pixel */
+ int32_t pixel_bits_out; /* Bits per output pixel */
+} SwsOpExec;
+
+static_assert(sizeof(SwsOpExec) == 16 * sizeof(void *) + 8 * sizeof(int32_t),
+ "SwsOpExec layout mismatch");
+
+/**
+ * Process a given range of pixel blocks.
+ *
+ * Note: `bx_start` and `bx_end` are in units of `SwsCompiledOp.block_size`.
+ */
+typedef void (*SwsOpFunc)(const SwsOpExec *exec, const void *priv,
+ int bx_start, int y_start, int bx_end, int y_end);
+
+#define SWS_DECL_FUNC(NAME) \
+ void NAME(const SwsOpExec *, const void *, int, int, int, int)
+
+typedef struct SwsCompiledOp {
+ SwsOpFunc func;
+
+ int block_size; /* number of pixels processed per iteration */
+ int over_read; /* implementation over-reads input by this many bytes */
+ int over_write; /* implementation over-writes output by this many bytes */
+ int cpu_flags; /* active set of CPU flags (informative) */
+
+ /* Arbitrary private data */
+ void *priv;
+ void (*free)(void *priv);
+} SwsCompiledOp;
+
+typedef struct SwsOpBackend {
+ const char *name; /* Descriptive name for this backend */
+
+ /**
+ * Compile an operation list to an implementation chain. May modify `ops`
+ * freely; the original list will be freed automatically by the caller.
+ *
+ * Returns 0 or a negative error code.
+ */
+ int (*compile)(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out);
+} SwsOpBackend;
+
+/* List of all backends, terminated by NULL */
+extern const SwsOpBackend *const ff_sws_op_backends[];
+
+/**
+ * Attempt to compile a list of operations using a specific backend.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int ff_sws_ops_compile_backend(SwsContext *ctx, const SwsOpBackend *backend,
+ const SwsOpList *ops, SwsCompiledOp *out);
+
+/**
+ * Compile a list of operations using the best available backend.
+ *
+ * Returns 0 on success, or a negative error code on failure.
+ */
+int ff_sws_ops_compile(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out);
+
#endif /* SWSCALE_OPS_INTERNAL_H */
commit ea9ca3ff353734dde0c7415259e45ca845ebd38e
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Sun May 18 16:52:15 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/optimizer: add high-level ops optimizer
This is responsible for taking a "naive" ops list and optimizing it
as much as possible. Also includes a small analyzer that generates component
metadata for use by the optimizer.
diff --git a/libswscale/Makefile b/libswscale/Makefile
index e0beef4e69..810c9dee78 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -16,6 +16,7 @@ OBJS = alphablend.o \
input.o \
lut3d.o \
ops.o \
+ ops_optimizer.o \
options.o \
output.o \
rgb2rgb.o \
diff --git a/libswscale/ops.h b/libswscale/ops.h
index 85788d7292..d11d7457f8 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -240,4 +240,16 @@ void ff_sws_op_list_remove_at(SwsOpList *ops, int index, int count);
*/
void ff_sws_op_list_print(void *log_ctx, int log_level, const SwsOpList *ops);
+/**
+ * Infer + propagate known information about components. Called automatically
+ * when needed by the optimizer and compiler.
+ */
+void ff_sws_op_list_update_comps(SwsOpList *ops);
+
+/**
+ * Fuse compatible and eliminate redundant operations, as well as replacing
+ * some operations with more efficient alternatives.
+ */
+int ff_sws_op_list_optimize(SwsOpList *ops);
+
#endif
diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c
new file mode 100644
index 0000000000..0a744a0c49
--- /dev/null
+++ b/libswscale/ops_optimizer.c
@@ -0,0 +1,771 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "libavutil/avassert.h"
+#include "libavutil/rational.h"
+
+#include "ops.h"
+#include "ops_internal.h"
+
+#define RET(x) \
+ do { \
+ if ((ret = (x)) < 0) \
+ return ret; \
+ } while (0)
+
+/* Returns true for operations that are independent per channel. These can
+ * usually be commuted freely other such operations. */
+static bool op_type_is_independent(SwsOpType op)
+{
+ switch (op) {
+ case SWS_OP_SWAP_BYTES:
+ case SWS_OP_LSHIFT:
+ case SWS_OP_RSHIFT:
+ case SWS_OP_CONVERT:
+ case SWS_OP_DITHER:
+ case SWS_OP_MIN:
+ case SWS_OP_MAX:
+ case SWS_OP_SCALE:
+ return true;
+ case SWS_OP_INVALID:
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ case SWS_OP_SWIZZLE:
+ case SWS_OP_CLEAR:
+ case SWS_OP_LINEAR:
+ case SWS_OP_PACK:
+ case SWS_OP_UNPACK:
+ return false;
+ case SWS_OP_TYPE_NB:
+ break;
+ }
+
+ av_unreachable("Invalid operation type!");
+ return false;
+}
+
+/* merge_comp_flags() forms a monoid with flags_identity as the null element */
+static const unsigned flags_identity = SWS_COMP_ZERO | SWS_COMP_EXACT;
+static unsigned merge_comp_flags(unsigned a, unsigned b)
+{
+ const unsigned flags_or = SWS_COMP_GARBAGE;
+ const unsigned flags_and = SWS_COMP_ZERO | SWS_COMP_EXACT;
+ return ((a & b) & flags_and) | ((a | b) & flags_or);
+}
+
+/* Infer + propagate known information about components */
+void ff_sws_op_list_update_comps(SwsOpList *ops)
+{
+ SwsComps next = { .unused = {true, true, true, true} };
+ SwsComps prev = { .flags = {
+ SWS_COMP_GARBAGE, SWS_COMP_GARBAGE, SWS_COMP_GARBAGE, SWS_COMP_GARBAGE,
+ }};
+
+ /* Forwards pass, propagates knowledge about the incoming pixel values */
+ for (int n = 0; n < ops->num_ops; n++) {
+ SwsOp *op = &ops->ops[n];
+
+ /* Prefill min/max values automatically; may have to be fixed in
+ * special cases */
+ memcpy(op->comps.min, prev.min, sizeof(prev.min));
+ memcpy(op->comps.max, prev.max, sizeof(prev.max));
+
+ if (op->op != SWS_OP_SWAP_BYTES) {
+ ff_sws_apply_op_q(op, op->comps.min);
+ ff_sws_apply_op_q(op, op->comps.max);
+ }
+
+ switch (op->op) {
+ case SWS_OP_READ:
+ for (int i = 0; i < op->rw.elems; i++) {
+ if (ff_sws_pixel_type_is_int(op->type)) {
+ int bits = 8 * ff_sws_pixel_type_size(op->type);
+ if (!op->rw.packed && ops->src.desc) {
+ /* Use legal value range from pixdesc if available;
+ * we don't need to do this for packed formats because
+ * non-byte-aligned packed formats will necessarily go
+ * through SWS_OP_UNPACK anyway */
+ for (int c = 0; c < 4; c++) {
+ if (ops->src.desc->comp[c].plane == i) {
+ bits = ops->src.desc->comp[c].depth;
+ break;
+ }
+ }
+ }
+
+ op->comps.flags[i] = SWS_COMP_EXACT;
+ op->comps.min[i] = Q(0);
+ op->comps.max[i] = Q((1ULL << bits) - 1);
+ }
+ }
+ for (int i = op->rw.elems; i < 4; i++)
+ op->comps.flags[i] = prev.flags[i];
+ break;
+ case SWS_OP_WRITE:
+ for (int i = 0; i < op->rw.elems; i++)
+ av_assert1(!(prev.flags[i] & SWS_COMP_GARBAGE));
+ /* fall through */
+ case SWS_OP_SWAP_BYTES:
+ case SWS_OP_LSHIFT:
+ case SWS_OP_RSHIFT:
+ case SWS_OP_MIN:
+ case SWS_OP_MAX:
+ /* Linearly propagate flags per component */
+ for (int i = 0; i < 4; i++)
+ op->comps.flags[i] = prev.flags[i];
+ break;
+ case SWS_OP_DITHER:
+ /* Strip zero flag because of the nonzero dithering offset */
+ for (int i = 0; i < 4; i++)
+ op->comps.flags[i] = prev.flags[i] & ~SWS_COMP_ZERO;
+ break;
+ case SWS_OP_UNPACK:
+ for (int i = 0; i < 4; i++) {
+ if (op->pack.pattern[i])
+ op->comps.flags[i] = prev.flags[0];
+ else
+ op->comps.flags[i] = SWS_COMP_GARBAGE;
+ }
+ break;
+ case SWS_OP_PACK: {
+ unsigned flags = flags_identity;
+ for (int i = 0; i < 4; i++) {
+ if (op->pack.pattern[i])
+ flags = merge_comp_flags(flags, prev.flags[i]);
+ if (i > 0) /* clear remaining comps for sanity */
+ op->comps.flags[i] = SWS_COMP_GARBAGE;
+ }
+ op->comps.flags[0] = flags;
+ break;
+ }
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (op->c.q4[i].den) {
+ if (op->c.q4[i].num == 0) {
+ op->comps.flags[i] = SWS_COMP_ZERO | SWS_COMP_EXACT;
+ } else if (op->c.q4[i].den == 1) {
+ op->comps.flags[i] = SWS_COMP_EXACT;
+ }
+ } else {
+ op->comps.flags[i] = prev.flags[i];
+ }
+ }
+ break;
+ case SWS_OP_SWIZZLE:
+ for (int i = 0; i < 4; i++)
+ op->comps.flags[i] = prev.flags[op->swizzle.in[i]];
+ break;
+ case SWS_OP_CONVERT:
+ for (int i = 0; i < 4; i++) {
+ op->comps.flags[i] = prev.flags[i];
+ if (ff_sws_pixel_type_is_int(op->convert.to))
+ op->comps.flags[i] |= SWS_COMP_EXACT;
+ }
+ break;
+ case SWS_OP_LINEAR:
+ for (int i = 0; i < 4; i++) {
+ unsigned flags = flags_identity;
+ AVRational min = Q(0), max = Q(0);
+ for (int j = 0; j < 4; j++) {
+ const AVRational k = op->lin.m[i][j];
+ AVRational mink = av_mul_q(prev.min[j], k);
+ AVRational maxk = av_mul_q(prev.max[j], k);
+ if (k.num) {
+ flags = merge_comp_flags(flags, prev.flags[j]);
+ if (k.den != 1) /* fractional coefficient */
+ flags &= ~SWS_COMP_EXACT;
+ if (k.num < 0)
+ FFSWAP(AVRational, mink, maxk);
+ min = av_add_q(min, mink);
+ max = av_add_q(max, maxk);
+ }
+ }
+ if (op->lin.m[i][4].num) { /* nonzero offset */
+ flags &= ~SWS_COMP_ZERO;
+ if (op->lin.m[i][4].den != 1) /* fractional offset */
+ flags &= ~SWS_COMP_EXACT;
+ min = av_add_q(min, op->lin.m[i][4]);
+ max = av_add_q(max, op->lin.m[i][4]);
+ }
+ op->comps.flags[i] = flags;
+ op->comps.min[i] = min;
+ op->comps.max[i] = max;
+ }
+ break;
+ case SWS_OP_SCALE:
+ for (int i = 0; i < 4; i++) {
+ op->comps.flags[i] = prev.flags[i];
+ if (op->c.q.den != 1) /* fractional scale */
+ op->comps.flags[i] &= ~SWS_COMP_EXACT;
+ if (op->c.q.num < 0)
+ FFSWAP(AVRational, op->comps.min[i], op->comps.max[i]);
+ }
+ break;
+
+ case SWS_OP_INVALID:
+ case SWS_OP_TYPE_NB:
+ av_unreachable("Invalid operation type!");
+ }
+
+ prev = op->comps;
+ }
+
+ /* Backwards pass, solves for component dependencies */
+ for (int n = ops->num_ops - 1; n >= 0; n--) {
+ SwsOp *op = &ops->ops[n];
+
+ switch (op->op) {
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ for (int i = 0; i < op->rw.elems; i++)
+ op->comps.unused[i] = op->op == SWS_OP_READ;
+ for (int i = op->rw.elems; i < 4; i++)
+ op->comps.unused[i] = next.unused[i];
+ break;
+ case SWS_OP_SWAP_BYTES:
+ case SWS_OP_LSHIFT:
+ case SWS_OP_RSHIFT:
+ case SWS_OP_CONVERT:
+ case SWS_OP_DITHER:
+ case SWS_OP_MIN:
+ case SWS_OP_MAX:
+ case SWS_OP_SCALE:
+ for (int i = 0; i < 4; i++)
+ op->comps.unused[i] = next.unused[i];
+ break;
+ case SWS_OP_UNPACK: {
+ bool unused = true;
+ for (int i = 0; i < 4; i++) {
+ if (op->pack.pattern[i])
+ unused &= next.unused[i];
+ op->comps.unused[i] = i > 0;
+ }
+ op->comps.unused[0] = unused;
+ break;
+ }
+ case SWS_OP_PACK:
+ for (int i = 0; i < 4; i++) {
+ if (op->pack.pattern[i])
+ op->comps.unused[i] = next.unused[0];
+ else
+ op->comps.unused[i] = true;
+ }
+ break;
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (op->c.q4[i].den)
+ op->comps.unused[i] = true;
+ else
+ op->comps.unused[i] = next.unused[i];
+ }
+ break;
+ case SWS_OP_SWIZZLE: {
+ bool unused[4] = { true, true, true, true };
+ for (int i = 0; i < 4; i++)
+ unused[op->swizzle.in[i]] &= next.unused[i];
+ for (int i = 0; i < 4; i++)
+ op->comps.unused[i] = unused[i];
+ break;
+ }
+ case SWS_OP_LINEAR:
+ for (int j = 0; j < 4; j++) {
+ bool unused = true;
+ for (int i = 0; i < 4; i++) {
+ if (op->lin.m[i][j].num)
+ unused &= next.unused[i];
+ }
+ op->comps.unused[j] = unused;
+ }
+ break;
+ }
+
+ next = op->comps;
+ }
+}
+
+/* returns log2(x) only if x is a power of two, or 0 otherwise */
+static int exact_log2(const int x)
+{
+ int p;
+ if (x <= 0)
+ return 0;
+ p = av_log2(x);
+ return (1 << p) == x ? p : 0;
+}
+
+static int exact_log2_q(const AVRational x)
+{
+ if (x.den == 1)
+ return exact_log2(x.num);
+ else if (x.num == 1)
+ return -exact_log2(x.den);
+ else
+ return 0;
+}
+
+/**
+ * If a linear operation can be reduced to a scalar multiplication, returns
+ * the corresponding scaling factor, or 0 otherwise.
+ */
+static bool extract_scalar(const SwsLinearOp *c, SwsComps prev, SwsComps next,
+ SwsConst *out_scale)
+{
+ SwsConst scale = {0};
+
+ /* There are components not on the main diagonal */
+ if (c->mask & ~SWS_MASK_DIAG4)
+ return false;
+
+ for (int i = 0; i < 4; i++) {
+ const AVRational s = c->m[i][i];
+ if ((prev.flags[i] & SWS_COMP_ZERO) || next.unused[i])
+ continue;
+ if (scale.q.den && av_cmp_q(s, scale.q))
+ return false;
+ scale.q = s;
+ }
+
+ if (scale.q.den)
+ *out_scale = scale;
+ return scale.q.den;
+}
+
+/* Extracts an integer clear operation (subset) from the given linear op. */
+static bool extract_constant_rows(SwsLinearOp *c, SwsComps prev,
+ SwsConst *out_clear)
+{
+ SwsConst clear = {0};
+ bool ret = false;
+
+ for (int i = 0; i < 4; i++) {
+ bool const_row = c->m[i][4].den == 1; /* offset is integer */
+ for (int j = 0; j < 4; j++) {
+ const_row &= c->m[i][j].num == 0 || /* scalar is zero */
+ (prev.flags[j] & SWS_COMP_ZERO); /* input is zero */
+ }
+ if (const_row && (c->mask & SWS_MASK_ROW(i))) {
+ clear.q4[i] = c->m[i][4];
+ for (int j = 0; j < 5; j++)
+ c->m[i][j] = Q(i == j);
+ c->mask &= ~SWS_MASK_ROW(i);
+ ret = true;
+ }
+ }
+
+ if (ret)
+ *out_clear = clear;
+ return ret;
+}
+
+/* Unswizzle a linear operation by aligning single-input rows with
+ * their corresponding diagonal */
+static bool extract_swizzle(SwsLinearOp *op, SwsComps prev, SwsSwizzleOp *out_swiz)
+{
+ SwsSwizzleOp swiz = SWS_SWIZZLE(0, 1, 2, 3);
+ SwsLinearOp c = *op;
+
+ for (int i = 0; i < 4; i++) {
+ int idx = -1;
+ for (int j = 0; j < 4; j++) {
+ if (!c.m[i][j].num || (prev.flags[j] & SWS_COMP_ZERO))
+ continue;
+ if (idx >= 0)
+ return false; /* multiple inputs */
+ idx = j;
+ }
+
+ if (idx >= 0 && idx != i) {
+ /* Move coefficient to the diagonal */
+ c.m[i][i] = c.m[i][idx];
+ c.m[i][idx] = Q(0);
+ swiz.in[i] = idx;
+ }
+ }
+
+ if (swiz.mask == SWS_SWIZZLE(0, 1, 2, 3).mask)
+ return false; /* no swizzle was identified */
+
+ c.mask = ff_sws_linear_mask(c);
+ *out_swiz = swiz;
+ *op = c;
+ return true;
+}
+
+int ff_sws_op_list_optimize(SwsOpList *ops)
+{
+ int ret;
+
+retry:
+ ff_sws_op_list_update_comps(ops);
+
+ for (int n = 0; n < ops->num_ops;) {
+ SwsOp dummy = {0};
+ SwsOp *op = &ops->ops[n];
+ SwsOp *prev = n ? &ops->ops[n - 1] : &dummy;
+ SwsOp *next = n + 1 < ops->num_ops ? &ops->ops[n + 1] : &dummy;
+
+ /* common helper variable */
+ bool noop = true;
+
+ switch (op->op) {
+ case SWS_OP_READ:
+ /* Optimized further into refcopy / memcpy */
+ if (next->op == SWS_OP_WRITE &&
+ next->rw.elems == op->rw.elems &&
+ next->rw.packed == op->rw.packed &&
+ next->rw.frac == op->rw.frac)
+ {
+ ff_sws_op_list_remove_at(ops, n, 2);
+ av_assert1(ops->num_ops == 0);
+ return 0;
+ }
+
+ /* Skip reading extra unneeded components */
+ if (!op->rw.packed) {
+ int needed = op->rw.elems;
+ while (needed > 0 && next->comps.unused[needed - 1])
+ needed--;
+ if (op->rw.elems != needed) {
+ op->rw.elems = needed;
+ goto retry;
+ }
+ }
+ break;
+
+ case SWS_OP_SWAP_BYTES:
+ /* Redundant (double) swap */
+ if (next->op == SWS_OP_SWAP_BYTES) {
+ ff_sws_op_list_remove_at(ops, n, 2);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_UNPACK:
+ /* Redundant unpack+pack */
+ if (next->op == SWS_OP_PACK && next->type == op->type &&
+ next->pack.pattern[0] == op->pack.pattern[0] &&
+ next->pack.pattern[1] == op->pack.pattern[1] &&
+ next->pack.pattern[2] == op->pack.pattern[2] &&
+ next->pack.pattern[3] == op->pack.pattern[3])
+ {
+ ff_sws_op_list_remove_at(ops, n, 2);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_LSHIFT:
+ case SWS_OP_RSHIFT:
+ /* Two shifts in the same direction */
+ if (next->op == op->op) {
+ op->c.u += next->c.u;
+ ff_sws_op_list_remove_at(ops, n + 1, 1);
+ goto retry;
+ }
+
+ /* No-op shift */
+ if (!op->c.u) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (!op->c.q4[i].den)
+ continue;
+
+ if ((prev->comps.flags[i] & SWS_COMP_ZERO) &&
+ !(prev->comps.flags[i] & SWS_COMP_GARBAGE) &&
+ op->c.q4[i].num == 0)
+ {
+ /* Redundant clear-to-zero of zero component */
+ op->c.q4[i].den = 0;
+ } else if (next->comps.unused[i]) {
+ /* Unnecessary clear of unused component */
+ op->c.q4[i] = (AVRational) {0, 0};
+ } else if (op->c.q4[i].den) {
+ noop = false;
+ }
+ }
+
+ if (noop) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+
+ /* Transitive clear */
+ if (next->op == SWS_OP_CLEAR) {
+ for (int i = 0; i < 4; i++) {
+ if (next->c.q4[i].den)
+ op->c.q4[i] = next->c.q4[i];
+ }
+ ff_sws_op_list_remove_at(ops, n + 1, 1);
+ goto retry;
+ }
+
+ /* Prefer to clear as late as possible, to avoid doing
+ * redundant work */
+ if ((op_type_is_independent(next->op) && next->op != SWS_OP_SWAP_BYTES) ||
+ next->op == SWS_OP_SWIZZLE)
+ {
+ if (next->op == SWS_OP_CONVERT)
+ op->type = next->convert.to;
+ ff_sws_apply_op_q(next, op->c.q4);
+ FFSWAP(SwsOp, *op, *next);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_SWIZZLE: {
+ bool seen[4] = {0};
+ bool has_duplicates = false;
+ for (int i = 0; i < 4; i++) {
+ if (next->comps.unused[i])
+ continue;
+ if (op->swizzle.in[i] != i)
+ noop = false;
+ has_duplicates |= seen[op->swizzle.in[i]];
+ seen[op->swizzle.in[i]] = true;
+ }
+
+ /* Identity swizzle */
+ if (noop) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+
+ /* Transitive swizzle */
+ if (next->op == SWS_OP_SWIZZLE) {
+ const SwsSwizzleOp orig = op->swizzle;
+ for (int i = 0; i < 4; i++)
+ op->swizzle.in[i] = orig.in[next->swizzle.in[i]];
+ ff_sws_op_list_remove_at(ops, n + 1, 1);
+ goto retry;
+ }
+
+ /* Try to push swizzles with duplicates towards the output */
+ if (has_duplicates && op_type_is_independent(next->op)) {
+ if (next->op == SWS_OP_CONVERT)
+ op->type = next->convert.to;
+ if (next->op == SWS_OP_MIN || next->op == SWS_OP_MAX) {
+ /* Un-swizzle the next operation */
+ const SwsConst c = next->c;
+ for (int i = 0; i < 4; i++) {
+ if (!next->comps.unused[i])
+ next->c.q4[op->swizzle.in[i]] = c.q4[i];
+ }
+ }
+ FFSWAP(SwsOp, *op, *next);
+ goto retry;
+ }
+
+ /* Move swizzle out of the way between two converts so that
+ * they may be merged */
+ if (prev->op == SWS_OP_CONVERT && next->op == SWS_OP_CONVERT) {
+ op->type = next->convert.to;
+ FFSWAP(SwsOp, *op, *next);
+ goto retry;
+ }
+ break;
+ }
+
+ case SWS_OP_CONVERT:
+ /* No-op conversion */
+ if (op->type == op->convert.to) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+
+ /* Transitive conversion */
+ if (next->op == SWS_OP_CONVERT &&
+ op->convert.expand == next->convert.expand)
+ {
+ av_assert1(op->convert.to == next->type);
+ op->convert.to = next->convert.to;
+ ff_sws_op_list_remove_at(ops, n + 1, 1);
+ goto retry;
+ }
+
+ /* Conversion followed by integer expansion */
+ if (next->op == SWS_OP_SCALE && !op->convert.expand &&
+ !av_cmp_q(next->c.q, ff_sws_pixel_expand(op->type, op->convert.to)))
+ {
+ op->convert.expand = true;
+ ff_sws_op_list_remove_at(ops, n + 1, 1);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_MIN:
+ for (int i = 0; i < 4; i++) {
+ if (next->comps.unused[i] || !op->c.q4[i].den)
+ continue;
+ if (av_cmp_q(op->c.q4[i], prev->comps.max[i]) < 0)
+ noop = false;
+ }
+
+ if (noop) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_MAX:
+ for (int i = 0; i < 4; i++) {
+ if (next->comps.unused[i] || !op->c.q4[i].den)
+ continue;
+ if (av_cmp_q(prev->comps.min[i], op->c.q4[i]) < 0)
+ noop = false;
+ }
+
+ if (noop) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_DITHER:
+ for (int i = 0; i < 4; i++) {
+ noop &= (prev->comps.flags[i] & SWS_COMP_EXACT) ||
+ next->comps.unused[i];
+ }
+
+ if (noop) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+ break;
+
+ case SWS_OP_LINEAR: {
+ SwsSwizzleOp swizzle;
+ SwsConst c;
+
+ /* No-op (identity) linear operation */
+ if (!op->lin.mask) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+
+ if (next->op == SWS_OP_LINEAR) {
+ /* 5x5 matrix multiplication after appending [ 0 0 0 0 1 ] */
+ const SwsLinearOp m1 = op->lin;
+ const SwsLinearOp m2 = next->lin;
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 5; j++) {
+ AVRational sum = Q(0);
+ for (int k = 0; k < 4; k++)
+ sum = av_add_q(sum, av_mul_q(m2.m[i][k], m1.m[k][j]));
+ if (j == 4) /* m1.m[4][j] == 1 */
+ sum = av_add_q(sum, m2.m[i][4]);
+ op->lin.m[i][j] = sum;
+ }
+ }
+ op->lin.mask = ff_sws_linear_mask(op->lin);
+ ff_sws_op_list_remove_at(ops, n + 1, 1);
+ goto retry;
+ }
+
+ /* Optimize away zero columns */
+ for (int j = 0; j < 4; j++) {
+ const uint32_t col = SWS_MASK_COL(j);
+ if (!(prev->comps.flags[j] & SWS_COMP_ZERO) || !(op->lin.mask & col))
+ continue;
+ for (int i = 0; i < 4; i++)
+ op->lin.m[i][j] = Q(i == j);
+ op->lin.mask &= ~col;
+ goto retry;
+ }
+
+ /* Optimize away unused rows */
+ for (int i = 0; i < 4; i++) {
+ const uint32_t row = SWS_MASK_ROW(i);
+ if (!next->comps.unused[i] || !(op->lin.mask & row))
+ continue;
+ for (int j = 0; j < 5; j++)
+ op->lin.m[i][j] = Q(i == j);
+ op->lin.mask &= ~row;
+ goto retry;
+ }
+
+ /* Convert constant rows to explicit clear instruction */
+ if (extract_constant_rows(&op->lin, prev->comps, &c)) {
+ RET(ff_sws_op_list_insert_at(ops, n + 1, &(SwsOp) {
+ .op = SWS_OP_CLEAR,
+ .type = op->type,
+ .comps = op->comps,
+ .c = c,
+ }));
+ goto retry;
+ }
+
+ /* Multiplication by scalar constant */
+ if (extract_scalar(&op->lin, prev->comps, next->comps, &c)) {
+ op->op = SWS_OP_SCALE;
+ op->c = c;
+ goto retry;
+ }
+
+ /* Swizzle by fixed pattern */
+ if (extract_swizzle(&op->lin, prev->comps, &swizzle)) {
+ RET(ff_sws_op_list_insert_at(ops, n, &(SwsOp) {
+ .op = SWS_OP_SWIZZLE,
+ .type = op->type,
+ .swizzle = swizzle,
+ }));
+ goto retry;
+ }
+ break;
+ }
+
+ case SWS_OP_SCALE: {
+ const int factor2 = exact_log2_q(op->c.q);
+
+ /* No-op scaling */
+ if (op->c.q.num == 1 && op->c.q.den == 1) {
+ ff_sws_op_list_remove_at(ops, n, 1);
+ goto retry;
+ }
+
+ /* Scaling by integer before conversion to int */
+ if (op->c.q.den == 1 &&
+ next->op == SWS_OP_CONVERT &&
+ ff_sws_pixel_type_is_int(next->convert.to))
+ {
+ op->type = next->convert.to;
+ FFSWAP(SwsOp, *op, *next);
+ goto retry;
+ }
+
+ /* Scaling by exact power of two */
+ if (factor2 && ff_sws_pixel_type_is_int(op->type)) {
+ op->op = factor2 > 0 ? SWS_OP_LSHIFT : SWS_OP_RSHIFT;
+ op->c.u = FFABS(factor2);
+ goto retry;
+ }
+ break;
+ }
+ }
+
+ /* No optimization triggered, move on to next operation */
+ n++;
+ }
+
+ return 0;
+}
commit 16e191c8efbd799a392e7b04e6e3d48a2849ba12
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Thu Mar 6 13:10:05 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:36 2025 +0200
swscale/ops: introduce new low level framework
See docs/swscale-v2.txt for an in-depth introduction to the new approach.
This commit merely introduces the ops definitions and boilerplate functions.
The subsequent commits will flesh out the underlying implementation.
diff --git a/libswscale/Makefile b/libswscale/Makefile
index d5e10d17dc..e0beef4e69 100644
--- a/libswscale/Makefile
+++ b/libswscale/Makefile
@@ -15,6 +15,7 @@ OBJS = alphablend.o \
graph.o \
input.o \
lut3d.o \
+ ops.o \
options.o \
output.o \
rgb2rgb.o \
diff --git a/libswscale/ops.c b/libswscale/ops.c
new file mode 100644
index 0000000000..5ee4b0fbfc
--- /dev/null
+++ b/libswscale/ops.c
@@ -0,0 +1,510 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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 "libavutil/avassert.h"
+#include "libavutil/bswap.h"
+#include "libavutil/mem.h"
+#include "libavutil/rational.h"
+#include "libavutil/refstruct.h"
+
+#include "ops.h"
+#include "ops_internal.h"
+
+const char *ff_sws_pixel_type_name(SwsPixelType type)
+{
+ switch (type) {
+ case SWS_PIXEL_U8: return "u8";
+ case SWS_PIXEL_U16: return "u16";
+ case SWS_PIXEL_U32: return "u32";
+ case SWS_PIXEL_F32: return "f32";
+ case SWS_PIXEL_NONE: return "none";
+ case SWS_PIXEL_TYPE_NB: break;
+ }
+
+ av_unreachable("Invalid pixel type!");
+ return "ERR";
+}
+
+int ff_sws_pixel_type_size(SwsPixelType type)
+{
+ switch (type) {
+ case SWS_PIXEL_U8: return sizeof(uint8_t);
+ case SWS_PIXEL_U16: return sizeof(uint16_t);
+ case SWS_PIXEL_U32: return sizeof(uint32_t);
+ case SWS_PIXEL_F32: return sizeof(float);
+ case SWS_PIXEL_NONE: break;
+ case SWS_PIXEL_TYPE_NB: break;
+ }
+
+ av_unreachable("Invalid pixel type!");
+ return 0;
+}
+
+bool ff_sws_pixel_type_is_int(SwsPixelType type)
+{
+ switch (type) {
+ case SWS_PIXEL_U8:
+ case SWS_PIXEL_U16:
+ case SWS_PIXEL_U32:
+ return true;
+ case SWS_PIXEL_F32:
+ return false;
+ case SWS_PIXEL_NONE:
+ case SWS_PIXEL_TYPE_NB: break;
+ }
+
+ av_unreachable("Invalid pixel type!");
+ return false;
+}
+
+SwsPixelType ff_sws_pixel_type_to_uint(SwsPixelType type)
+{
+ if (!type)
+ return type;
+
+ switch (ff_sws_pixel_type_size(type)) {
+ case 8: return SWS_PIXEL_U8;
+ case 16: return SWS_PIXEL_U16;
+ case 32: return SWS_PIXEL_U32;
+ }
+
+ av_unreachable("Invalid pixel type!");
+ return SWS_PIXEL_NONE;
+}
+
+/* biased towards `a` */
+static AVRational av_min_q(AVRational a, AVRational b)
+{
+ return av_cmp_q(a, b) == 1 ? b : a;
+}
+
+static AVRational av_max_q(AVRational a, AVRational b)
+{
+ return av_cmp_q(a, b) == -1 ? b : a;
+}
+
+void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4])
+{
+ uint64_t mask[4];
+ int shift[4];
+
+ switch (op->op) {
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ return;
+ case SWS_OP_UNPACK: {
+ unsigned val = x[0].num;
+ ff_sws_pack_op_decode(op, mask, shift);
+ for (int i = 0; i < 4; i++)
+ x[i] = Q((val >> shift[i]) & mask[i]);
+ return;
+ }
+ case SWS_OP_PACK: {
+ unsigned val = 0;
+ ff_sws_pack_op_decode(op, mask, shift);
+ for (int i = 0; i < 4; i++)
+ val |= (x[i].num & mask[i]) << shift[i];
+ x[0] = Q(val);
+ return;
+ }
+ case SWS_OP_SWAP_BYTES:
+ switch (ff_sws_pixel_type_size(op->type)) {
+ case 2:
+ for (int i = 0; i < 4; i++)
+ x[i].num = av_bswap16(x[i].num);
+ break;
+ case 4:
+ for (int i = 0; i < 4; i++)
+ x[i].num = av_bswap32(x[i].num);
+ break;
+ }
+ return;
+ case SWS_OP_CLEAR:
+ for (int i = 0; i < 4; i++) {
+ if (op->c.q4[i].den)
+ x[i] = op->c.q4[i];
+ }
+ return;
+ case SWS_OP_LSHIFT: {
+ AVRational mult = Q(1 << op->c.u);
+ for (int i = 0; i < 4; i++)
+ x[i] = x[i].den ? av_mul_q(x[i], mult) : x[i];
+ return;
+ }
+ case SWS_OP_RSHIFT: {
+ AVRational mult = Q(1 << op->c.u);
+ for (int i = 0; i < 4; i++)
+ x[i] = x[i].den ? av_div_q(x[i], mult) : x[i];
+ return;
+ }
+ case SWS_OP_SWIZZLE: {
+ const AVRational orig[4] = { x[0], x[1], x[2], x[3] };
+ for (int i = 0; i < 4; i++)
+ x[i] = orig[op->swizzle.in[i]];
+ return;
+ }
+ case SWS_OP_CONVERT:
+ if (ff_sws_pixel_type_is_int(op->convert.to)) {
+ const AVRational scale = ff_sws_pixel_expand(op->type, op->convert.to);
+ for (int i = 0; i < 4; i++) {
+ x[i] = x[i].den ? Q(x[i].num / x[i].den) : x[i];
+ if (op->convert.expand)
+ x[i] = av_mul_q(x[i], scale);
+ }
+ }
+ return;
+ case SWS_OP_DITHER:
+ for (int i = 0; i < 4; i++)
+ x[i] = x[i].den ? av_add_q(x[i], av_make_q(1, 2)) : x[i];
+ return;
+ case SWS_OP_MIN:
+ for (int i = 0; i < 4; i++)
+ x[i] = av_min_q(x[i], op->c.q4[i]);
+ return;
+ case SWS_OP_MAX:
+ for (int i = 0; i < 4; i++)
+ x[i] = av_max_q(x[i], op->c.q4[i]);
+ return;
+ case SWS_OP_LINEAR: {
+ const AVRational orig[4] = { x[0], x[1], x[2], x[3] };
+ for (int i = 0; i < 4; i++) {
+ AVRational sum = op->lin.m[i][4];
+ for (int j = 0; j < 4; j++)
+ sum = av_add_q(sum, av_mul_q(orig[j], op->lin.m[i][j]));
+ x[i] = sum;
+ }
+ return;
+ }
+ case SWS_OP_SCALE:
+ for (int i = 0; i < 4; i++)
+ x[i] = x[i].den ? av_mul_q(x[i], op->c.q) : x[i];
+ return;
+ }
+
+ av_unreachable("Invalid operation type!");
+}
+
+static void op_uninit(SwsOp *op)
+{
+ switch (op->op) {
+ case SWS_OP_DITHER:
+ av_refstruct_unref(&op->dither.matrix);
+ break;
+ }
+
+ *op = (SwsOp) {0};
+}
+
+SwsOpList *ff_sws_op_list_alloc(void)
+{
+ SwsOpList *ops = av_mallocz(sizeof(SwsOpList));
+ if (!ops)
+ return NULL;
+
+ ff_fmt_clear(&ops->src);
+ ff_fmt_clear(&ops->dst);
+ return ops;
+}
+
+void ff_sws_op_list_free(SwsOpList **p_ops)
+{
+ SwsOpList *ops = *p_ops;
+ if (!ops)
+ return;
+
+ for (int i = 0; i < ops->num_ops; i++)
+ op_uninit(&ops->ops[i]);
+
+ av_freep(&ops->ops);
+ av_free(ops);
+ *p_ops = NULL;
+}
+
+SwsOpList *ff_sws_op_list_duplicate(const SwsOpList *ops)
+{
+ SwsOpList *copy = av_malloc(sizeof(*copy));
+ if (!copy)
+ return NULL;
+
+ int num = ops->num_ops;
+ if (num)
+ num = 1 << av_ceil_log2(num);
+
+ *copy = *ops;
+ copy->ops = av_memdup(ops->ops, num * sizeof(ops->ops[0]));
+ if (!copy->ops) {
+ av_free(copy);
+ return NULL;
+ }
+
+ for (int i = 0; i < ops->num_ops; i++) {
+ const SwsOp *op = &ops->ops[i];
+ switch (op->op) {
+ case SWS_OP_DITHER:
+ av_refstruct_ref(copy->ops[i].dither.matrix);
+ break;
+ }
+ }
+
+ return copy;
+}
+
+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 = index; i < end; i++)
+ ops->ops[i] = ops->ops[i + count];
+ ops->num_ops = end;
+}
+
+int ff_sws_op_list_insert_at(SwsOpList *ops, int index, SwsOp *op)
+{
+ void *ret = av_dynarray2_add((void **) &ops->ops, &ops->num_ops, sizeof(*op), NULL);
+ if (!ret) {
+ op_uninit(op);
+ return AVERROR(ENOMEM);
+ }
+
+ for (int i = ops->num_ops - 1; i > index; i--)
+ ops->ops[i] = ops->ops[i - 1];
+ ops->ops[index] = *op;
+ return 0;
+}
+
+int ff_sws_op_list_append(SwsOpList *ops, SwsOp *op)
+{
+ return ff_sws_op_list_insert_at(ops, ops->num_ops, op);
+}
+
+int ff_sws_op_list_max_size(const SwsOpList *ops)
+{
+ int max_size = 0;
+ for (int i = 0; i < ops->num_ops; i++) {
+ const int size = ff_sws_pixel_type_size(ops->ops[i].type);
+ max_size = FFMAX(max_size, size);
+ }
+
+ return max_size;
+}
+
+uint32_t ff_sws_linear_mask(const SwsLinearOp c)
+{
+ uint32_t mask = 0;
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 5; j++) {
+ if (av_cmp_q(c.m[i][j], Q(i == j)))
+ mask |= SWS_MASK(i, j);
+ }
+ }
+ return mask;
+}
+
+static const char *describe_lin_mask(uint32_t mask)
+{
+ /* Try to be fairly descriptive without assuming too much */
+ static const struct {
+ char name[24];
+ uint32_t mask;
+ } patterns[] = {
+ { "noop", 0 },
+ { "luma", SWS_MASK_LUMA },
+ { "alpha", SWS_MASK_ALPHA },
+ { "luma+alpha", SWS_MASK_LUMA | SWS_MASK_ALPHA },
+ { "dot3", 0x7 },
+ { "dot4", 0xF },
+ { "row0", SWS_MASK_ROW(0) },
+ { "row0+alpha", SWS_MASK_ROW(0) | SWS_MASK_ALPHA },
+ { "col0", SWS_MASK_COL(0) },
+ { "col0+off3", SWS_MASK_COL(0) | SWS_MASK_OFF3 },
+ { "off3", SWS_MASK_OFF3 },
+ { "off3+alpha", SWS_MASK_OFF3 | SWS_MASK_ALPHA },
+ { "diag3", SWS_MASK_DIAG3 },
+ { "diag4", SWS_MASK_DIAG4 },
+ { "diag3+alpha", SWS_MASK_DIAG3 | SWS_MASK_ALPHA },
+ { "diag3+off3", SWS_MASK_DIAG3 | SWS_MASK_OFF3 },
+ { "diag3+off3+alpha", SWS_MASK_DIAG3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA },
+ { "diag4+off4", SWS_MASK_DIAG4 | SWS_MASK_OFF4 },
+ { "matrix3", SWS_MASK_MAT3 },
+ { "matrix3+off3", SWS_MASK_MAT3 | SWS_MASK_OFF3 },
+ { "matrix3+off3+alpha", SWS_MASK_MAT3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA },
+ { "matrix4", SWS_MASK_MAT4 },
+ { "matrix4+off4", SWS_MASK_MAT4 | SWS_MASK_OFF4 },
+ };
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(patterns); i++) {
+ if (!(mask & ~patterns[i].mask))
+ return patterns[i].name;
+ }
+
+ av_unreachable("Invalid linear mask!");
+ return "ERR";
+}
+
+static char describe_comp_flags(unsigned flags)
+{
+ if (flags & SWS_COMP_GARBAGE)
+ return 'X';
+ else if (flags & SWS_COMP_ZERO)
+ return '0';
+ else if (flags & SWS_COMP_EXACT)
+ return '+';
+ else
+ return '.';
+}
+
+static const char *print_q(const AVRational q, char buf[], int buf_len)
+{
+ if (!q.den) {
+ return q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan";
+ } else if (q.den == 1) {
+ snprintf(buf, buf_len, "%d", q.num);
+ return buf;
+ } else if (abs(q.num) > 1000 || abs(q.den) > 1000) {
+ snprintf(buf, buf_len, "%f", av_q2d(q));
+ return buf;
+ } else {
+ snprintf(buf, buf_len, "%d/%d", q.num, q.den);
+ return buf;
+ }
+}
+
+#define PRINTQ(q) print_q(q, (char[32]){0}, sizeof(char[32]) - 1)
+
+void ff_sws_op_list_print(void *log, int lev, const SwsOpList *ops)
+{
+ if (!ops->num_ops) {
+ av_log(log, lev, " (empty)\n");
+ return;
+ }
+
+ for (int i = 0; i < ops->num_ops; i++) {
+ const SwsOp *op = &ops->ops[i];
+ 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' : '.',
+ describe_comp_flags(op->comps.flags[0]),
+ describe_comp_flags(op->comps.flags[1]),
+ describe_comp_flags(op->comps.flags[2]),
+ describe_comp_flags(op->comps.flags[3]));
+
+ switch (op->op) {
+ case SWS_OP_INVALID:
+ av_log(log, lev, "SWS_OP_INVALID\n");
+ break;
+ case SWS_OP_READ:
+ case SWS_OP_WRITE:
+ av_log(log, lev, "%-20s: %d elem(s) %s >> %d\n",
+ op->op == SWS_OP_READ ? "SWS_OP_READ"
+ : "SWS_OP_WRITE",
+ op->rw.elems, op->rw.packed ? "packed" : "planar",
+ op->rw.frac);
+ break;
+ case SWS_OP_SWAP_BYTES:
+ av_log(log, lev, "SWS_OP_SWAP_BYTES\n");
+ break;
+ case SWS_OP_LSHIFT:
+ av_log(log, lev, "%-20s: << %u\n", "SWS_OP_LSHIFT", op->c.u);
+ break;
+ case SWS_OP_RSHIFT:
+ av_log(log, lev, "%-20s: >> %u\n", "SWS_OP_RSHIFT", op->c.u);
+ break;
+ case SWS_OP_PACK:
+ case SWS_OP_UNPACK:
+ av_log(log, lev, "%-20s: {%d %d %d %d}\n",
+ op->op == SWS_OP_PACK ? "SWS_OP_PACK"
+ : "SWS_OP_UNPACK",
+ 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", "SWS_OP_CLEAR",
+ 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", "SWS_OP_SWIZZLE",
+ 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", "SWS_OP_CONVERT",
+ 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\n", "SWS_OP_DITHER",
+ 1 << op->dither.size_log2, 1 << op->dither.size_log2);
+ break;
+ case SWS_OP_MIN:
+ av_log(log, lev, "%-20s: x <= {%s %s %s %s}\n", "SWS_OP_MIN",
+ 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", "SWS_OP_MAX",
+ 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",
+ "SWS_OP_LINEAR", 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: * %s\n", "SWS_OP_SCALE",
+ PRINTQ(op->c.q));
+ break;
+ case SWS_OP_TYPE_NB:
+ break;
+ }
+
+ 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 ||
+ op->comps.max[2].den || op->comps.max[3].den)
+ {
+ av_log(log, AV_LOG_TRACE, " min: {%s, %s, %s, %s}, max: {%s, %s, %s, %s}\n",
+ PRINTQ(op->comps.min[0]), PRINTQ(op->comps.min[1]),
+ PRINTQ(op->comps.min[2]), PRINTQ(op->comps.min[3]),
+ PRINTQ(op->comps.max[0]), PRINTQ(op->comps.max[1]),
+ PRINTQ(op->comps.max[2]), PRINTQ(op->comps.max[3]));
+ }
+
+ }
+
+ av_log(log, lev, " (X = unused, + = exact, 0 = zero)\n");
+}
diff --git a/libswscale/ops.h b/libswscale/ops.h
new file mode 100644
index 0000000000..85788d7292
--- /dev/null
+++ b/libswscale/ops.h
@@ -0,0 +1,243 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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
+ */
+
+#ifndef SWSCALE_OPS_H
+#define SWSCALE_OPS_H
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdalign.h>
+
+#include "graph.h"
+
+typedef enum SwsPixelType {
+ SWS_PIXEL_NONE = 0,
+ SWS_PIXEL_U8,
+ SWS_PIXEL_U16,
+ SWS_PIXEL_U32,
+ SWS_PIXEL_F32,
+ SWS_PIXEL_TYPE_NB
+} SwsPixelType;
+
+const char *ff_sws_pixel_type_name(SwsPixelType type);
+int ff_sws_pixel_type_size(SwsPixelType type) av_const;
+bool ff_sws_pixel_type_is_int(SwsPixelType type) av_const;
+SwsPixelType ff_sws_pixel_type_to_uint(SwsPixelType type) av_const;
+
+typedef enum SwsOpType {
+ SWS_OP_INVALID = 0,
+
+ /* Input/output handling */
+ SWS_OP_READ, /* gather raw pixels from planes */
+ SWS_OP_WRITE, /* write raw pixels to planes */
+ SWS_OP_SWAP_BYTES, /* swap byte order (for differing endianness) */
+ SWS_OP_UNPACK, /* split tightly packed data into components */
+ SWS_OP_PACK, /* compress components into tightly packed data */
+
+ /* Pixel manipulation */
+ SWS_OP_CLEAR, /* clear pixel values */
+ SWS_OP_LSHIFT, /* logical left shift of raw pixel values by (u8) */
+ SWS_OP_RSHIFT, /* right shift of raw pixel values by (u8) */
+ SWS_OP_SWIZZLE, /* rearrange channel order, or duplicate channels */
+ SWS_OP_CONVERT, /* convert (cast) between formats */
+ SWS_OP_DITHER, /* add dithering noise */
+
+ /* Arithmetic operations */
+ SWS_OP_LINEAR, /* generalized linear affine transform */
+ SWS_OP_SCALE, /* multiplication by scalar (q) */
+ SWS_OP_MIN, /* numeric minimum (q4) */
+ SWS_OP_MAX, /* numeric maximum (q4) */
+
+ SWS_OP_TYPE_NB,
+} SwsOpType;
+
+enum SwsCompFlags {
+ SWS_COMP_GARBAGE = 1 << 0, /* contents are undefined / garbage data */
+ SWS_COMP_EXACT = 1 << 1, /* value is an in-range, exact, integer */
+ SWS_COMP_ZERO = 1 << 2, /* known to be a constant zero */
+};
+
+typedef union SwsConst {
+ /* Generic constant value */
+ AVRational q4[4];
+ AVRational q;
+ unsigned u;
+} SwsConst;
+
+static_assert(sizeof(SwsConst) == sizeof(AVRational) * 4,
+ "First field of SwsConst should span the entire union");
+
+typedef struct SwsComps {
+ unsigned flags[4]; /* knowledge about (output) component contents */
+ bool unused[4]; /* which input components are definitely unused */
+
+ /* Keeps track of the known possible value range, or {0, 0} for undefined
+ * or (unknown range) floating point inputs */
+ AVRational min[4], max[4];
+} SwsComps;
+
+typedef struct SwsReadWriteOp {
+ uint8_t elems; /* number of elements (of type `op.type`) to read/write */
+ uint8_t frac; /* fractional pixel step factor (log2) */
+ bool packed; /* read multiple elements from a single plane */
+
+ /** Examples:
+ * rgba = 4x u8 packed
+ * yuv444p = 3x u8
+ * rgb565 = 1x u16 <- use SWS_OP_UNPACK to unpack
+ * monow = 1x u8 (frac 3)
+ * rgb4 = 1x u8 (frac 1)
+ */
+} SwsReadWriteOp;
+
+typedef struct SwsPackOp {
+ uint8_t pattern[4]; /* bit depth pattern, from MSB to LSB */
+} SwsPackOp;
+
+typedef struct SwsSwizzleOp {
+ /**
+ * Input component for each output component:
+ * Out[x] := In[swizzle.in[x]]
+ */
+ union {
+ uint32_t mask;
+ uint8_t in[4];
+ struct { uint8_t x, y, z, w; };
+ };
+} SwsSwizzleOp;
+
+#define SWS_SWIZZLE(X,Y,Z,W) ((SwsSwizzleOp) { .in = {X, Y, Z, W} })
+
+typedef struct SwsConvertOp {
+ SwsPixelType to; /* type of pixel to convert to */
+ bool expand; /* if true, integers are expanded to the full range */
+} SwsConvertOp;
+
+typedef struct SwsDitherOp {
+ AVRational *matrix; /* tightly packed dither matrix (refstruct) */
+ int size_log2; /* size (in bits) of the dither matrix */
+} SwsDitherOp;
+
+typedef struct SwsLinearOp {
+ /**
+ * Generalized 5x5 affine transformation:
+ * [ Out.x ] = [ A B C D E ]
+ * [ Out.y ] = [ F G H I J ] * [ x y z w 1 ]
+ * [ Out.z ] = [ K L M N O ]
+ * [ Out.w ] = [ P Q R S T ]
+ *
+ * The mask keeps track of which components differ from an identity matrix.
+ * There may be more efficient implementations of particular subsets, for
+ * example the common subset of {A, E, G, J, M, O} can be implemented with
+ * just three fused multiply-add operations.
+ */
+ AVRational m[4][5];
+ uint32_t mask; /* m[i][j] <-> 1 << (5 * i + j) */
+} SwsLinearOp;
+
+#define SWS_MASK(I, J) (1 << (5 * (I) + (J)))
+#define SWS_MASK_OFF(I) SWS_MASK(I, 4)
+#define SWS_MASK_ROW(I) (0x1F << (5 * (I)))
+#define SWS_MASK_COL(J) (0x8421 << J)
+
+enum {
+ SWS_MASK_ALL = (1 << 20) - 1,
+ SWS_MASK_LUMA = SWS_MASK(0, 0) | SWS_MASK_OFF(0),
+ SWS_MASK_ALPHA = SWS_MASK(3, 3) | SWS_MASK_OFF(3),
+
+ SWS_MASK_DIAG3 = SWS_MASK(0, 0) | SWS_MASK(1, 1) | SWS_MASK(2, 2),
+ SWS_MASK_OFF3 = SWS_MASK_OFF(0) | SWS_MASK_OFF(1) | SWS_MASK_OFF(2),
+ SWS_MASK_MAT3 = SWS_MASK(0, 0) | SWS_MASK(0, 1) | SWS_MASK(0, 2) |
+ SWS_MASK(1, 0) | SWS_MASK(1, 1) | SWS_MASK(1, 2) |
+ SWS_MASK(2, 0) | SWS_MASK(2, 1) | SWS_MASK(2, 2),
+
+ SWS_MASK_DIAG4 = SWS_MASK_DIAG3 | SWS_MASK(3, 3),
+ SWS_MASK_OFF4 = SWS_MASK_OFF3 | SWS_MASK_OFF(3),
+ SWS_MASK_MAT4 = SWS_MASK_ALL & ~SWS_MASK_OFF4,
+};
+
+/* Helper function to compute the correct mask */
+uint32_t ff_sws_linear_mask(SwsLinearOp);
+
+typedef struct SwsOp {
+ SwsOpType op; /* operation to perform */
+ SwsPixelType type; /* pixel type to operate on */
+ union {
+ SwsLinearOp lin;
+ SwsReadWriteOp rw;
+ SwsPackOp pack;
+ SwsSwizzleOp swizzle;
+ SwsConvertOp convert;
+ SwsDitherOp dither;
+ SwsConst c;
+ };
+
+ /* For use internal use inside ff_sws_*() functions */
+ SwsComps comps;
+} SwsOp;
+
+/**
+ * Frees any allocations associated with an SwsOp and sets it to {0}.
+ */
+void ff_sws_op_uninit(SwsOp *op);
+
+/**
+ * Apply an operation to an AVRational. No-op for read/write operations.
+ */
+void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]);
+
+/**
+ * Helper struct for representing a list of operations.
+ */
+typedef struct SwsOpList {
+ SwsOp *ops;
+ int num_ops;
+
+ /* Purely informative metadata associated with this operation list */
+ SwsFormat src, dst;
+} SwsOpList;
+
+SwsOpList *ff_sws_op_list_alloc(void);
+void ff_sws_op_list_free(SwsOpList **ops);
+
+/**
+ * Returns a duplicate of `ops`, or NULL on OOM.
+ */
+SwsOpList *ff_sws_op_list_duplicate(const SwsOpList *ops);
+
+/**
+ * Returns the size of the largest pixel type used in `ops`.
+ */
+int ff_sws_op_list_max_size(const SwsOpList *ops);
+
+/**
+ * These will take over ownership of `op` and set it to {0}, even on failure.
+ */
+int ff_sws_op_list_append(SwsOpList *ops, SwsOp *op);
+int ff_sws_op_list_insert_at(SwsOpList *ops, int index, SwsOp *op);
+
+void ff_sws_op_list_remove_at(SwsOpList *ops, int index, int count);
+
+/**
+ * Print out the contents of an operation list.
+ */
+void ff_sws_op_list_print(void *log_ctx, int log_level, const SwsOpList *ops);
+
+#endif
diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h
new file mode 100644
index 0000000000..78dc5d7d0a
--- /dev/null
+++ b/libswscale/ops_internal.h
@@ -0,0 +1,48 @@
+/**
+ * Copyright (C) 2025 Niklas Haas
+ *
+ * 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
+ */
+
+#ifndef SWSCALE_OPS_INTERNAL_H
+#define SWSCALE_OPS_INTERNAL_H
+
+#include "ops.h"
+
+#define Q(N) ((AVRational) { N, 1 })
+
+static inline AVRational ff_sws_pixel_expand(SwsPixelType from, SwsPixelType to)
+{
+ const int src = ff_sws_pixel_type_size(from);
+ const int dst = ff_sws_pixel_type_size(to);
+ int scale = 0;
+ for (int i = 0; i < dst / src; i++)
+ scale = scale << src * 8 | 1;
+ return Q(scale);
+}
+
+static inline void ff_sws_pack_op_decode(const SwsOp *op, uint64_t mask[4], int shift[4])
+{
+ const int size = ff_sws_pixel_type_size(op->type) * 8;
+ for (int i = 0; i < 4; i++) {
+ const int bits = op->pack.pattern[i];
+ mask[i] = (UINT64_C(1) << bits) - 1;
+ shift[i] = (i ? shift[i - 1] : size) - bits;
+ }
+}
+
+#endif /* SWSCALE_OPS_INTERNAL_H */
commit ce0938da8cd113d4a7c6d1e4f49f6bbad216855a
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Mon Mar 31 12:22:34 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:28:35 2025 +0200
swscale: add SWS_UNSTABLE flag
Give users and developers a way to opt in to the new format conversion code,
and more code from the swscale rewrite in general, even while development is
still ongoing.
diff --git a/doc/APIchanges b/doc/APIchanges
index 7fbe3a0769..bd5a76cbe6 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
API changes, most recent first:
+2025-09-xx - xxxxxxxxxx - lsws 9.3.100 - swscale.h
+ Add SWS_UNSTABLE flag.
+
2025-09-01 - xxxxxxxx - lavc 62.14.100 - packet.h
Add AV_PKT_DATA_EXIF
diff --git a/doc/scaler.texi b/doc/scaler.texi
index a9ed10e34e..0c2b952395 100644
--- a/doc/scaler.texi
+++ b/doc/scaler.texi
@@ -68,6 +68,10 @@ Select full chroma input.
@item bitexact
Enable bitexact output.
+
+@item unstable
+Allow the use of experimental new code. May subtly affect the output or even
+produce wrong results. For testing only.
@end table
@item srcw @var{(API only)}
diff --git a/libswscale/options.c b/libswscale/options.c
index feecae8c89..06e51dcfe9 100644
--- a/libswscale/options.c
+++ b/libswscale/options.c
@@ -50,6 +50,7 @@ static const AVOption swscale_options[] = {
{ "full_chroma_inp", "full chroma input", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_FULL_CHR_H_INP }, .flags = VE, .unit = "sws_flags" },
{ "bitexact", "bit-exact mode", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BITEXACT }, .flags = VE, .unit = "sws_flags" },
{ "error_diffusion", "error diffusion dither", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_ERROR_DIFFUSION}, .flags = VE, .unit = "sws_flags" },
+ { "unstable", "allow experimental new code", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_UNSTABLE }, .flags = VE, .unit = "sws_flags" },
{ "param0", "scaler param 0", OFFSET(scaler_params[0]), AV_OPT_TYPE_DOUBLE, { .dbl = SWS_PARAM_DEFAULT }, INT_MIN, INT_MAX, VE },
{ "param1", "scaler param 1", OFFSET(scaler_params[1]), AV_OPT_TYPE_DOUBLE, { .dbl = SWS_PARAM_DEFAULT }, INT_MIN, INT_MAX, VE },
diff --git a/libswscale/swscale.h b/libswscale/swscale.h
index 39e23075a4..345e5458ce 100644
--- a/libswscale/swscale.h
+++ b/libswscale/swscale.h
@@ -155,6 +155,13 @@ typedef enum SwsFlags {
SWS_ACCURATE_RND = 1 << 18,
SWS_BITEXACT = 1 << 19,
+ /**
+ * Allow using experimental new code paths. This may be faster, slower,
+ * or produce different output, with semantics subject to change at any
+ * point in time. For testing and debugging purposes only.
+ */
+ SWS_UNSTABLE = 1 << 20,
+
/**
* Deprecated flags.
*/
diff --git a/libswscale/version.h b/libswscale/version.h
index 79f5c69f1c..51eb013a29 100644
--- a/libswscale/version.h
+++ b/libswscale/version.h
@@ -28,7 +28,7 @@
#include "version_major.h"
-#define LIBSWSCALE_VERSION_MINOR 2
+#define LIBSWSCALE_VERSION_MINOR 3
#define LIBSWSCALE_VERSION_MICRO 100
#define LIBSWSCALE_VERSION_INT AV_VERSION_INT(LIBSWSCALE_VERSION_MAJOR, \
commit 8406c56b0cb36e8aed56628d03702f90019f104d
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Tue Apr 8 20:43:50 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:27:53 2025 +0200
tests/checkasm: generalize DEF_CHECKASM_CHECK_FUNC to floats
We split the standard macro into its body (implementation) and declaration,
and use a macro argument in place of the raw `memcmp` call, with the major
difference that we now take the number of pixels to compare instead of the
number of bytes (to match the signature of float_near_ulp_array).
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index cd6063f508..8693086575 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -1196,14 +1196,8 @@ static int check_err(const char *file, int line,
return 0;
}
-#define DEF_CHECKASM_CHECK_FUNC(type, fmt) \
-int checkasm_check_##type(const char *file, int line, \
- const type *buf1, ptrdiff_t stride1, \
- const type *buf2, ptrdiff_t stride2, \
- int w, int h, const char *name, \
- int align_w, int align_h, \
- int padding) \
-{ \
+#define DEF_CHECKASM_CHECK_BODY(compare, type, fmt) \
+do { \
int64_t aligned_w = (w - 1LL + align_w) & ~(align_w - 1); \
int64_t aligned_h = (h - 1LL + align_h) & ~(align_h - 1); \
int err = 0; \
@@ -1213,7 +1207,7 @@ int checkasm_check_##type(const char *file, int line, \
stride1 /= sizeof(*buf1); \
stride2 /= sizeof(*buf2); \
for (y = 0; y < h; y++) \
- if (memcmp(&buf1[y*stride1], &buf2[y*stride2], w*sizeof(*buf1))) \
+ if (!compare(&buf1[y*stride1], &buf2[y*stride2], w)) \
break; \
if (y != h) { \
if (check_err(file, line, name, w, h, &err)) \
@@ -1235,38 +1229,50 @@ int checkasm_check_##type(const char *file, int line, \
buf2 -= h*stride2; \
} \
for (y = -padding; y < 0; y++) \
- if (memcmp(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \
- (w + 2*padding)*sizeof(*buf1))) { \
+ if (!compare(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \
+ w + 2*padding)) { \
if (check_err(file, line, name, w, h, &err)) \
return 1; \
fprintf(stderr, " overwrite above\n"); \
break; \
} \
for (y = aligned_h; y < aligned_h + padding; y++) \
- if (memcmp(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \
- (w + 2*padding)*sizeof(*buf1))) { \
+ if (!compare(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \
+ w + 2*padding)) { \
if (check_err(file, line, name, w, h, &err)) \
return 1; \
fprintf(stderr, " overwrite below\n"); \
break; \
} \
for (y = 0; y < h; y++) \
- if (memcmp(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \
- padding*sizeof(*buf1))) { \
+ if (!compare(&buf1[y*stride1 - padding], &buf2[y*stride2 - padding], \
+ padding)) { \
if (check_err(file, line, name, w, h, &err)) \
return 1; \
fprintf(stderr, " overwrite left\n"); \
break; \
} \
for (y = 0; y < h; y++) \
- if (memcmp(&buf1[y*stride1 + aligned_w], &buf2[y*stride2 + aligned_w], \
- padding*sizeof(*buf1))) { \
+ if (!compare(&buf1[y*stride1 + aligned_w], &buf2[y*stride2 + aligned_w], \
+ padding)) { \
if (check_err(file, line, name, w, h, &err)) \
return 1; \
fprintf(stderr, " overwrite right\n"); \
break; \
} \
return err; \
+} while (0)
+
+#define cmp_int(a, b, len) (!memcmp(a, b, (len) * sizeof(*(a))))
+#define DEF_CHECKASM_CHECK_FUNC(type, fmt) \
+int checkasm_check_##type(const char *file, int line, \
+ const type *buf1, ptrdiff_t stride1, \
+ const type *buf2, ptrdiff_t stride2, \
+ int w, int h, const char *name, \
+ int align_w, int align_h, \
+ int padding) \
+{ \
+ DEF_CHECKASM_CHECK_BODY(cmp_int, type, fmt); \
}
DEF_CHECKASM_CHECK_FUNC(uint8_t, "%02x")
@@ -1274,3 +1280,15 @@ DEF_CHECKASM_CHECK_FUNC(uint16_t, "%04x")
DEF_CHECKASM_CHECK_FUNC(uint32_t, "%08x")
DEF_CHECKASM_CHECK_FUNC(int16_t, "%6d")
DEF_CHECKASM_CHECK_FUNC(int32_t, "%9d")
+
+int checkasm_check_float_ulp(const char *file, int line,
+ const float *buf1, ptrdiff_t stride1,
+ const float *buf2, ptrdiff_t stride2,
+ int w, int h, const char *name,
+ unsigned max_ulp, int align_w, int align_h,
+ int padding)
+{
+ #define cmp_float(a, b, len) float_near_ulp_array(a, b, max_ulp, len)
+ DEF_CHECKASM_CHECK_BODY(cmp_float, float, "%g");
+ #undef cmp_float
+}
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 375ad2d930..714afd1dc5 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -426,6 +426,13 @@ DECL_CHECKASM_CHECK_FUNC(uint32_t);
DECL_CHECKASM_CHECK_FUNC(int16_t);
DECL_CHECKASM_CHECK_FUNC(int32_t);
+int checkasm_check_float_ulp(const char *file, int line,
+ const float *buf1, ptrdiff_t stride1,
+ const float *buf2, ptrdiff_t stride2,
+ int w, int h, const char *name,
+ unsigned max_ulp, int align_w, int align_h,
+ int padding);
+
#define PASTE(a,b) a ## b
#define CONCAT(a,b) PASTE(a,b)
commit faf62cbdf5df78884c47b57aac74df3461052974
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Mon Mar 31 15:05:37 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:27:53 2025 +0200
tests/checkasm: increase number of runs in between measurements
Sometimes, when measuring very small functions, rdtsc is not accurate enough
to get a reliable measurement. This increases the number of runs inside the
inner loop from 4 to 32, which should help a lot. Less important when using
the more precise linux-perf API, but still useful.
There should be no user-visible change since the number of runs is adjusted
to keep the total time spent measuring the same.
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 968961c03c..cd6063f508 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -637,7 +637,7 @@ static inline double avg_cycles_per_call(const CheckasmPerf *const p)
if (p->iterations) {
const double cycles = (double)(10 * p->cycles) / p->iterations - state.nop_time;
if (cycles > 0.0)
- return cycles / 4.0; /* 4 calls per iteration */
+ return cycles / 32.0; /* 32 calls per iteration */
}
return 0.0;
}
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 6e59c6ebf0..375ad2d930 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -345,6 +345,22 @@ typedef struct CheckasmPerf {
#define PERF_STOP(t) t = AV_READ_TIME() - t
#endif
+#define CALL4(...)\
+ do {\
+ tfunc(__VA_ARGS__); \
+ tfunc(__VA_ARGS__); \
+ tfunc(__VA_ARGS__); \
+ tfunc(__VA_ARGS__); \
+ } while (0)
+
+#define CALL16(...)\
+ do {\
+ CALL4(__VA_ARGS__); \
+ CALL4(__VA_ARGS__); \
+ CALL4(__VA_ARGS__); \
+ CALL4(__VA_ARGS__); \
+ } while (0)
+
/* Benchmark the function */
#define bench_new(...)\
do {\
@@ -355,14 +371,12 @@ typedef struct CheckasmPerf {
uint64_t tsum = 0;\
uint64_t ti, tcount = 0;\
uint64_t t = 0; \
- const uint64_t truns = bench_runs;\
+ const uint64_t truns = FFMAX(bench_runs >> 3, 1);\
checkasm_set_signal_handler_state(1);\
for (ti = 0; ti < truns; ti++) {\
PERF_START(t);\
- tfunc(__VA_ARGS__);\
- tfunc(__VA_ARGS__);\
- tfunc(__VA_ARGS__);\
- tfunc(__VA_ARGS__);\
+ CALL16(__VA_ARGS__);\
+ CALL16(__VA_ARGS__);\
PERF_STOP(t);\
if (t*tcount <= tsum*4 && ti > 0) {\
tsum += t;\
commit 5a28f392b90397d9cc9c40646c5d9529c8851f52
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Mon Jul 14 10:45:06 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:27:53 2025 +0200
tests/swscale: avoid binary literals
These are C23-only.
diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 0f1f8311c9..564f38af2e 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -126,9 +126,9 @@ static float estimate_quantization_noise(enum AVPixelFormat fmt)
static int fmt_comps(enum AVPixelFormat fmt)
{
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt);
- int comps = desc->nb_components >= 3 ? 0b111 : 0b1;
+ int comps = desc->nb_components >= 3 ? 0x7 : 0x1;
if (desc->flags & AV_PIX_FMT_FLAG_ALPHA)
- comps |= 0b1000;
+ comps |= 0x8;
return comps;
}
commit 322f606fa81ef6fe63cc78b291d3438274561c65
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Mon May 5 12:57:04 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:27:53 2025 +0200
swscale/format: add ff_fmt_clear()
Reset an SwsFormat to its fully unset/invalid state.
diff --git a/libswscale/format.h b/libswscale/format.h
index 3b6d745159..be92038f4f 100644
--- a/libswscale/format.h
+++ b/libswscale/format.h
@@ -85,6 +85,20 @@ typedef struct SwsFormat {
SwsColor color;
} SwsFormat;
+static inline void ff_fmt_clear(SwsFormat *fmt)
+{
+ *fmt = (SwsFormat) {
+ .format = AV_PIX_FMT_NONE,
+ .range = AVCOL_RANGE_UNSPECIFIED,
+ .csp = AVCOL_SPC_UNSPECIFIED,
+ .loc = AVCHROMA_LOC_UNSPECIFIED,
+ .color = {
+ .prim = AVCOL_PRI_UNSPECIFIED,
+ .trc = AVCOL_TRC_UNSPECIFIED,
+ },
+ };
+}
+
/**
* This function also sanitizes and strips the input data, removing irrelevant
* fields for certain formats.
commit 041a318299e072c8ca5c4406610626946ef8b400
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Sun Feb 23 16:52:21 2025 +0100
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:27:53 2025 +0200
swscale/format: rename legacy format conversion table
diff --git a/libswscale/format.c b/libswscale/format.c
index 5bb4cd0c29..adc06671da 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -24,14 +24,14 @@
#include "format.h"
-typedef struct FormatEntry {
+typedef struct LegacyFormatEntry {
uint8_t is_supported_in :1;
uint8_t is_supported_out :1;
uint8_t is_supported_endianness :1;
-} FormatEntry;
+} LegacyFormatEntry;
/* Format support table for legacy swscale */
-static const FormatEntry format_entries[] = {
+static const LegacyFormatEntry legacy_format_entries[] = {
[AV_PIX_FMT_YUV420P] = { 1, 1 },
[AV_PIX_FMT_YUYV422] = { 1, 1 },
[AV_PIX_FMT_RGB24] = { 1, 1 },
@@ -270,20 +270,20 @@ static const FormatEntry format_entries[] = {
int sws_isSupportedInput(enum AVPixelFormat pix_fmt)
{
- return (unsigned)pix_fmt < FF_ARRAY_ELEMS(format_entries) ?
- format_entries[pix_fmt].is_supported_in : 0;
+ return (unsigned)pix_fmt < FF_ARRAY_ELEMS(legacy_format_entries) ?
+ legacy_format_entries[pix_fmt].is_supported_in : 0;
}
int sws_isSupportedOutput(enum AVPixelFormat pix_fmt)
{
- return (unsigned)pix_fmt < FF_ARRAY_ELEMS(format_entries) ?
- format_entries[pix_fmt].is_supported_out : 0;
+ return (unsigned)pix_fmt < FF_ARRAY_ELEMS(legacy_format_entries) ?
+ legacy_format_entries[pix_fmt].is_supported_out : 0;
}
int sws_isSupportedEndiannessConversion(enum AVPixelFormat pix_fmt)
{
- return (unsigned)pix_fmt < FF_ARRAY_ELEMS(format_entries) ?
- format_entries[pix_fmt].is_supported_endianness : 0;
+ return (unsigned)pix_fmt < FF_ARRAY_ELEMS(legacy_format_entries) ?
+ legacy_format_entries[pix_fmt].is_supported_endianness : 0;
}
/**
commit c18676aae476d2e9ef1d3bf945890869a8d3490e
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Tue Jun 10 14:56:26 2025 +0200
Commit: Niklas Haas <git(a)haasn.dev>
CommitDate: Mon Sep 1 19:27:53 2025 +0200
swscale/graph: pass per-pass image pointers to setup()
This behavior had no real justification and was just incredibly confusing,
since the in/out pointers passet to setup() did not match those passed to
run(), all for what is arguably an exception anyways (the palette setup).
diff --git a/libswscale/graph.c b/libswscale/graph.c
index 9d46f8f27f..62c0fe3eb7 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -169,6 +169,8 @@ static void free_legacy_swscale(void *priv)
static void setup_legacy_swscale(const SwsImg *out, const SwsImg *in,
const SwsPass *pass)
{
+ const SwsGraph *graph = pass->graph;
+ const SwsImg *in_orig = &graph->exec.input;
SwsContext *sws = pass->priv;
SwsInternal *c = sws_internal(sws);
if (sws->flags & SWS_BITEXACT && sws->dither == SWS_DITHER_ED && c->dither_error[0]) {
@@ -177,7 +179,7 @@ static void setup_legacy_swscale(const SwsImg *out, const SwsImg *in,
}
if (usePal(sws->src_format))
- ff_update_palette(c, (const uint32_t *) in->data[1]);
+ ff_update_palette(c, (const uint32_t *) in_orig->data[1]);
}
static inline SwsContext *slice_ctx(const SwsPass *pass, int y)
@@ -715,8 +717,10 @@ void ff_sws_graph_run(SwsGraph *graph, uint8_t *const out_data[4],
for (int i = 0; i < graph->num_passes; i++) {
const SwsPass *pass = graph->passes[i];
graph->exec.pass = pass;
- if (pass->setup)
- pass->setup(out, in, pass);
+ if (pass->setup) {
+ pass->setup(pass->output.fmt != AV_PIX_FMT_NONE ? &pass->output : out,
+ pass->input ? &pass->input->output : in, pass);
+ }
avpriv_slicethread_execute(graph->slicethread, pass->num_slices, 0);
}
}
diff --git a/libswscale/graph.h b/libswscale/graph.h
index 0630b31ce6..b829bac88c 100644
--- a/libswscale/graph.h
+++ b/libswscale/graph.h
@@ -92,8 +92,6 @@ struct SwsPass {
/**
* Called once from the main thread before running the filter. Optional.
- * `out` and `in` always point to the main image input/output, regardless
- * of `input` and `output` fields.
*/
void (*setup)(const SwsImg *out, const SwsImg *in, const SwsPass *pass);
-----------------------------------------------------------------------
Summary of changes:
configure | 4 +-
doc/APIchanges | 3 +
doc/scaler.texi | 4 +
libswscale/Makefile | 7 +
libswscale/format.c | 968 ++++++++++++++++++++++++++++++++++++-
libswscale/format.h | 37 ++
libswscale/graph.c | 98 +++-
libswscale/graph.h | 2 -
libswscale/ops.c | 861 +++++++++++++++++++++++++++++++++
libswscale/ops.h | 269 +++++++++++
libswscale/ops_backend.c | 109 +++++
libswscale/ops_backend.h | 167 +++++++
libswscale/ops_chain.c | 291 +++++++++++
libswscale/ops_chain.h | 134 ++++++
libswscale/ops_internal.h | 161 +++++++
libswscale/ops_memcpy.c | 132 +++++
libswscale/ops_optimizer.c | 866 +++++++++++++++++++++++++++++++++
libswscale/ops_tmpl_common.c | 176 +++++++
libswscale/ops_tmpl_float.c | 257 ++++++++++
libswscale/ops_tmpl_int.c | 603 +++++++++++++++++++++++
libswscale/options.c | 1 +
libswscale/swscale.h | 7 +
libswscale/tests/swscale.c | 4 +-
libswscale/version.h | 2 +-
libswscale/x86/Makefile | 6 +
libswscale/x86/ops.c | 723 ++++++++++++++++++++++++++++
libswscale/x86/ops_common.asm | 305 ++++++++++++
libswscale/x86/ops_float.asm | 389 +++++++++++++++
libswscale/x86/ops_int.asm | 1062 +++++++++++++++++++++++++++++++++++++++++
tests/checkasm/Makefile | 8 +-
tests/checkasm/checkasm.c | 55 ++-
tests/checkasm/checkasm.h | 32 +-
tests/checkasm/sw_ops.c | 776 ++++++++++++++++++++++++++++++
tests/fate/checkasm.mak | 1 +
34 files changed, 8476 insertions(+), 44 deletions(-)
create mode 100644 libswscale/ops.c
create mode 100644 libswscale/ops.h
create mode 100644 libswscale/ops_backend.c
create mode 100644 libswscale/ops_backend.h
create mode 100644 libswscale/ops_chain.c
create mode 100644 libswscale/ops_chain.h
create mode 100644 libswscale/ops_internal.h
create mode 100644 libswscale/ops_memcpy.c
create mode 100644 libswscale/ops_optimizer.c
create mode 100644 libswscale/ops_tmpl_common.c
create mode 100644 libswscale/ops_tmpl_float.c
create mode 100644 libswscale/ops_tmpl_int.c
create mode 100644 libswscale/x86/ops.c
create mode 100644 libswscale/x86/ops_common.asm
create mode 100644 libswscale/x86/ops_float.asm
create mode 100644 libswscale/x86/ops_int.asm
create mode 100644 tests/checkasm/sw_ops.c
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. eb14d45824 avfilter/vf_colordetect: add aarch64 asm
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via eb14d4582447aa6560a47e5d8c6f142d76540766 (commit)
via 6450e01446f5c8c48e4cd2fd43c805ba2991d9ba (commit)
from f07c12d806b9a6f3ca05870a99d10a818a3aabc1 (commit)
- Log -----------------------------------------------------------------
commit eb14d4582447aa6560a47e5d8c6f142d76540766
Author: Zhao Zhili <quinkblack(a)foxmail.com>
AuthorDate: Thu Aug 21 20:44:37 2025 +0800
Commit: Zhao Zhili <quink(a)noreply.code.ffmpeg.org>
CommitDate: Mon Sep 1 15:35:16 2025 +0000
avfilter/vf_colordetect: add aarch64 asm
| rpi5 gcc 12 | m1 clang -fno-vectorize | m1 clang
---------------------------------------------------------------------------
alpha_8_full_c: | 32159.2 ( 1.00x) | 135.8 ( 1.00x) | 26.4 ( 1.00x)
alpha_8_full_neon: | 1266.0 (25.40x) | 8.0 (17.03x) | 8.4 ( 3.15x)
alpha_8_limited_c: | 37561.9 ( 1.00x) | 169.1 ( 1.00x) | 47.7 ( 1.00x)
alpha_8_limited_neon: | 3967.0 ( 9.47x) | 12.5 (13.53x) | 13.3 ( 3.59x)
alpha_16_full_c: | 15867.9 ( 1.00x) | 64.5 ( 1.00x) | 13.7 ( 1.00x)
alpha_16_full_neon: | 1256.9 (12.62x) | 7.9 ( 8.15x) | 8.3 ( 1.64x)
alpha_16_limited_c: | 16723.7 ( 1.00x) | 88.7 ( 1.00x) | 103.3 ( 1.00x)
alpha_16_limited_neon: | 4031.3 ( 4.15x) | 12.5 ( 7.08x) | 13.2 ( 7.86x)
range_8_c: | 21819.7 ( 1.00x) | 120.0 ( 1.00x) | 9.4 ( 1.00x)
range_8_neon: | 1148.3 (19.00x) | 4.3 (27.60x) | 4.8 ( 1.97x)
range_16_c: | 10757.1 ( 1.00x) | 45.7 ( 1.00x) | 7.9 ( 1.00x)
range_16_neon: | 1141.5 ( 9.42x) | 4.4 (10.38x) | 4.6 ( 1.72x)
diff --git a/libavfilter/aarch64/Makefile b/libavfilter/aarch64/Makefile
index b68209bc94..c7b7e18467 100644
--- a/libavfilter/aarch64/Makefile
+++ b/libavfilter/aarch64/Makefile
@@ -1,5 +1,7 @@
OBJS-$(CONFIG_BWDIF_FILTER) += aarch64/vf_bwdif_init_aarch64.o
+OBJS-$(CONFIG_COLORDETECT_FILTER) += aarch64/vf_colordetect_init.o
OBJS-$(CONFIG_NLMEANS_FILTER) += aarch64/vf_nlmeans_init.o
NEON-OBJS-$(CONFIG_BWDIF_FILTER) += aarch64/vf_bwdif_neon.o
+NEON-OBJS-$(CONFIG_COLORDETECT_FILTER) += aarch64/vf_colordetect_neon.o
NEON-OBJS-$(CONFIG_NLMEANS_FILTER) += aarch64/vf_nlmeans_neon.o
diff --git a/libavfilter/aarch64/vf_colordetect_init.c b/libavfilter/aarch64/vf_colordetect_init.c
new file mode 100644
index 0000000000..4db6b90542
--- /dev/null
+++ b/libavfilter/aarch64/vf_colordetect_init.c
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2025 Zhao Zhili <quinkblack(a)foxmail.com>
+ *
+ * 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 "libavutil/aarch64/cpu.h"
+#include "libavfilter/vf_colordetect.h"
+
+int ff_detect_alpha_full_neon(const uint8_t *color, ptrdiff_t color_stride,
+ const uint8_t *alpha, ptrdiff_t alpha_stride,
+ ptrdiff_t width, ptrdiff_t height,
+ int alpha_max, int mpeg_range, int offset);
+
+int ff_detect_alpha16_full_neon(const uint8_t *color, ptrdiff_t color_stride,
+ const uint8_t *alpha, ptrdiff_t alpha_stride,
+ ptrdiff_t width, ptrdiff_t height,
+ int alpha_max, int mpeg_range, int offset);
+
+int ff_detect_alpha_limited_neon(const uint8_t *color, ptrdiff_t color_stride,
+ const uint8_t *alpha, ptrdiff_t alpha_stride,
+ ptrdiff_t width, ptrdiff_t height,
+ int alpha_max, int mpeg_range, int offset);
+
+int ff_detect_alpha16_limited_neon(const uint8_t *color, ptrdiff_t color_stride,
+ const uint8_t *alpha, ptrdiff_t alpha_stride,
+ ptrdiff_t width, ptrdiff_t height,
+ int alpha_max, int mpeg_range, int offset);
+
+int ff_detect_range_neon(const uint8_t *data, ptrdiff_t stride,
+ ptrdiff_t width, ptrdiff_t height,
+ int mpeg_min, int mpeg_max);
+
+int ff_detect_range16_neon(const uint8_t *data, ptrdiff_t stride,
+ ptrdiff_t width, ptrdiff_t height,
+ int mpeg_min, int mpeg_max);
+
+av_cold void ff_color_detect_dsp_init_aarch64(FFColorDetectDSPContext *dsp, int depth,
+ enum AVColorRange color_range)
+{
+ int cpu_flags = av_get_cpu_flags();
+
+ if (have_neon(cpu_flags)) {
+ dsp->detect_range = depth > 8 ? ff_detect_range16_neon : ff_detect_range_neon;
+ if (color_range == AVCOL_RANGE_JPEG)
+ dsp->detect_alpha = depth > 8 ? ff_detect_alpha16_full_neon : ff_detect_alpha_full_neon;
+ else
+ dsp->detect_alpha = depth > 8 ? ff_detect_alpha16_limited_neon : ff_detect_alpha_limited_neon;
+ }
+}
diff --git a/libavfilter/aarch64/vf_colordetect_neon.S b/libavfilter/aarch64/vf_colordetect_neon.S
new file mode 100644
index 0000000000..f3cca16fed
--- /dev/null
+++ b/libavfilter/aarch64/vf_colordetect_neon.S
@@ -0,0 +1,480 @@
+/*
+ * Copyright (c) 2025 Zhao Zhili <quinkblack(a)foxmail.com>
+ *
+ * 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 "libavutil/aarch64/asm.S"
+
+#define FF_ALPHA_TRANSPARENT (1 << 0)
+#define FF_ALPHA_STRAIGHT ((1 << 1) | FF_ALPHA_TRANSPARENT)
+
+const mask
+ .byte 255, 255, 255, 255, 255, 255, 255, 255
+ .byte 255, 255, 255, 255, 255, 255, 255, 255
+mask_start:
+ .byte 0, 0, 0, 0, 0, 0, 0, 0
+ .byte 0, 0, 0, 0, 0, 0, 0, 0
+ .byte 255, 255, 255, 255, 255, 255, 255, 255
+ .byte 255, 255, 255, 255, 255, 255, 255, 255
+endconst
+
+.macro load_mask_zero, shift=0
+ movrel x9, mask_start
+ sub x9, x9, x7, lsl #(\shift)
+ ldr q3, [x9]
+.endm
+
+.macro load_mask, shift=0
+ movrel x9, mask_start
+ sub x9, x9, x7, lsl #(\shift)
+ ld1 {v3.16b, v4.16b}, [x9]
+.endm
+
+/* x0: const uint8_t *data
+ * x1: ptrdiff_t stride
+ * x2: ptrdiff_t width
+ * x3: ptrdiff_t height
+ * w4: int mpeg_min
+ * w5: int mpeg_max
+ */
+function ff_detect_range_neon, export=1
+ ands x7, x2, #15 // width % 16
+ bic x8, x2, #15 // width / 16 * 16
+ bic x6, x2, #31 // width / 32 * 32
+ and x10, x2, #16 // check x8 != x6
+ dup v0.16b, w4 // mpeg_min
+ dup v1.16b, w5 // mpeg_max
+ movi v2.16b, #0 // cond
+ sub x1, x1, x8
+ b.eq 1f
+ load_mask_zero
+1:
+ cbz x6, 20f // width < 32
+ mov x12, x6
+2:
+ ld1 {v5.16b, v6.16b}, [x0], #32
+ cmhi v16.16b, v0.16b, v5.16b
+ cmhi v17.16b, v5.16b, v1.16b
+ cmhi v18.16b, v0.16b, v6.16b
+ cmhi v19.16b, v6.16b, v1.16b
+ orr v20.16b, v16.16b, v17.16b
+ orr v21.16b, v18.16b, v19.16b
+ subs x12, x12, #32
+ orr v20.16b, v20.16b, v21.16b
+ orr v2.16b, v2.16b, v20.16b
+ b.gt 2b
+20:
+ cbz x10, 3f // width < 16
+ ldr q20, [x0], #16
+ cmhi v16.16b, v0.16b, v20.16b
+ cmhi v17.16b, v20.16b, v1.16b
+ orr v16.16b, v16.16b, v17.16b
+ orr v2.16b, v2.16b, v16.16b
+3:
+ cbz x7, 4f
+ ldr q21, [x0]
+ cmhi v18.16b, v0.16b, v21.16b
+ cmhi v19.16b, v21.16b, v1.16b
+ orr v16.16b, v18.16b, v19.16b
+ and v16.16b, v16.16b, v3.16b
+ orr v2.16b, v2.16b, v16.16b
+4:
+ umaxv b4, v2.16b
+ subs x3, x3, #1
+ umov w9, v4.b[0]
+ add x0, x0, x1
+ cbnz w9, 8f
+ b.gt 1b
+ mov x0, #0
+ ret
+8:
+ mov x0, #1
+ ret
+endfunc
+
+/* x0: const uint8_t *data
+ * x1: ptrdiff_t stride
+ * x2: ptrdiff_t width
+ * x3: ptrdiff_t height
+ * w4: int mpeg_min
+ * w5: int mpeg_max
+ */
+function ff_detect_range16_neon, export=1
+ ands x7, x2, #7 // width % 7
+ bic x8, x2, #7 // width / 8 * 8
+ bic x6, x2, #15 // width / 16 * 16
+ and x10, x2, #8 // check x8 != x6
+ dup v0.8h, w4 // mpeg_min
+ dup v1.8h, w5 // mpeg_max
+ movi v2.16b, #0 // cond
+ sub x1, x1, x8, lsl #1
+ b.eq 1f
+ load_mask_zero shift=1
+1:
+ cbz x6, 20f // width < 16
+ mov x12, x6
+2:
+ ld1 {v5.8h, v6.8h}, [x0], #32
+ cmhi v16.8h, v0.8h, v5.8h
+ cmhi v17.8h, v5.8h, v1.8h
+ cmhi v18.8h, v0.8h, v6.8h
+ cmhi v19.8h, v6.8h, v1.8h
+ orr v20.16b, v16.16b, v17.16b
+ orr v21.16b, v18.16b, v19.16b
+ subs x12, x12, #16
+ orr v20.16b, v20.16b, v21.16b
+ orr v2.16b, v2.16b, v20.16b
+ b.gt 2b
+20:
+ cbz x10, 3f // width < 8
+ ldr q20, [x0], #16
+ cmhi v16.8h, v0.8h, v20.8h
+ cmhi v17.8h, v20.8h, v1.8h
+ orr v16.16b, v16.16b, v17.16b
+ orr v2.16b, v2.16b, v16.16b
+3:
+ cbz x7, 4f
+ ldr q21, [x0]
+ cmhi v18.8h, v0.8h, v21.8h
+ cmhi v19.8h, v21.8h, v1.8h
+ orr v16.16b, v18.16b, v19.16b
+ and v16.16b, v16.16b, v3.16b
+ orr v2.16b, v2.16b, v16.16b
+4:
+ umaxv h4, v2.8h
+ subs x3, x3, #1
+ umov w9, v4.h[0]
+ add x0, x0, x1
+ cbnz w9, 8f
+ b.gt 1b
+ mov x0, #0
+ ret
+8:
+ mov x0, #1
+ ret
+endfunc
+
+/*
+ * x0: const uint8_t *color,
+ * x1: ptrdiff_t color_stride,
+ * x2: const uint8_t *alpha,
+ * x3: ptrdiff_t alpha_stride,
+ * x4: ptrdiff_t width,
+ * x5: ptrdiff_t height,
+ * w6: int alpha_max,
+ */
+function ff_detect_alpha_full_neon, export=1
+ ands x7, x4, #15 // width % 16
+ bic x8, x4, #15 // width / 16 * 16
+ movi v0.16b, #0
+ movi v1.16b, #255
+ dup v2.16b, w6 // alpha_max
+ sub x1, x1, x8 // color_stride - aligned_width
+ sub x3, x3, x8 // alpha_stride - aligned_width
+ b.eq 1f
+
+ // Create mask for non-aligned width
+ load_mask
+1:
+ cbz x8, 20f // width < 16
+ mov x12, x8 // w12: aligned_width
+2:
+ ldr q5, [x0], #16
+ ldr q6, [x2], #16
+ subs x12, x12, #16
+ cmhi v7.16b, v5.16b, v6.16b
+ cmeq v16.16b, v6.16b, v2.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+ b.gt 2b
+20:
+ cbz w7, 3f
+ // handle loop tail
+ ldr q5, [x0]
+ ldr q6, [x2]
+ cmhi v7.16b, v5.16b, v6.16b
+ cmeq v16.16b, v6.16b, v2.16b
+ and v7.16b, v7.16b, v3.16b
+ orr v16.16b, v16.16b, v4.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+3:
+ umaxv b17, v0.16b
+ subs x5, x5, #1
+ umov w9, v17.b[0]
+ add x0, x0, x1
+ add x2, x2, x3
+ cbnz w9, 4f
+ b.gt 1b
+
+ uminv b1, v1.16b
+ umov w9, v1.b[0]
+ mov x0, #0
+ cbnz w9, 5f
+ mov x0, #FF_ALPHA_TRANSPARENT
+ ret
+4:
+ mov x0, #FF_ALPHA_STRAIGHT
+5:
+ ret
+endfunc
+
+/*
+ * x0: const uint8_t *color,
+ * x1: ptrdiff_t color_stride,
+ * x2: const uint8_t *alpha,
+ * x3: ptrdiff_t alpha_stride,
+ * x4: ptrdiff_t width,
+ * x5: ptrdiff_t height,
+ * w6: int alpha_max,
+ */
+function ff_detect_alpha16_full_neon, export=1
+ ands x7, x4, #7 // width % 8
+ bic x8, x4, #7 // width / 8 * 8
+ movi v0.8h, #0
+ movi v1.16b, #255
+ dup v2.8h, w6 // alpha_max
+ sub x1, x1, x8, lsl #1 // color_stride - (aligned_width * 2)
+ sub x3, x3, x8, lsl #1 // alpha_stride - (aligned_width * 2)
+ b.eq 1f
+
+ // Create mask for non-aligned width
+ load_mask shift=1
+1:
+ cbz x8, 20f // width < 8
+ mov x12, x8 // w12: aligned_width
+2:
+ ldr q5, [x0], #16
+ ldr q6, [x2], #16
+ subs x12, x12, #8
+ cmhi v7.8h, v5.8h, v6.8h
+ cmeq v16.8h, v6.8h, v2.8h
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+ b.gt 2b
+20:
+ cbz w7, 3f
+ // handle loop tail
+ ldr q5, [x0]
+ ldr q6, [x2]
+ cmhi v7.8h, v5.8h, v6.8h
+ cmeq v16.8h, v6.8h, v2.8h
+ and v7.16b, v7.16b, v3.16b
+ orr v16.16b, v16.16b, v4.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+3:
+ umaxv h17, v0.8h
+ subs x5, x5, #1
+ umov w9, v17.h[0]
+ add x0, x0, x1
+ add x2, x2, x3
+ cbnz w9, 4f
+ b.gt 1b
+
+ uminv h1, v1.8h
+ umov w9, v1.h[0]
+ mov x0, #0
+ cbnz w9, 5f
+ mov x0, #FF_ALPHA_TRANSPARENT
+ ret
+4:
+ mov x0, #FF_ALPHA_STRAIGHT
+5:
+ ret
+endfunc
+
+/*
+ * x0: const uint8_t *color,
+ * x1: ptrdiff_t color_stride,
+ * x2: const uint8_t *alpha,
+ * x3: ptrdiff_t alpha_stride,
+ * x4: ptrdiff_t width,
+ * x5: ptrdiff_t height,
+ * w6: int alpha_max,
+ * w7: int mpeg_range
+ * [sp]: int offset
+ */
+function ff_detect_alpha_limited_neon, export=1
+ dup v17.16b, w7 // mpeg_range
+ ldr w13, [sp]
+ movi v0.16b, #0
+ movi v1.16b, #255
+ dup v2.16b, w6 // alpha_max
+ ands x7, x4, #15 // width % 16
+ bic x8, x4, #15 // width / 16 * 16
+ dup v18.8h, w13 // offset
+ sub x1, x1, x8 // color_stride - aligned_width
+ sub x3, x3, x8 // alpha_stride - aligned_width
+ b.eq 1f
+
+ // Create mask for non-aligned width
+ load_mask
+1:
+ cbz x8, 20f // width < 16
+ mov x12, x8 // w12: aligned_width
+2:
+ ldr q5, [x0], #16 // color
+ ldr q6, [x2], #16 // alpha
+ umull v19.8h, v2.8b, v5.8b // alpha_max * color
+ umull2 v20.8h, v2.16b, v5.16b // alpha_max * color
+ umull v21.8h, v17.8b, v6.8b // range * alpha
+ umull2 v22.8h, v17.16b, v6.16b // range * alpha
+ cmeq v16.16b, v6.16b, v2.16b
+ subs x12, x12, #16
+ uqsub v19.8h, v19.8h, v18.8h // alpha_max * color - offset
+ uqsub v20.8h, v20.8h, v18.8h // alpha_max * color - offset
+
+ cmhi v19.8h, v19.8h, v21.8h
+ cmhi v20.8h, v20.8h, v22.8h
+ orr v7.16b, v19.16b, v20.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+ b.gt 2b
+20:
+ cbz w7, 3f
+ // handle loop tail
+ ldr q5, [x0]
+ ldr q6, [x2]
+ umull v19.8h, v2.8b, v5.8b // alpha_max * color
+ umull2 v20.8h, v2.16b, v5.16b // alpha_max * color
+ umull v21.8h, v17.8b, v6.8b // range * alpha
+ umull2 v22.8h, v17.16b, v6.16b // range * alpha
+ uqsub v19.8h, v19.8h, v18.8h // alpha_max * color - offset
+ uqsub v20.8h, v20.8h, v18.8h // alpha_max * color - offset
+
+ cmhi v19.8h, v19.8h, v21.8h
+ cmhi v20.8h, v20.8h, v22.8h
+ uqxtn v7.8b, v19.8h
+ uqxtn2 v7.16b, v20.8h
+ cmeq v16.16b, v6.16b, v2.16b
+
+ and v7.16b, v7.16b, v3.16b
+ orr v16.16b, v16.16b, v4.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+3:
+ umaxv b23, v0.16b
+ subs x5, x5, #1
+ umov w9, v23.b[0]
+ add x0, x0, x1
+ add x2, x2, x3
+ cbnz w9, 4f
+ b.gt 1b
+
+ uminv b1, v1.16b
+ umov w9, v1.b[0]
+ mov x0, #0
+ cbnz w9, 5f
+ mov x0, #FF_ALPHA_TRANSPARENT
+ ret
+4:
+ mov x0, #FF_ALPHA_STRAIGHT
+5:
+ ret
+endfunc
+
+/*
+ * x0: const uint8_t *color,
+ * x1: ptrdiff_t color_stride,
+ * x2: const uint8_t *alpha,
+ * x3: ptrdiff_t alpha_stride,
+ * x4: ptrdiff_t width,
+ * x5: ptrdiff_t height,
+ * w6: int alpha_max,
+ * w7: int mpeg_range
+ * [sp]: int offset
+ */
+function ff_detect_alpha16_limited_neon, export=1
+ dup v17.8h, w7 // mpeg_range
+ ldr w13, [sp]
+ movi v0.8h, #0
+ movi v1.16b, #255
+ dup v2.8h, w6 // alpha_max
+ ands x7, x4, #7 // width % 8
+ bic x8, x4, #7 // width / 8 * 8
+ dup v18.4s, w13 // offset
+ sub x1, x1, x8, lsl #1 // color_stride - (aligned_width * 2)
+ sub x3, x3, x8, lsl #1 // alpha_stride - (aligned_width * 2)
+ b.eq 1f
+
+ // Create mask for non-aligned width
+ load_mask shift=1
+1:
+ cbz x8, 20f // width < 8
+ mov x12, x8 // w12: aligned_width
+2:
+ ldr q5, [x0], #16
+ ldr q6, [x2], #16
+ umull v19.4s, v2.4h, v5.4h // alpha_max * color
+ umull2 v20.4s, v2.8h, v5.8h // alpha_max * color
+ umull v21.4s, v17.4h, v6.4h // range * alpha
+ umull2 v22.4s, v17.8h, v6.8h // range * alpha
+ cmeq v16.8h, v6.8h, v2.8h
+ subs x12, x12, #8
+ uqsub v19.4s, v19.4s, v18.4s // alpha_max * color - offset
+ uqsub v20.4s, v20.4s, v18.4s // alpha_max * color - offset
+
+ cmhi v19.4s, v19.4s, v21.4s
+ cmhi v20.4s, v20.4s, v22.4s
+ orr v7.16b, v19.16b, v20.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+ b.gt 2b
+20:
+ cbz w7, 3f
+ // handle loop tail
+ ldr q5, [x0]
+ ldr q6, [x2]
+ umull v19.4s, v2.4h, v5.4h // alpha_max * color
+ umull2 v20.4s, v2.8h, v5.8h // alpha_max * color
+ umull v21.4s, v17.4h, v6.4h // range * alpha
+ umull2 v22.4s, v17.8h, v6.8h // range * alpha
+ uqsub v19.4s, v19.4s, v18.4s // alpha_max * color - offset
+ uqsub v20.4s, v20.4s, v18.4s // alpha_max * color - offset
+
+ cmhi v19.4s, v19.4s, v21.4s
+ cmhi v20.4s, v20.4s, v22.4s
+ uqxtn v7.4h, v19.4s
+ uqxtn2 v7.8h, v20.4s
+ cmeq v16.8h, v6.8h, v2.8h
+
+ and v7.16b, v7.16b, v3.16b
+ orr v16.16b, v16.16b, v4.16b
+ orr v0.16b, v0.16b, v7.16b
+ and v1.16b, v1.16b, v16.16b
+3:
+ umaxv s23, v0.4s
+ subs x5, x5, #1
+ umov w9, v23.s[0]
+ add x0, x0, x1
+ add x2, x2, x3
+ cbnz w9, 4f
+ b.gt 1b
+
+ uminv h1, v1.8h
+ umov w9, v1.h[0]
+ mov x0, #0
+ cbnz w9, 5f
+ mov x0, #FF_ALPHA_TRANSPARENT
+ ret
+4:
+ mov x0, #FF_ALPHA_STRAIGHT
+5:
+ ret
+endfunc
diff --git a/libavfilter/vf_colordetect.c b/libavfilter/vf_colordetect.c
index 88374ac3e2..ef7fb25130 100644
--- a/libavfilter/vf_colordetect.c
+++ b/libavfilter/vf_colordetect.c
@@ -236,7 +236,9 @@ av_cold void ff_color_detect_dsp_init(FFColorDetectDSPContext *dsp, int depth,
dsp->detect_alpha = depth > 8 ? ff_detect_alpha16_limited_c : ff_detect_alpha_limited_c;
}
-#if ARCH_X86
+#if ARCH_AARCH64
+ ff_color_detect_dsp_init_aarch64(dsp, depth, color_range);
+#elif ARCH_X86
ff_color_detect_dsp_init_x86(dsp, depth, color_range);
#endif
}
diff --git a/libavfilter/vf_colordetect.h b/libavfilter/vf_colordetect.h
index 0c0dc889dc..95f30a6ac2 100644
--- a/libavfilter/vf_colordetect.h
+++ b/libavfilter/vf_colordetect.h
@@ -50,6 +50,8 @@ typedef struct FFColorDetectDSPContext {
void ff_color_detect_dsp_init(FFColorDetectDSPContext *dsp, int depth,
enum AVColorRange color_range);
+void ff_color_detect_dsp_init_aarch64(FFColorDetectDSPContext *dsp, int depth,
+ enum AVColorRange color_range);
void ff_color_detect_dsp_init_x86(FFColorDetectDSPContext *dsp, int depth,
enum AVColorRange color_range);
commit 6450e01446f5c8c48e4cd2fd43c805ba2991d9ba
Author: Zhao Zhili <quinkblack(a)foxmail.com>
AuthorDate: Thu Aug 21 16:40:09 2025 +0800
Commit: Zhao Zhili <quink(a)noreply.code.ffmpeg.org>
CommitDate: Mon Sep 1 15:35:16 2025 +0000
checkasm/vf_colordetect: test non-aligned width
diff --git a/tests/checkasm/vf_colordetect.c b/tests/checkasm/vf_colordetect.c
index 9a16de7392..18472e9b66 100644
--- a/tests/checkasm/vf_colordetect.c
+++ b/tests/checkasm/vf_colordetect.c
@@ -22,9 +22,9 @@
#include "libavfilter/vf_colordetect.h"
#include "libavutil/mem_internal.h"
-#define WIDTH 256
+#define WIDTH 540
#define HEIGHT 16
-#define STRIDE (WIDTH + 32)
+#define STRIDE FFALIGN(WIDTH, 32)
static void check_range_detect(int depth)
{
-----------------------------------------------------------------------
Summary of changes:
libavfilter/aarch64/Makefile | 2 +
libavfilter/aarch64/vf_colordetect_init.c | 64 ++++
libavfilter/aarch64/vf_colordetect_neon.S | 480 ++++++++++++++++++++++++++++++
libavfilter/vf_colordetect.c | 4 +-
libavfilter/vf_colordetect.h | 2 +
tests/checkasm/vf_colordetect.c | 4 +-
6 files changed, 553 insertions(+), 3 deletions(-)
create mode 100644 libavfilter/aarch64/vf_colordetect_init.c
create mode 100644 libavfilter/aarch64/vf_colordetect_neon.S
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. f07c12d806 avfilter/x86/vf_colordetect: fix alpha detect tail handling
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via f07c12d806b9a6f3ca05870a99d10a818a3aabc1 (commit)
from 9dc79241d9680fc050376bb0cc37875a41d00cc9 (commit)
- Log -----------------------------------------------------------------
commit f07c12d806b9a6f3ca05870a99d10a818a3aabc1
Author: Niklas Haas <git(a)haasn.dev>
AuthorDate: Mon Sep 1 16:39:00 2025 +0200
Commit: Niklas Haas <ffmpeg(a)haasn.dev>
CommitDate: Mon Sep 1 15:33:43 2025 +0000
avfilter/x86/vf_colordetect: fix alpha detect tail handling
This wrapping logic still considered any nonzero return from the ASM function
to be the overall result, but this is not true since the addition of
FF_ALPHA_TRANSPARENT.
Fix it by only early returning if FF_ALPHA_STRAIGHT is detected.
Fixes: 9b8b78a8153f9ca94b7eb9cba87442954787912a
See-Also: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/20301#issuecomment-4802
diff --git a/libavfilter/x86/vf_colordetect_init.c b/libavfilter/x86/vf_colordetect_init.c
index 828f01937d..72fa021bf2 100644
--- a/libavfilter/x86/vf_colordetect_init.c
+++ b/libavfilter/x86/vf_colordetect_init.c
@@ -31,11 +31,11 @@ static int FUNC_NAME(const uint8_t *src, ptrdiff_t stride,
{ \
ptrdiff_t bytes = (width << SHIFT) & ~(MMSIZE - 1); \
int ret = ASM_FUNC_NAME(src, stride, bytes, height, min, max); \
- if (ret) \
+ if (ret == FF_ALPHA_STRAIGHT) \
return ret; \
\
- return C_FUNC_NAME(src + bytes, stride, width - (bytes >> SHIFT), \
- height, min, max); \
+ return ret | C_FUNC_NAME(src + bytes, stride, width - (bytes >> SHIFT), \
+ height, min, max); \
}
#define DETECT_ALPHA_FUNC(FUNC_NAME, ASM_FUNC_NAME, C_FUNC_NAME, SHIFT, MMSIZE) \
@@ -50,11 +50,12 @@ static int FUNC_NAME(const uint8_t *color, ptrdiff_t color_stride,
ptrdiff_t bytes = (width << SHIFT) & ~(MMSIZE - 1); \
int ret = ASM_FUNC_NAME(color, color_stride, alpha, alpha_stride, \
bytes, height, p, q, k); \
- if (ret) \
+ if (ret == FF_ALPHA_STRAIGHT) \
return ret; \
\
- return C_FUNC_NAME(color + bytes, color_stride, alpha + bytes, alpha_stride,\
- width - (bytes >> SHIFT), height, p, q, k); \
+ return ret | C_FUNC_NAME(color + bytes, color_stride, alpha + bytes, \
+ alpha_stride, width - (bytes >> SHIFT), height, \
+ p, q, k); \
}
#if HAVE_X86ASM
-----------------------------------------------------------------------
Summary of changes:
libavfilter/x86/vf_colordetect_init.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. 9dc79241d9 avcodec/decode: always extract display matrix from Exif in frames
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via 9dc79241d9680fc050376bb0cc37875a41d00cc9 (commit)
via 6a15183e6a04a11953e4d7a88d8960ef4e7a29e8 (commit)
via fa1938c5747fa3a91596e467ba0af624f367c5a9 (commit)
via 75bd5056a44119d98a8b4876c8d46065fbb81467 (commit)
via 310be1c5a402cc77d58a4c1b21af2654a87f6228 (commit)
via 74e430202d933284fc38b591d4b3a12464e8aec6 (commit)
via 6a115e20664329b5bff92fab781903e55e8d4acd (commit)
via 11d9d7b8f31f7fac704359576bb13faac17be42b (commit)
via e62f9a071b5c912c5a8cabea5e89943aee5510d7 (commit)
via 4ffd621523e89a90d94bc2fbeeacdd59a65d66a1 (commit)
from 45dcfb2d6039c2f40f1d54d01219526cad8633d4 (commit)
- Log -----------------------------------------------------------------
commit 9dc79241d9680fc050376bb0cc37875a41d00cc9
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Thu Aug 21 23:39:58 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:08:04 2025 -0300
avcodec/decode: always extract display matrix from Exif in frames
Should help ensure no conflicting values are propagated.
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 15ace44d78..edd2390490 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -2365,8 +2365,8 @@ static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMe
}
}
- if (orient && orient->value.uint[0] > 1) {
- av_log(avctx, AV_LOG_DEBUG, "found nontrivial EXIF orientation: %" PRIu64 "\n", orient->value.uint[0]);
+ if (orient) {
+ av_log(avctx, AV_LOG_DEBUG, "found EXIF orientation: %" PRIu64 "\n", orient->value.uint[0]);
ret = attach_displaymatrix(avctx, frame, orient->value.uint[0]);
if (ret < 0) {
av_log(avctx, AV_LOG_WARNING, "unable to attach displaymatrix from EXIF\n");
commit 6a15183e6a04a11953e4d7a88d8960ef4e7a29e8
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Thu Aug 21 21:44:27 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:59 2025 -0300
avcodec/decode: parse Exif packet side data before passing it to frames
Extract Orientation and export it as a display matrix if present, and set the
frame's metadata with the remaining Exif entries.
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index fa76dba51c..15ace44d78 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -1389,6 +1389,78 @@ static int side_data_stereo3d_merge(AVFrameSideData *sd_frame,
return 0;
}
+static int side_data_exif_parse(AVFrame *dst, const AVPacketSideData *sd_pkt)
+{
+ AVExifMetadata ifd = { 0 };
+ AVExifEntry *entry = NULL;
+ AVBufferRef *buf = NULL;
+ AVFrameSideData *sd_frame;
+ int ret;
+
+ ret = av_exif_parse_buffer(NULL, sd_pkt->data, sd_pkt->size, &ifd,
+ AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
+ return ret;
+
+ ret = av_exif_get_entry(NULL, &ifd, av_exif_get_tag_id("Orientation"), 0, &entry);
+ if (ret < 0)
+ goto end;
+
+ if (!entry) {
+ ret = av_exif_ifd_to_dict(NULL, &ifd, &dst->metadata);
+ if (ret < 0)
+ goto end;
+
+ sd_frame = av_frame_side_data_new(&dst->side_data, &dst->nb_side_data, AV_FRAME_DATA_EXIF,
+ sd_pkt->size, 0);
+ if (sd_frame)
+ memcpy(sd_frame->data, sd_pkt->data, sd_pkt->size);
+ ret = sd_frame ? 0 : AVERROR(ENOMEM);
+
+ goto end;
+ } else if (entry->count <= 0 || entry->type != AV_TIFF_SHORT) {
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ }
+
+ // If a display matrix already exists in the frame, give it priority
+ if (av_frame_side_data_get(dst->side_data, dst->nb_side_data, AV_FRAME_DATA_DISPLAYMATRIX))
+ goto finish;
+
+ sd_frame = av_frame_side_data_new(&dst->side_data, &dst->nb_side_data, AV_FRAME_DATA_DISPLAYMATRIX,
+ sizeof(int32_t) * 9, 0);
+ if (!sd_frame) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ ret = av_exif_orientation_to_matrix((int32_t *)sd_frame->data, entry->value.uint[0]);
+ if (ret < 0)
+ goto end;
+
+finish:
+ av_exif_remove_entry(NULL, &ifd, entry->id, 0);
+
+ ret = av_exif_ifd_to_dict(NULL, &ifd, &dst->metadata);
+ if (ret < 0)
+ goto end;
+
+ ret = av_exif_write(NULL, &ifd, &buf, AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
+ goto end;
+
+ if (!av_frame_side_data_add(&dst->side_data, &dst->nb_side_data, AV_FRAME_DATA_EXIF, &buf, 0)) {
+ ret = AVERROR(ENOMEM);
+ goto end;
+ }
+
+ ret = 0;
+end:
+ av_buffer_unref(&buf);
+ av_exif_free(&ifd);
+ return ret;
+}
+
static int side_data_map(AVFrame *dst,
const AVPacketSideData *sd_src, int nb_sd_src,
const SideDataMap *map)
@@ -1415,11 +1487,21 @@ static int side_data_map(AVFrame *dst,
continue;
}
- sd_frame = av_frame_new_side_data(dst, type_frame, sd_pkt->size);
- if (!sd_frame)
- return AVERROR(ENOMEM);
+ switch (type_pkt) {
+ case AV_PKT_DATA_EXIF: {
+ int ret = side_data_exif_parse(dst, sd_pkt);
+ if (ret < 0)
+ return ret;
+ break;
+ }
+ default:
+ sd_frame = av_frame_new_side_data(dst, type_frame, sd_pkt->size);
+ if (!sd_frame)
+ return AVERROR(ENOMEM);
- memcpy(sd_frame->data, sd_pkt->data, sd_pkt->size);
+ memcpy(sd_frame->data, sd_pkt->data, sd_pkt->size);
+ break;
+ }
}
return 0;
diff --git a/libavcodec/version.h b/libavcodec/version.h
index 7531c6c42a..c20072197d 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -30,7 +30,7 @@
#include "version_major.h"
#define LIBAVCODEC_VERSION_MINOR 14
-#define LIBAVCODEC_VERSION_MICRO 100
+#define LIBAVCODEC_VERSION_MICRO 101
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
LIBAVCODEC_VERSION_MINOR, \
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index c95ed42621..c43f260dd3 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -188,7 +188,7 @@ fate-mov-heic-demux-clap-irot-imir: CMD = stream_demux mov $(TARGET_SAMPLES)/hei
FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC MJPEG, HEVC_PARSER) \
+= fate-mov-heic-demux-still-image-multiple-thumb
fate-mov-heic-demux-still-image-multiple-thumb: CMD = stream_demux mov $(TARGET_SAMPLES)/heif/P1001091.HIF "" "-c:v copy -map 0" \
- "-show_entries stream=index,id:stream_disposition:stream_side_data_list"
+ "-show_entries stream=index,id:stream_disposition:stream_side_data_list:frame=stream_index:frame_side_data_list:frame_tags"
# heic demuxing - still image with multiple items in a grid.
FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER) \
diff --git a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
index e5764f6de8..4e66fe978c 100644
--- a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
+++ b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
@@ -17,6 +17,1892 @@
0, 0, 0, 1, 272222, 0x562364b2
1, 0, 0, 1, 5448, 0x0a517e78
2, 0, 0, 1, 858544, 0xca5a3f9a
+[FRAME]
+stream_index=1
+[/FRAME]
+[FRAME]
+stream_index=2
+[/FRAME]
+[FRAME]
+stream_index=0
+TAG:Compression= 51176
+TAG:Make=Panasonic
+TAG:Model=DC-S1M2
+TAG:StripOffsets= 985600
+TAG:ResolutionUnit= 2
+TAG:DateTime=2025:07:19 03:21:55
+TAG:Artist=
+TAG:Copyright=
+TAG:ExifIFD/ExposureTime= 10:50
+TAG:ExifIFD/FNumber= 28:10
+TAG:ExifIFD/ExposureProgram= 1
+TAG:ExifIFD/ISOSpeedRatings= 400
+TAG:ExifIFD/0x8830= 1
+TAG:ExifIFD/0x8831= 400
+TAG:ExifIFD/ExifVersion= 48, 51, 48, 48
+TAG:ExifIFD/DateTimeOriginal=2025:07:19 03:21:55
+TAG:ExifIFD/DateTimeDigitized=2025:07:19 03:21:55
+TAG:ExifIFD/0x9010=+09:00
+TAG:ExifIFD/0x9011=+09:00
+TAG:ExifIFD/0x9012=+09:00
+TAG:ExifIFD/ComponentsConfiguration= 1, 2, 3, 0
+TAG:ExifIFD/CompressedBitsPerPixel= 2:1
+TAG:ExifIFD/ExposureBiasValue= 0:100
+TAG:ExifIFD/MaxApertureValue= 768:256
+TAG:ExifIFD/MeteringMode= 255
+TAG:ExifIFD/LightSource= 0
+TAG:ExifIFD/Flash= 16
+TAG:ExifIFD/FocalLength= 600:10
+TAG:ExifIFD/MakerNote/GPSLatitudeRef= 7
+TAG:ExifIFD/MakerNote/GPSLatitude= 0, 1, 1, 0
+TAG:ExifIFD/MakerNote/GPSLongitudeRef= 1
+TAG:ExifIFD/MakerNote/GPSTimeStamp= 7
+TAG:ExifIFD/MakerNote/GPSTrack= 16, 0
+TAG:ExifIFD/MakerNote/GPSDestDistance= 6
+TAG:ExifIFD/MakerNote/GPSAreaInformation= 2
+TAG:ExifIFD/MakerNote/0x001F= 11
+TAG:ExifIFD/MakerNote/0x0020= 2
+TAG:ExifIFD/MakerNote/0x0021= 68, 86, 1, 2, 69, 80, 0, 0, 240, 255, 68, 66, 26, 18, 240, 255
+ 65, 70, 226, 2, 160, 15, 8, 128, 162, 15, 0, 129, 164, 15, 0, 5
+166, 15, 2, 0, 202, 15, 2, 2, 204, 15, 126, 33, 230, 15, 16, 0
+232, 15, 33, 0, 234, 15, 23, 1, 186, 15, 229, 48, 254, 15, 107, 22
+190, 15, 253, 255, 250, 15, 0, 0, 248, 15, 0, 0, 226, 16, 0, 0
+220, 15, 0, 0, 226, 15, 1, 0, 224, 15, 190, 46, 136, 16, 186, 46
+178, 15, 0, 64, 180, 15, 0, 0, 252, 15, 0, 0, 158, 15, 0, 0
+168, 15, 0, 0, 170, 15, 166, 46, 198, 15, 163, 46, 200, 15, 253, 53
+216, 15, 201, 1, 236, 15, 184, 46, 182, 15, 8, 1, 184, 15, 99, 0
+152, 15, 32, 1, 154, 15, 8, 0, 222, 15, 95, 0, 206, 15, 1, 0
+210, 15, 224, 255, 208, 15, 0, 0, 172, 15, 2, 0, 176, 15, 70, 0
+174, 15, 101, 0, 212, 15, 210, 0, 244, 15, 56, 0, 242, 15, 88, 0
+ 96, 15, 255, 255, 98, 15, 255, 183, 100, 15, 1, 0, 102, 15, 0, 0
+104, 15, 0, 0, 106, 15, 0, 0, 108, 15, 255, 255, 110, 15, 255, 255
+112, 15, 1, 0, 114, 15, 0, 0, 116, 15, 0, 0, 118, 15, 0, 0
+120, 15, 2, 0, 122, 15, 0, 0, 124, 15, 0, 0, 126, 15, 0, 0
+128, 15, 0, 0, 130, 15, 0, 0, 132, 15, 0, 0, 134, 15, 0, 0
+148, 16, 0, 0, 150, 16, 0, 0, 152, 16, 0, 0, 154, 16, 0, 0
+156, 16, 0, 0, 158, 16, 0, 0, 136, 15, 0, 0, 138, 15, 0, 0
+140, 15, 0, 0, 142, 15, 0, 0, 144, 15, 160, 0, 146, 15, 120, 0
+148, 15, 52, 0, 150, 15, 52, 0, 204, 16, 6, 0, 206, 16, 0, 0
+108, 12, 0, 0, 110, 12, 0, 0, 112, 12, 0, 0, 114, 12, 0, 0
+116, 12, 0, 0, 118, 12, 0, 0, 246, 15, 0, 0, 238, 15, 88, 2
+240, 15, 88, 2, 34, 15, 120, 40, 36, 15, 221, 44, 38, 15, 185, 142
+ 40, 15, 218, 148, 214, 15, 0, 0, 56, 15, 75, 2, 48, 15, 232, 6
+ 50, 15, 27, 3, 52, 15, 0, 2, 44, 15, 173, 6, 54, 15, 236, 2
+120, 16, 55, 0, 36, 16, 163, 46, 38, 16, 189, 71, 40, 16, 5, 6
+ 44, 16, 255, 15, 46, 16, 0, 0, 42, 16, 165, 46, 62, 15, 210, 255
+ 60, 15, 210, 255, 56, 16, 211, 255, 144, 16, 0, 0, 146, 16, 66, 0
+ 60, 16, 28, 37, 58, 16, 162, 55, 166, 16, 0, 28, 168, 16, 0, 36
+170, 16, 0, 20, 172, 16, 98, 47, 174, 16, 112, 45, 176, 16, 85, 49
+192, 16, 0, 0, 194, 16, 255, 255, 196, 16, 0, 0, 198, 16, 172, 46
+200, 16, 172, 46, 202, 16, 172, 46, 178, 16, 172, 46, 160, 16, 1, 0
+162, 16, 128, 0, 163, 16, 128, 0, 164, 16, 0, 0, 165, 16, 0, 0
+180, 16, 255, 255, 182, 16, 206, 45, 184, 16, 3, 47, 186, 16, 104, 46
+188, 16, 181, 2, 190, 16, 6, 8, 208, 16, 0, 0, 96, 16, 0, 0
+ 98, 16, 0, 0, 100, 16, 0, 0, 102, 16, 0, 0, 112, 16, 0, 64
+122, 16, 0, 2, 140, 16, 0, 0, 114, 16, 0, 0, 124, 16, 0, 0
+116, 16, 0, 0, 118, 16, 0, 0, 104, 16, 0, 0, 106, 16, 0, 0
+108, 16, 232, 3, 110, 16, 1, 0, 128, 16, 247, 0, 130, 16, 0, 0
+132, 16, 205, 46, 134, 16, 0, 0, 138, 16, 0, 0, 126, 16, 1, 0
+142, 16, 0, 0, 58, 15, 0, 0, 36, 12, 0, 0, 38, 12, 0, 0
+ 40, 12, 0, 0, 42, 12, 0, 0, 188, 12, 0, 2, 190, 12, 2, 2
+184, 12, 166, 0, 186, 12, 249, 0, 210, 16, 0, 0, 211, 16, 0, 0
+212, 16, 0, 0, 213, 16, 0, 0, 214, 16, 0, 0, 215, 16, 0, 0
+ 55, 16, 2, 0, 54, 16, 0, 0, 216, 16, 0, 0, 218, 16, 0, 0
+ 56, 17, 0, 0, 58, 17, 0, 0, 48, 16, 0, 0, 50, 16, 106, 0
+240, 255, 65, 69, 42, 2, 104, 6, 0, 3, 106, 6, 0, 9, 108, 6
+ 27, 3, 110, 6, 8, 9, 114, 6, 0, 0, 2, 6, 126, 0, 14, 6
+ 77, 0, 8, 6, 174, 0, 0, 6, 174, 0, 10, 6, 195, 0, 12, 6
+195, 0, 4, 6, 37, 0, 16, 6, 0, 0, 6, 6, 77, 0, 164, 6
+ 0, 0, 50, 6, 10, 4, 20, 6, 129, 4, 28, 6, 129, 4, 22, 6
+ 10, 4, 18, 6, 0, 4, 26, 6, 44, 5, 30, 6, 0, 0, 42, 6
+236, 2, 56, 6, 215, 4, 52, 6, 38, 12, 54, 6, 0, 0, 62, 6
+ 0, 0, 46, 6, 0, 0, 32, 6, 27, 3, 34, 6, 232, 6, 48, 6
+234, 2, 58, 6, 3, 2, 38, 6, 0, 1, 212, 6, 27, 3, 214, 6
+232, 5, 216, 6, 130, 4, 128, 6, 0, 0, 130, 6, 0, 0, 134, 6
+ 0, 0, 160, 6, 0, 0, 192, 6, 0, 0, 64, 7, 0, 0, 80, 7
+ 0, 0, 224, 21, 0, 0, 190, 6, 0, 0, 62, 7, 0, 0, 132, 6
+ 0, 0, 194, 6, 0, 0, 138, 6, 0, 0, 144, 6, 0, 0, 162, 6
+ 0, 0, 72, 7, 0, 0, 82, 7, 0, 0, 84, 7, 0, 0, 186, 6
+ 0, 0, 136, 6, 0, 0, 140, 6, 0, 0, 142, 6, 0, 0, 146, 6
+ 0, 0, 154, 6, 0, 0, 156, 6, 0, 0, 166, 6, 0, 0, 172, 6
+ 0, 0, 174, 6, 0, 0, 180, 6, 0, 0, 182, 6, 0, 0, 168, 6
+ 0, 0, 220, 7, 0, 0, 214, 7, 0, 0, 152, 6, 0, 0, 158, 6
+ 0, 0, 54, 7, 0, 0, 32, 7, 0, 0, 34, 7, 0, 0, 36, 7
+ 0, 0, 38, 7, 0, 0, 42, 7, 0, 0, 46, 7, 0, 0, 40, 7
+ 0, 0, 44, 7, 0, 0, 48, 7, 0, 0, 24, 7, 0, 0, 26, 7
+ 0, 0, 58, 7, 0, 0, 60, 7, 0, 0, 52, 7, 0, 0, 70, 7
+ 0, 0, 86, 7, 0, 0, 90, 7, 0, 0, 88, 7, 0, 0, 68, 7
+ 0, 0, 66, 7, 0, 0, 92, 7, 0, 0, 94, 7, 0, 0, 226, 21
+ 0, 0, 228, 21, 0, 0, 230, 21, 0, 0, 232, 21, 0, 0, 234, 21
+ 0, 0, 236, 21, 0, 0, 68, 6, 42, 0, 124, 6, 64, 2, 126, 6
+ 0, 0, 74, 6, 42, 0, 72, 6, 0, 0, 88, 6, 12, 0, 64, 6
+ 0, 3, 66, 6, 86, 2, 120, 6, 27, 3, 122, 6, 59, 2, 22, 7
+ 0, 0, 92, 6, 41, 51, 100, 6, 0, 0, 4, 7, 0, 0, 184, 6
+ 0, 0, 102, 6, 1, 0, 76, 6, 0, 0, 82, 6, 14, 1, 84, 6
+ 0, 0, 218, 6, 59, 2, 220, 6, 42, 0, 222, 6, 41, 51, 90, 6
+ 0, 0, 12, 7, 0, 2, 14, 7, 255, 127, 6, 7, 255, 127, 112, 6
+ 0, 7, 10, 7, 33, 16, 20, 7, 33, 16, 238, 21, 0, 0, 224, 6
+138, 252, 18, 7, 50, 1, 164, 1, 50, 1, 0, 7, 0, 32, 8, 7
+ 0, 0, 16, 7, 4, 0, 2, 7, 12, 0, 240, 255, 87, 66, 118, 2
+ 22, 8, 11, 8, 26, 8, 70, 8, 24, 8, 0, 4, 28, 8, 84, 8
+ 30, 8, 120, 7, 84, 8, 22, 17, 224, 8, 12, 254, 78, 8, 83, 8
+ 80, 8, 113, 7, 86, 8, 249, 16, 226, 8, 253, 253, 102, 8, 65, 2
+124, 8, 44, 5, 54, 8, 17, 0, 52, 8, 1, 20, 2, 8, 0, 0
+ 0, 8, 0, 0, 168, 8, 0, 4, 170, 8, 0, 4, 12, 8, 148, 17
+ 14, 8, 41, 0, 56, 8, 0, 4, 58, 8, 0, 4, 202, 9, 0, 0
+204, 9, 0, 0, 206, 9, 0, 0, 208, 9, 0, 0, 210, 9, 0, 0
+176, 8, 0, 0, 178, 8, 0, 0, 230, 8, 0, 0, 232, 8, 0, 0
+234, 8, 0, 0, 236, 8, 0, 0, 64, 8, 36, 8, 66, 8, 120, 7
+ 68, 8, 10, 10, 70, 8, 5, 9, 72, 8, 84, 8, 74, 8, 120, 7
+112, 8, 36, 8, 114, 8, 120, 7, 120, 8, 0, 0, 122, 8, 0, 0
+116, 8, 0, 0, 118, 8, 0, 0, 32, 8, 84, 8, 34, 8, 120, 7
+172, 8, 73, 8, 174, 8, 108, 7, 36, 8, 84, 8, 38, 8, 120, 7
+ 40, 8, 3, 1, 42, 8, 84, 8, 44, 8, 118, 7, 46, 8, 2, 0
+ 48, 8, 70, 8, 50, 8, 17, 8, 4, 8, 0, 0, 6, 8, 36, 8
+ 8, 8, 34, 8, 218, 8, 0, 0, 220, 8, 36, 8, 222, 8, 34, 8
+126, 8, 0, 0, 88, 8, 36, 8, 90, 8, 34, 8, 250, 9, 0, 0
+214, 8, 36, 8, 216, 8, 34, 8, 212, 9, 4, 12, 214, 9, 189, 9
+216, 9, 4, 12, 218, 9, 189, 9, 220, 9, 0, 0, 104, 8, 147, 9
+106, 8, 143, 5, 108, 8, 51, 10, 110, 8, 196, 4, 62, 8, 77, 3
+ 96, 8, 61, 1, 98, 8, 5, 1, 100, 8, 14, 0, 82, 8, 0, 4
+180, 8, 0, 1, 182, 8, 0, 1, 76, 8, 0, 1, 184, 8, 0, 0
+ 60, 8, 0, 4, 228, 8, 0, 0, 238, 8, 0, 0, 240, 8, 249, 0
+242, 8, 0, 0, 244, 8, 0, 0, 246, 8, 210, 1, 248, 8, 39, 2
+250, 8, 0, 0, 252, 8, 61, 0, 192, 9, 19, 17, 194, 9, 13, 254
+196, 9, 13, 254, 198, 9, 0, 0, 200, 9, 0, 0, 186, 8, 0, 0
+188, 8, 17, 0, 190, 8, 9, 0, 92, 8, 0, 0, 160, 8, 194, 5
+162, 8, 240, 9, 164, 8, 239, 7, 166, 8, 155, 6, 152, 8, 194, 5
+154, 8, 240, 9, 156, 8, 239, 7, 158, 8, 155, 6, 144, 8, 198, 5
+146, 8, 9, 10, 148, 8, 250, 7, 150, 8, 169, 6, 128, 8, 0, 0
+130, 8, 0, 0, 132, 8, 0, 0, 134, 8, 0, 0, 192, 8, 0, 0
+194, 8, 0, 0, 196, 8, 0, 0, 198, 8, 0, 0, 136, 8, 0, 0
+138, 8, 0, 0, 140, 8, 0, 0, 142, 8, 0, 0, 200, 8, 0, 0
+202, 8, 0, 0, 204, 8, 0, 0, 206, 8, 0, 0, 180, 11, 0, 0
+182, 11, 0, 0, 184, 11, 0, 0, 186, 11, 0, 0, 188, 11, 0, 0
+190, 11, 0, 0, 230, 10, 0, 0, 232, 10, 0, 0, 234, 10, 0, 0
+236, 10, 0, 0, 238, 10, 0, 0, 178, 11, 0, 0, 240, 10, 255, 7
+242, 10, 255, 7, 244, 10, 255, 7, 246, 10, 255, 7, 248, 10, 255, 7
+250, 10, 255, 7, 252, 10, 255, 7, 254, 10, 255, 7, 176, 11, 0, 0
+240, 255, 70, 68, 214, 0, 222, 7, 1, 0, 96, 12, 0, 0, 98, 12
+ 0, 0, 128, 12, 0, 0, 130, 12, 0, 0, 132, 12, 0, 0, 134, 12
+ 0, 0, 136, 12, 0, 0, 138, 12, 0, 0, 140, 12, 0, 0, 142, 12
+ 0, 0, 144, 12, 0, 0, 146, 12, 0, 0, 148, 12, 0, 0, 150, 12
+ 0, 0, 152, 12, 0, 0, 154, 12, 0, 0, 156, 12, 0, 0, 158, 12
+ 0, 0, 160, 12, 0, 0, 162, 12, 0, 0, 164, 12, 0, 0, 166, 12
+ 0, 0, 106, 20, 0, 0, 108, 20, 0, 0, 110, 20, 0, 0, 112, 20
+ 0, 0, 64, 12, 0, 0, 66, 12, 0, 0, 68, 12, 0, 0, 70, 12
+ 0, 0, 72, 12, 0, 0, 74, 12, 0, 0, 76, 12, 0, 0, 78, 12
+ 0, 0, 80, 12, 0, 0, 82, 12, 0, 0, 84, 12, 0, 0, 86, 12
+ 0, 0, 88, 12, 0, 0, 90, 12, 0, 0, 92, 12, 0, 0, 94, 12
+ 0, 0, 210, 7, 16, 0, 196, 7, 0, 0, 204, 7, 0, 0, 206, 7
+ 0, 0, 208, 7, 0, 0, 202, 7, 100, 0, 194, 7, 16, 0, 104, 12
+ 64, 1, 106, 12, 212, 0, 240, 255, 72, 68, 246, 1, 64, 17, 54, 19
+ 66, 17, 64, 1, 68, 17, 212, 0, 70, 17, 1, 1, 72, 17, 0, 0
+ 74, 17, 0, 0, 76, 17, 0, 0, 78, 17, 0, 0, 80, 17, 0, 0
+ 82, 17, 0, 0, 84, 17, 0, 0, 86, 17, 0, 0, 88, 17, 0, 0
+ 90, 17, 0, 0, 92, 17, 0, 0, 94, 17, 0, 0, 96, 17, 0, 0
+ 98, 17, 0, 0, 100, 17, 0, 0, 102, 17, 0, 0, 104, 17, 0, 0
+106, 17, 0, 0, 108, 17, 0, 0, 110, 17, 0, 0, 112, 17, 0, 0
+114, 17, 0, 0, 116, 17, 0, 0, 118, 17, 0, 0, 120, 17, 0, 0
+122, 17, 0, 0, 124, 17, 0, 0, 126, 17, 0, 0, 128, 17, 0, 0
+130, 17, 0, 0, 132, 17, 0, 0, 134, 17, 0, 0, 136, 17, 0, 0
+138, 17, 0, 0, 140, 17, 0, 0, 142, 17, 0, 0, 144, 17, 0, 0
+146, 17, 0, 0, 148, 17, 0, 0, 150, 17, 0, 0, 152, 17, 0, 0
+154, 17, 0, 0, 64, 20, 0, 0, 66, 20, 0, 0, 68, 20, 0, 0
+ 70, 20, 0, 0, 72, 20, 0, 0, 74, 20, 0, 0, 76, 20, 0, 0
+ 78, 20, 0, 0, 80, 20, 0, 0, 82, 20, 0, 0, 84, 20, 0, 0
+ 86, 20, 0, 0, 88, 20, 0, 0, 90, 20, 0, 0, 92, 20, 0, 0
+ 94, 20, 0, 0, 96, 20, 0, 0, 98, 20, 0, 0, 100, 20, 0, 0
+102, 20, 0, 0, 104, 20, 0, 0, 156, 17, 255, 0, 158, 17, 255, 255
+116, 20, 160, 0, 118, 20, 106, 0, 120, 20, 52, 0, 122, 20, 52, 0
+160, 17, 0, 0, 162, 17, 0, 0, 164, 17, 0, 0, 166, 17, 0, 0
+168, 17, 0, 0, 170, 17, 0, 0, 172, 17, 0, 0, 174, 17, 0, 0
+176, 17, 0, 0, 178, 17, 0, 0, 180, 17, 0, 0, 182, 17, 0, 0
+184, 17, 0, 0, 186, 17, 0, 0, 188, 17, 0, 0, 190, 17, 0, 0
+192, 17, 0, 0, 194, 17, 0, 0, 196, 17, 0, 0, 198, 17, 0, 0
+200, 17, 0, 0, 202, 17, 0, 0, 204, 17, 0, 0, 206, 17, 0, 0
+208, 17, 0, 0, 210, 17, 0, 0, 212, 17, 0, 0, 214, 17, 0, 0
+216, 17, 0, 0, 218, 17, 0, 0, 220, 17, 0, 0, 222, 17, 0, 0
+224, 17, 0, 0, 226, 17, 0, 0, 228, 17, 0, 0, 230, 17, 0, 0
+232, 17, 0, 0, 234, 17, 0, 0, 236, 17, 0, 0, 238, 17, 0, 0
+240, 17, 0, 0, 242, 17, 0, 0, 244, 17, 0, 0, 246, 17, 0, 0
+248, 17, 3, 255, 250, 17, 0, 0, 252, 17, 0, 0, 254, 17, 0, 0
+114, 20, 0, 0, 124, 20, 255, 255, 126, 20, 0, 0, 240, 255, 83, 84
+ 22, 1, 0, 9, 0, 0, 32, 9, 0, 0, 34, 9, 0, 0, 90, 9
+ 0, 0, 36, 9, 0, 0, 40, 9, 0, 0, 42, 9, 0, 0, 112, 7
+ 0, 0, 46, 9, 0, 0, 48, 9, 0, 0, 50, 9, 0, 0, 52, 9
+ 0, 0, 54, 9, 0, 0, 56, 9, 0, 0, 58, 9, 0, 0, 60, 9
+ 0, 0, 62, 9, 0, 0, 64, 9, 0, 0, 2, 9, 0, 0, 78, 9
+ 0, 0, 80, 9, 0, 0, 82, 9, 0, 0, 84, 9, 0, 0, 96, 9
+ 0, 0, 100, 9, 0, 0, 104, 9, 0, 0, 98, 9, 0, 0, 102, 9
+ 0, 0, 106, 9, 0, 0, 112, 9, 0, 0, 122, 9, 0, 0, 124, 9
+ 0, 0, 118, 9, 1, 0, 88, 9, 0, 0, 108, 9, 0, 0, 110, 9
+ 0, 0, 168, 7, 0, 0, 150, 7, 0, 0, 164, 7, 0, 0, 166, 7
+ 0, 0, 154, 7, 0, 0, 156, 7, 0, 0, 128, 7, 0, 0, 130, 7
+ 0, 0, 132, 7, 0, 0, 134, 7, 0, 0, 136, 7, 0, 0, 138, 7
+ 0, 0, 140, 7, 0, 0, 142, 7, 0, 0, 146, 7, 0, 0, 148, 7
+ 0, 0, 152, 7, 0, 0, 158, 7, 0, 0, 96, 7, 0, 0, 98, 7
+ 0, 0, 102, 7, 0, 0, 104, 7, 0, 0, 100, 7, 0, 0, 106, 7
+ 0, 0, 120, 7, 0, 0, 108, 7, 0, 0, 110, 7, 0, 0, 122, 7
+ 0, 0, 124, 7, 0, 0, 126, 7, 0, 0, 118, 7, 0, 0, 114, 9
+ 0, 0, 240, 255, 69, 83, 62, 0, 16, 9, 0, 0, 14, 9, 0, 0
+ 20, 9, 0, 0, 6, 9, 0, 0, 8, 9, 0, 0, 18, 9, 0, 0
+ 12, 9, 0, 0, 10, 9, 0, 0, 182, 7, 0, 0, 184, 7, 0, 0
+186, 7, 0, 0, 188, 7, 0, 0, 190, 7, 0, 0, 22, 9, 0, 0
+240, 255, 89, 67, 54, 1, 98, 11, 0, 0, 100, 11, 0, 0, 102, 11
+ 0, 0, 104, 11, 0, 0, 106, 11, 0, 0, 108, 11, 0, 0, 110, 11
+ 0, 0, 112, 11, 0, 0, 114, 11, 0, 0, 116, 11, 0, 0, 118, 11
+ 0, 0, 120, 11, 0, 0, 122, 11, 0, 0, 124, 11, 0, 0, 96, 11
+ 0, 0, 0, 12, 0, 32, 2, 12, 12, 32, 4, 12, 26, 32, 6, 12
+ 53, 32, 8, 12, 144, 32, 10, 12, 1, 33, 12, 12, 125, 33, 14, 12
+ 11, 34, 16, 12, 169, 34, 18, 12, 79, 35, 20, 12, 10, 36, 22, 12
+204, 36, 24, 12, 157, 37, 26, 12, 124, 38, 28, 12, 117, 39, 30, 12
+130, 40, 96, 21, 53, 0, 114, 21, 100, 0, 98, 21, 88, 2, 100, 21
+109, 54, 104, 21, 27, 3, 116, 21, 0, 0, 118, 21, 0, 0, 54, 21
+ 1, 0, 56, 21, 148, 24, 58, 21, 100, 23, 60, 21, 100, 23, 62, 21
+145, 22, 106, 21, 0, 0, 108, 21, 0, 0, 110, 21, 0, 0, 112, 21
+ 0, 0, 220, 11, 0, 0, 222, 11, 0, 0, 192, 11, 1, 0, 194, 11
+ 51, 48, 196, 11, 0, 0, 198, 11, 32, 32, 200, 11, 0, 0, 202, 11
+102, 86, 204, 11, 9, 18, 206, 11, 22, 11, 208, 11, 0, 0, 210, 11
+102, 86, 212, 11, 248, 244, 214, 11, 255, 28, 64, 16, 0, 0, 66, 16
+ 0, 0, 68, 16, 0, 0, 70, 16, 0, 0, 72, 16, 0, 0, 74, 16
+ 0, 0, 76, 16, 0, 0, 78, 16, 0, 0, 80, 16, 0, 0, 82, 16
+ 0, 0, 84, 16, 0, 0, 86, 16, 0, 0, 87, 16, 0, 0, 88, 16
+ 0, 0, 90, 16, 0, 0, 240, 255, 65, 84, 106, 0, 34, 12, 0, 0
+ 32, 12, 0, 0, 36, 12, 0, 0, 38, 12, 0, 0, 40, 12, 0, 0
+ 42, 12, 0, 0, 120, 12, 0, 0, 122, 12, 0, 0, 44, 12, 0, 0
+ 46, 12, 0, 0, 48, 12, 0, 0, 50, 12, 0, 0, 52, 12, 0, 0
+ 54, 12, 0, 0, 56, 12, 0, 0, 58, 12, 0, 0, 60, 12, 0, 0
+ 62, 12, 0, 0, 124, 12, 0, 0, 126, 12, 0, 0, 168, 12, 0, 0
+170, 12, 0, 0, 172, 12, 0, 0, 174, 12, 0, 0, 176, 12, 0, 0
+240, 255, 73, 65, 134, 0, 160, 9, 255, 255, 162, 9, 255, 255, 164, 9
+255, 255, 166, 9, 255, 255, 168, 9, 255, 255, 170, 9, 255, 255, 172, 9
+255, 255, 174, 9, 255, 255, 176, 9, 255, 255, 178, 9, 255, 255, 180, 9
+255, 255, 182, 9, 255, 255, 184, 9, 255, 255, 186, 9, 255, 255, 188, 9
+255, 255, 190, 9, 255, 255, 128, 9, 0, 0, 130, 9, 0, 0, 132, 9
+ 0, 0, 134, 9, 0, 0, 136, 9, 0, 0, 138, 9, 0, 0, 144, 9
+ 0, 0, 146, 9, 0, 0, 140, 9, 0, 0, 142, 9, 0, 0, 148, 9
+ 0, 0, 150, 9, 0, 0, 152, 9, 0, 0, 154, 9, 0, 0, 156, 9
+ 0, 0, 158, 9, 0, 0, 240, 255, 68, 83, 70, 0, 0, 15, 0, 0
+ 28, 15, 1, 0, 2, 15, 0, 0, 24, 15, 0, 0, 30, 15, 0, 0
+ 4, 15, 0, 0, 6, 15, 0, 0, 8, 15, 0, 0, 10, 15, 0, 0
+ 12, 15, 0, 0, 14, 15, 0, 0, 26, 15, 0, 0, 16, 15, 0, 0
+ 18, 15, 0, 0, 20, 15, 0, 0, 22, 15, 0, 0, 240, 255, 73, 83
+ 46, 1, 68, 19, 5, 0, 70, 19, 0, 0, 71, 19, 0, 0, 96, 18
+ 0, 0, 66, 19, 64, 64, 146, 19, 7, 0, 130, 19, 0, 0, 132, 19
+ 0, 0, 134, 19, 0, 0, 135, 19, 0, 0, 148, 19, 242, 88, 150, 19
+242, 88, 152, 19, 0, 0, 154, 19, 7, 5, 94, 19, 200, 0, 95, 19
+200, 0, 156, 19, 0, 0, 158, 19, 7, 5, 92, 19, 200, 0, 93, 19
+200, 0, 74, 19, 108, 0, 76, 19, 71, 1, 78, 19, 95, 1, 72, 19
+255, 0, 73, 19, 255, 0, 86, 19, 224, 3, 88, 19, 224, 3, 90, 19
+223, 3, 96, 19, 224, 3, 102, 19, 232, 3, 108, 19, 232, 3, 98, 19
+224, 3, 104, 19, 232, 3, 110, 19, 232, 3, 100, 19, 224, 3, 106, 19
+232, 3, 112, 19, 232, 3, 160, 18, 0, 0, 162, 18, 0, 0, 164, 18
+ 0, 0, 166, 18, 25, 0, 168, 18, 0, 0, 170, 18, 0, 0, 172, 18
+ 4, 0, 174, 18, 0, 170, 80, 19, 144, 1, 82, 19, 144, 1, 84, 19
+144, 1, 136, 19, 232, 3, 138, 19, 232, 3, 140, 19, 232, 3, 142, 19
+104, 1, 144, 19, 68, 1, 192, 19, 4, 0, 122, 19, 0, 31, 124, 19
+ 80, 30, 126, 19, 178, 29, 114, 19, 144, 0, 115, 19, 144, 0, 116, 19
+ 70, 0, 117, 19, 144, 0, 118, 19, 144, 0, 119, 19, 70, 0, 160, 19
+ 0, 0, 162, 19, 102, 255, 164, 19, 39, 0, 166, 19, 108, 253, 168, 19
+ 99, 255, 170, 19, 40, 0, 172, 19, 242, 252, 120, 19, 65, 2, 174, 19
+ 0, 0, 176, 19, 0, 0, 178, 19, 0, 0, 240, 255, 76, 78, 122, 0
+ 96, 14, 0, 1, 98, 14, 0, 1, 100, 14, 0, 1, 102, 14, 128, 0
+104, 14, 128, 0, 106, 14, 128, 0, 108, 14, 29, 64, 110, 14, 0, 0
+112, 14, 4, 0, 114, 14, 0, 128, 116, 14, 27, 3, 164, 14, 88, 2
+166, 14, 88, 2, 128, 14, 11, 0, 130, 14, 0, 0, 132, 14, 145, 46
+134, 14, 137, 46, 140, 14, 0, 3, 136, 14, 165, 46, 138, 14, 0, 3
+142, 14, 0, 0, 144, 14, 11, 0, 146, 14, 0, 0, 148, 14, 0, 0
+156, 14, 0, 0, 150, 14, 0, 0, 152, 14, 0, 0, 154, 14, 4, 0
+158, 14, 0, 0, 240, 255, 69, 70, 174, 0, 96, 4, 155, 4, 98, 4
+129, 14, 100, 4, 155, 4, 102, 4, 129, 14, 104, 4, 155, 4, 106, 4
+129, 14, 108, 4, 155, 4, 110, 4, 129, 14, 174, 4, 158, 5, 176, 4
+229, 12, 116, 4, 0, 0, 118, 4, 0, 0, 120, 4, 0, 0, 122, 4
+ 0, 0, 114, 4, 0, 0, 112, 4, 0, 0, 178, 4, 0, 0, 124, 4
+ 0, 0, 126, 4, 133, 0, 128, 4, 0, 8, 130, 4, 0, 8, 132, 4
+ 0, 8, 134, 4, 0, 8, 136, 4, 0, 8, 138, 4, 0, 8, 140, 4
+ 0, 8, 142, 4, 0, 8, 144, 4, 0, 8, 146, 4, 255, 0, 148, 4
+ 0, 0, 150, 4, 0, 0, 152, 4, 0, 0, 154, 4, 0, 0, 156, 4
+ 0, 0, 158, 4, 0, 0, 160, 4, 159, 15, 162, 4, 76, 15, 164, 4
+ 0, 0, 166, 4, 0, 0, 168, 4, 0, 0, 170, 4, 0, 0, 172, 4
+ 0, 0, 240, 255, 82, 67, 150, 1, 0, 22, 0, 0, 2, 22, 0, 0
+ 4, 22, 0, 0, 6, 22, 0, 0, 8, 22, 3, 0, 10, 22, 3, 0
+ 12, 22, 3, 0, 14, 22, 3, 0, 16, 22, 3, 0, 18, 22, 0, 0
+ 20, 22, 0, 0, 22, 22, 0, 0, 24, 22, 0, 0, 26, 22, 0, 0
+ 30, 22, 0, 0, 32, 22, 0, 0, 34, 22, 33, 0, 36, 22, 28, 0
+ 38, 22, 36, 0, 40, 22, 0, 0, 42, 22, 50, 0, 44, 22, 250, 0
+ 46, 22, 11, 1, 48, 22, 52, 1, 64, 22, 84, 0, 66, 22, 104, 0
+ 68, 22, 219, 23, 70, 22, 31, 16, 72, 22, 0, 0, 74, 22, 12, 0
+ 76, 22, 12, 0, 78, 22, 12, 0, 80, 22, 12, 0, 82, 22, 0, 0
+ 58, 22, 98, 0, 60, 22, 0, 0, 62, 22, 250, 102, 128, 22, 0, 0
+ 96, 22, 157, 19, 98, 22, 157, 19, 100, 22, 159, 19, 102, 22, 159, 19
+104, 22, 159, 19, 106, 22, 0, 0, 108, 22, 160, 19, 110, 22, 172, 19
+112, 22, 173, 19, 114, 22, 174, 19, 116, 22, 0, 0, 118, 22, 0, 0
+120, 22, 3, 0, 122, 22, 3, 0, 160, 23, 0, 0, 162, 23, 0, 0
+164, 23, 0, 0, 166, 23, 0, 0, 168, 23, 0, 0, 170, 23, 0, 0
+172, 23, 0, 0, 174, 23, 0, 0, 176, 23, 0, 0, 178, 23, 0, 0
+180, 23, 0, 0, 182, 23, 0, 0, 184, 23, 0, 0, 186, 23, 0, 0
+188, 23, 0, 0, 190, 23, 0, 0, 192, 23, 0, 0, 194, 23, 0, 0
+196, 23, 0, 0, 198, 23, 0, 0, 200, 23, 0, 0, 202, 23, 0, 0
+204, 23, 0, 0, 206, 23, 0, 0, 208, 23, 0, 0, 210, 23, 0, 0
+212, 23, 0, 0, 214, 23, 0, 0, 216, 23, 0, 0, 218, 23, 0, 0
+220, 23, 0, 0, 222, 23, 0, 0, 224, 23, 0, 0, 226, 23, 0, 0
+228, 23, 0, 0, 230, 23, 0, 0, 232, 23, 0, 0, 234, 23, 0, 0
+236, 23, 0, 0, 238, 23, 0, 0, 240, 23, 0, 0, 242, 23, 0, 0
+244, 23, 0, 0, 246, 23, 0, 0, 248, 23, 0, 0, 250, 23, 0, 0
+252, 23, 0, 0, 254, 23, 0, 0, 240, 255, 67, 77, 26, 0, 0, 10
+ 6, 0, 254, 9, 36, 48, 224, 9, 99, 99, 226, 9, 99, 99, 112, 10
+ 88, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 68, 83, 67, 80, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 68, 73, 83, 86
+ 2, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, 84, 0, 0, 0, 0
+108, 84, 0, 0, 0, 0, 108, 84, 0, 0, 0, 0, 107, 84, 0, 0
+ 0, 0, 109, 84, 0, 0, 0, 0, 106, 84, 0, 0, 0, 0, 106, 85
+ 0, 0, 0, 0, 107, 84, 0, 0, 0, 0, 107, 84, 0, 0, 0, 0
+105, 84, 0, 0, 0, 0, 168, 128, 0, 0, 0, 0, 168, 128, 0, 0
+ 0, 0, 167, 128, 0, 0, 0, 0, 169, 128, 0, 0, 0, 0, 169, 128
+ 0, 0, 0, 0, 167, 128, 0, 0, 0, 0, 167, 128, 0, 0, 0, 0
+168, 128, 0, 0, 0, 0, 168, 128, 0, 0, 0, 0, 168, 128, 0, 0
+ 0, 0, 49, 34, 0, 0, 0, 0, 49, 34, 0, 0, 0, 0, 47, 34
+ 0, 0, 0, 0, 48, 34, 0, 0, 0, 0, 49, 34, 0, 0, 0, 0
+ 48, 34, 0, 0, 0, 0, 47, 34, 0, 0, 0, 0, 48, 34, 0, 0
+ 0, 0, 48, 34, 0, 0, 0, 0, 47, 34, 0, 0, 0, 0, 177, 141
+ 0, 0, 0, 0, 176, 140, 0, 0, 0, 0, 177, 141, 0, 0, 0, 0
+174, 141, 0, 0, 0, 0, 176, 141, 0, 0, 0, 0, 176, 141, 0, 0
+ 0, 0, 176, 141, 0, 0, 0, 0, 177, 142, 0, 0, 0, 0, 175, 141
+ 0, 0, 0, 0, 174, 141, 0, 0, 0, 0, 107, 82, 0, 0, 0, 0
+106, 82, 0, 0, 0, 0, 108, 82, 0, 0, 0, 0, 106, 82, 0, 0
+ 0, 0, 107, 83, 0, 0, 0, 0, 105, 83, 0, 0, 0, 0, 106, 83
+ 0, 0, 0, 0, 106, 83, 0, 0, 0, 0, 105, 83, 0, 0, 0, 0
+105, 83, 0, 0, 0, 0, 160, 121, 0, 0, 0, 0, 161, 122, 0, 0
+ 0, 0, 157, 121, 0, 0, 0, 0, 157, 121, 0, 0, 0, 0, 157, 121
+ 0, 0, 0, 0, 158, 121, 0, 0, 0, 0, 159, 121, 0, 0, 0, 0
+160, 121, 0, 0, 0, 0, 159, 121, 0, 0, 0, 0, 159, 121, 0, 0
+ 0, 0, 48, 34, 0, 0, 0, 0, 48, 34, 0, 0, 0, 0, 46, 34
+ 0, 0, 0, 0, 46, 34, 0, 0, 0, 0, 48, 34, 0, 0, 14, 1
+ 49, 33, 0, 0, 0, 0, 50, 34, 0, 0, 0, 0, 50, 34, 0, 0
+ 0, 0, 48, 34, 0, 0, 0, 0, 48, 34, 0, 0, 0, 0, 163, 129
+ 0, 0, 0, 0, 161, 129, 0, 0, 0, 0, 162, 130, 0, 0, 0, 0
+161, 130, 0, 0, 0, 0, 162, 130, 0, 0, 0, 0, 162, 130, 0, 0
+ 0, 0, 160, 130, 0, 0, 0, 0, 163, 129, 0, 0, 0, 0, 162, 130
+ 0, 0, 0, 0, 161, 129, 34, 33, 32, 32, 33, 33, 40, 33, 32, 31
+ 13, 12, 11, 14, 14, 12, 15, 13, 9, 12, 67, 61, 67, 62, 72, 64
+ 63, 80, 63, 69, 30, 22, 26, 30, 25, 26, 20, 30, 24, 27, 10, 9
+ 9, 9, 10, 9, 10, 10, 7, 10, 3, 4, 3, 5, 5, 4, 3, 3
+ 3, 5, 39, 38, 39, 44, 51, 43, 39, 41, 38, 41, 15, 12, 14, 14
+ 16, 13, 16, 14, 13, 19, 28, 29, 28, 30, 32, 31, 33, 31, 26, 31
+ 10, 10, 10, 12, 16, 13, 11, 10, 12, 10, 55, 61, 53, 57, 60, 58
+ 51, 60, 63, 57, 37, 35, 32, 38, 38, 33, 32, 36, 35, 39, 9, 6
+ 7, 8, 8, 7, 9, 7, 9, 8, 2, 3, 1, 2, 2, 1, 3, 3
+ 2, 3, 38, 35, 34, 35, 38, 37, 33, 31, 37, 38, 16, 14, 14, 14
+ 17, 18, 15, 13, 14, 12, 0, 0, 79, 73, 83, 71, 1, 12, 0, 0
+255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 254, 255, 254, 255
+249, 255, 249, 255, 248, 255, 248, 255, 255, 255, 0, 0, 249, 255, 243, 254
+243, 254, 242, 255, 0, 0, 254, 255, 252, 255, 252, 255, 253, 255, 255, 255
+ 0, 0, 1, 0, 2, 0, 249, 255, 249, 255, 250, 255, 250, 255, 255, 255
+ 0, 0, 249, 255, 189, 254, 189, 254, 3, 0, 0, 0, 0, 0, 2, 0
+ 4, 0, 5, 0, 3, 0, 4, 0, 3, 0, 0, 0, 249, 255, 248, 255
+247, 255, 246, 255, 255, 255, 0, 0, 249, 255, 108, 255, 108, 255, 39, 0
+ 0, 0, 3, 0, 0, 0, 253, 255, 252, 255, 253, 255, 253, 255, 250, 255
+252, 255, 244, 255, 250, 255, 246, 255, 250, 255, 255, 255, 0, 0, 249, 255
+ 74, 255, 74, 255, 121, 255, 2, 0, 254, 255, 251, 255, 251, 255, 0, 0
+ 0, 0, 4, 0, 8, 0, 6, 0, 252, 255, 251, 255, 249, 255, 250, 255
+255, 255, 0, 0, 249, 255, 34, 255, 34, 255, 28, 0, 45, 0, 3, 0
+ 3, 0, 3, 0, 0, 0, 2, 0, 254, 255, 250, 255, 249, 255, 250, 255
+250, 255, 252, 255, 255, 255, 255, 255, 0, 0, 249, 255, 143, 255, 143, 255
+115, 255, 139, 0, 253, 255, 249, 255, 248, 255, 249, 255, 251, 255, 0, 0
+ 5, 0, 11, 0, 254, 255, 254, 255, 254, 255, 251, 255, 255, 255, 0, 0
+249, 255, 206, 254, 206, 254, 24, 0, 1, 1, 252, 255, 255, 255, 1, 0
+ 2, 0, 12, 0, 11, 0, 7, 0, 2, 0, 250, 255, 249, 255, 251, 255
+252, 255, 255, 255, 0, 0, 249, 255, 12, 255, 12, 255, 205, 0, 55, 1
+ 1, 0, 255, 255, 254, 255, 252, 255, 255, 255, 253, 255, 254, 255, 0, 0
+253, 255, 254, 255, 253, 255, 252, 255, 255, 255, 0, 0, 249, 255, 226, 254
+226, 254, 151, 0, 169, 1, 253, 255, 254, 255, 0, 0, 0, 0, 2, 0
+ 3, 0, 2, 0, 0, 0, 250, 255, 248, 255, 245, 255, 244, 255, 255, 255
+ 0, 0, 249, 255, 245, 254, 245, 254, 162, 0, 64, 1, 1, 0, 0, 0
+ 0, 0, 254, 255, 252, 255, 249, 255, 249, 255, 250, 255, 243, 255, 244, 255
+247, 255, 248, 255, 255, 255, 0, 0, 249, 255, 6, 255, 6, 255, 201, 255
+221, 0, 253, 255, 2, 0, 252, 255, 250, 255, 253, 255, 255, 255, 6, 0
+ 6, 0, 249, 255, 255, 255, 252, 255, 248, 255, 255, 255, 0, 0, 249, 255
+156, 254, 156, 254, 26, 0, 14, 1, 254, 255, 253, 255, 252, 255, 252, 255
+253, 255, 252, 255, 254, 255, 255, 255, 248, 255, 250, 255, 252, 255, 253, 255
+255, 255, 0, 0, 249, 255, 185, 253, 185, 253, 111, 254, 0, 0, 253, 255
+253, 255, 253, 255, 253, 255, 0, 0, 3, 0, 6, 0, 10, 0, 253, 255
+254, 255, 254, 255, 254, 255, 255, 255, 0, 0, 249, 255, 141, 253, 141, 253
+ 79, 255, 0, 0, 254, 255, 0, 0, 0, 0, 1, 0, 12, 0, 13, 0
+ 12, 0, 10, 0, 254, 255, 253, 255, 252, 255, 252, 255, 255, 255, 0, 0
+249, 255, 211, 253, 211, 253, 163, 0, 0, 0, 1, 0, 0, 0, 0, 0
+255, 255, 6, 0, 3, 0, 0, 0, 255, 255, 251, 255, 250, 255, 248, 255
+246, 255, 255, 255, 0, 0, 249, 255, 15, 254, 15, 254, 152, 0, 0, 0
+ 0, 0, 255, 255, 255, 255, 254, 255, 253, 255, 251, 255, 251, 255, 250, 255
+245, 255, 244, 255, 245, 255, 246, 255, 255, 255, 0, 0, 249, 255, 17, 254
+ 17, 254, 213, 255, 0, 0, 253, 255, 253, 255, 254, 255, 255, 255, 251, 255
+254, 255, 0, 0, 4, 0, 246, 255, 247, 255, 248, 255, 249, 255, 255, 255
+ 0, 0, 249, 255, 3, 254, 3, 254, 227, 255, 0, 0, 0, 0, 0, 0
+ 0, 0, 255, 255, 6, 0, 9, 0, 11, 0, 11, 0, 250, 255, 250, 255
+250, 255, 251, 255, 255, 255, 0, 0, 249, 255, 38, 254, 38, 254, 8, 1
+ 0, 0, 254, 255, 253, 255, 253, 255, 0, 0, 9, 0, 5, 0, 1, 0
+254, 255, 251, 255, 250, 255, 249, 255, 248, 255, 255, 255, 0, 0, 249, 255
+ 31, 254, 31, 254, 18, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0
+251, 255, 250, 255, 250, 255, 251, 255, 247, 255, 246, 255, 244, 255, 246, 255
+255, 255, 0, 0, 249, 255, 127, 254, 127, 254, 66, 0, 0, 0, 0, 0
+ 0, 0, 255, 255, 255, 255, 253, 255, 0, 0, 3, 0, 5, 0, 247, 255
+250, 255, 252, 255, 254, 255, 255, 255, 0, 0, 249, 255, 134, 254, 134, 254
+142, 0, 0, 0, 254, 255, 254, 255, 0, 0, 0, 0, 7, 0, 7, 0
+ 7, 0, 6, 0, 254, 255, 254, 255, 254, 255, 254, 255, 255, 255, 0, 0
+249, 255, 156, 254, 156, 254, 81, 1, 0, 0, 0, 0, 0, 0, 0, 0
+255, 255, 4, 0, 0, 0, 254, 255, 252, 255, 254, 255, 254, 255, 252, 255
+251, 255, 255, 255, 0, 0, 249, 255, 193, 254, 193, 254, 247, 0, 0, 0
+254, 255, 252, 255, 251, 255, 251, 255, 252, 255, 252, 255, 254, 255, 0, 0
+250, 255, 250, 255, 249, 255, 248, 255, 255, 255, 0, 0, 249, 255, 85, 254
+ 85, 254, 154, 0, 0, 0, 250, 255, 252, 255, 0, 0, 2, 0, 0, 0
+ 0, 0, 1, 0, 2, 0, 249, 255, 249, 255, 249, 255, 248, 255, 255, 255
+ 0, 0, 249, 255, 121, 254, 121, 254, 178, 0, 0, 0, 4, 0, 4, 0
+ 1, 0, 255, 255, 2, 0, 2, 0, 2, 0, 2, 0, 248, 255, 248, 255
+248, 255, 248, 255, 255, 255, 0, 0, 249, 255, 210, 254, 210, 254, 220, 0
+ 0, 0, 251, 255, 250, 255, 250, 255, 253, 255, 1, 0, 0, 0, 0, 0
+255, 255, 249, 255, 248, 255, 249, 255, 248, 255, 255, 255, 0, 0, 249, 255
+107, 254, 107, 254, 180, 0, 0, 0, 255, 255, 1, 0, 2, 0, 2, 0
+255, 255, 255, 255, 255, 255, 255, 255, 249, 255, 248, 255, 247, 255, 248, 255
+255, 255, 0, 0, 249, 255, 204, 254, 204, 254, 105, 0, 0, 0, 0, 0
+ 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 248, 255
+249, 255, 249, 255, 249, 255, 255, 255, 0, 0, 249, 255, 217, 254, 217, 254
+ 76, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 128, 0, 64, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0
+ 0, 0, 0, 64, 0, 0, 0, 0, 16, 0, 0, 64, 0, 0, 0, 0
+ 32, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 192, 0, 48, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0
+ 0, 0, 0, 0, 0, 64, 0, 0, 0, 0, 32, 64, 0, 0, 0, 0
+ 0, 16, 0, 0, 32, 0, 0, 0, 0, 0, 0, 4, 0, 8, 0, 0
+ 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0, 0
+ 0, 64, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 65, 69, 66, 77, 5, 0, 17, 0, 20, 0, 46, 0
+ 48, 0, 48, 0, 47, 0, 51, 0, 48, 0, 48, 0, 53, 0, 50, 0
+ 3, 0, 16, 0, 20, 0, 41, 0, 74, 0, 76, 0, 38, 0, 86, 0
+ 48, 0, 51, 0, 53, 0, 49, 0, 3, 0, 30, 0, 21, 0, 39, 0
+ 58, 0, 123, 0, 43, 0, 161, 0, 60, 0, 89, 0, 55, 0, 52, 0
+ 3, 0, 4, 0, 9, 0, 42, 0, 55, 0, 70, 0, 53, 0, 100, 0
+101, 0, 57, 0, 61, 0, 58, 0, 4, 0, 5, 0, 5, 0, 18, 0
+ 31, 0, 46, 0, 107, 0, 73, 0, 92, 0, 67, 0, 68, 0, 65, 0
+ 4, 0, 5, 0, 2, 0, 18, 0, 27, 0, 49, 0, 137, 0, 81, 0
+ 50, 0, 62, 0, 74, 0, 66, 0, 3, 0, 3, 0, 3, 0, 14, 0
+ 28, 0, 54, 0, 125, 0, 65, 0, 43, 0, 51, 0, 43, 0, 68, 0
+ 2, 0, 2, 0, 5, 0, 19, 0, 25, 0, 33, 0, 43, 0, 29, 0
+ 55, 0, 39, 0, 43, 0, 67, 0, 1, 0, 4, 0, 4, 0, 11, 0
+ 22, 0, 18, 0, 14, 0, 19, 0, 59, 0, 31, 0, 67, 0, 63, 0
+ 4, 0, 1, 0, 1, 0, 7, 0, 14, 0, 21, 0, 11, 0, 13, 0
+ 21, 0, 32, 0, 54, 0, 60, 0, 3, 0, 4, 0, 2, 0, 5, 0
+ 9, 0, 26, 0, 14, 0, 13, 0, 4, 0, 13, 0, 34, 0, 56, 0
+ 1, 0, 3, 0, 2, 0, 5, 0, 9, 0, 12, 0, 12, 0, 16, 0
+ 16, 0, 29, 0, 77, 0, 65, 0, 65, 69, 72, 76, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 65, 69, 67, 90
+ 8, 20, 20, 20, 3, 3, 3, 3, 3, 3, 3, 3, 8, 20, 7, 20
+ 20, 8, 20, 8, 3, 3, 3, 3, 8, 8, 8, 3, 20, 8, 3, 8
+ 20, 8, 3, 3, 8, 8, 8, 20, 20, 8, 3, 8, 20, 20, 3, 3
+ 8, 8, 8, 20, 20, 8, 8, 20, 20, 20, 3, 3, 8, 8, 8, 20
+ 20, 8, 8, 8, 20, 20, 20, 3, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 20, 8, 3, 8, 8, 8, 8, 8, 8, 8, 8, 20, 20, 3, 20
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 3, 3, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 20, 20, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 20, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 20, 3
+ 80, 82, 83, 84, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 70, 67, 67, 86
+ 18, 0, 132, 1, 166, 46, 218, 148, 120, 40, 186, 46, 196, 46, 177, 46
+202, 46, 190, 46, 190, 46, 179, 46, 184, 46, 214, 46, 188, 46, 218, 148
+120, 40, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 91, 0, 8, 0, 167, 46, 134, 46, 156, 46, 126, 11
+154, 14, 172, 10, 106, 14, 237, 8, 43, 13, 134, 21, 65, 5, 41, 9
+154, 6, 96, 12, 21, 5, 117, 6, 35, 5, 218, 6, 27, 4, 207, 5
+ 67, 9, 223, 2, 238, 3, 0, 0, 246, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 127, 0, 73, 46, 143, 17, 74, 32, 75, 13
+175, 16, 175, 12, 50, 16, 127, 10, 182, 15, 204, 24, 54, 6, 106, 11
+229, 6, 9, 13, 89, 5, 191, 6, 127, 5, 65, 7, 72, 4, 26, 6
+192, 9, 8, 3, 33, 4, 0, 0, 19, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 128, 0, 172, 46, 124, 18, 237, 34, 248, 13
+244, 17, 230, 13, 82, 17, 143, 11, 32, 17, 144, 26, 109, 7, 55, 13
+ 40, 7, 174, 13, 155, 5, 12, 7, 204, 5, 167, 7, 132, 4, 107, 6
+ 45, 10, 78, 3, 97, 4, 0, 0, 76, 8, 32, 10, 0, 0, 206, 46
+ 3, 0, 249, 255, 250, 255, 129, 0, 16, 47, 74, 17, 21, 33, 119, 13
+189, 16, 139, 13, 76, 16, 9, 11, 46, 16, 19, 25, 116, 7, 183, 12
+ 46, 7, 198, 13, 157, 5, 2, 7, 225, 5, 171, 7, 122, 4, 119, 6
+ 50, 10, 74, 3, 121, 4, 0, 0, 104, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 130, 0, 115, 47, 219, 13, 121, 26, 59, 11
+ 80, 13, 109, 11, 223, 12, 69, 9, 232, 12, 214, 19, 180, 6, 110, 10
+173, 6, 208, 12, 52, 5, 104, 6, 133, 5, 30, 7, 35, 4, 239, 5
+114, 9, 8, 3, 31, 4, 0, 0, 11, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 131, 0, 245, 45, 136, 15, 63, 28, 233, 11
+ 30, 14, 32, 11, 31, 14, 170, 9, 52, 13, 162, 21, 8, 5, 123, 9
+184, 6, 183, 12, 59, 5, 131, 6, 70, 5, 238, 6, 41, 4, 12, 6
+117, 9, 212, 2, 239, 3, 208, 255, 201, 255, 217, 255, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 132, 0, 93, 46, 255, 17, 74, 33, 164, 13
+217, 16, 12, 13, 109, 16, 55, 11, 5, 16, 58, 25, 104, 6, 169, 11
+ 38, 7, 185, 13, 137, 5, 251, 6, 153, 5, 139, 7, 121, 4, 122, 6
+ 38, 10, 19, 3, 71, 4, 0, 0, 59, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 133, 0, 193, 46, 36, 18, 56, 34, 31, 14
+ 26, 17, 194, 13, 182, 16, 163, 11, 106, 16, 179, 25, 34, 7, 152, 12
+ 45, 7, 179, 13, 141, 5, 246, 6, 170, 5, 134, 7, 123, 4, 123, 6
+ 28, 10, 39, 3, 75, 4, 0, 0, 38, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 134, 0, 37, 47, 121, 16, 217, 31, 66, 13
+217, 15, 86, 13, 171, 15, 246, 10, 113, 15, 194, 23, 103, 7, 27, 12
+ 19, 7, 168, 13, 115, 5, 227, 6, 166, 5, 135, 7, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 198, 0, 199, 46
+ 2, 0, 249, 255, 250, 255, 0, 0, 12, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 136, 0, 218, 45, 196, 14, 96, 26, 218, 10
+190, 13, 1, 10, 122, 13, 249, 8, 85, 12, 46, 20, 237, 4, 171, 8
+120, 6, 53, 12, 216, 4, 90, 6, 218, 4, 175, 6, 252, 3, 204, 5
+254, 8, 190, 2, 199, 3, 0, 0, 121, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 137, 0, 65, 46, 116, 17, 147, 32, 184, 12
+132, 16, 235, 11, 210, 16, 217, 10, 18, 15, 168, 24, 23, 6, 242, 10
+250, 6, 61, 13, 63, 5, 211, 6, 70, 5, 76, 7, 103, 4, 52, 6
+185, 9, 8, 3, 37, 4, 0, 0, 226, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 138, 0, 164, 46, 170, 18, 155, 35, 35, 14
+ 13, 18, 195, 13, 13, 18, 40, 12, 203, 16, 229, 26, 121, 7, 145, 12
+ 77, 7, 246, 13, 131, 5, 210, 255, 206, 255, 216, 255, 161, 4, 118, 6
+ 67, 10, 74, 3, 106, 4, 0, 0, 60, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 139, 0, 8, 47, 132, 16, 53, 32, 242, 12
+116, 16, 20, 13, 41, 16, 58, 11, 149, 15, 84, 24, 161, 7, 235, 11
+255, 6, 128, 13, 85, 5, 219, 6, 131, 5, 133, 7, 121, 4, 47, 6
+227, 9, 59, 3, 73, 4, 0, 0, 2, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 140, 0, 107, 47, 235, 13, 162, 27, 237, 10
+ 10, 14, 156, 11, 227, 13, 150, 9, 54, 13, 139, 20, 1, 7, 145, 10
+186, 6, 245, 12, 36, 5, 149, 6, 106, 5, 69, 7, 55, 4, 235, 5
+113, 9, 39, 3, 35, 4, 0, 0, 247, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 141, 0, 216, 45, 210, 14, 77, 26, 157, 10
+ 75, 14, 224, 9, 175, 13, 203, 8, 101, 12, 62, 20, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 199, 0, 199, 46
+252, 255, 249, 255, 246, 255, 228, 6, 50, 5, 124, 7, 76, 4, 9, 6
+183, 9, 248, 2, 24, 4, 0, 0, 229, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 143, 0, 160, 46, 3, 18, 254, 34, 199, 13
+ 20, 18, 70, 13, 230, 17, 51, 11, 149, 16, 69, 26, 70, 7, 37, 12
+ 37, 7, 230, 13, 99, 5, 30, 7, 128, 5, 219, 7, 122, 4, 79, 6
+ 23, 10, 46, 3, 94, 4, 0, 0, 54, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 144, 0, 4, 47, 249, 16, 175, 33, 72, 13
+ 87, 17, 181, 13, 56, 17, 43, 11, 15, 16, 4, 25, 203, 7, 107, 12
+ 48, 7, 37, 14, 118, 5, 44, 7, 169, 5, 253, 7, 143, 4, 86, 6
+ 66, 10, 81, 3, 111, 4, 0, 0, 115, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 145, 0, 104, 47, 5, 14, 3, 28, 250, 10
+130, 14, 199, 11, 30, 14, 205, 255, 202, 255, 211, 255, 7, 7, 101, 10
+175, 6, 33, 13, 21, 5, 171, 6, 89, 5, 107, 7, 43, 4, 233, 5
+133, 9, 34, 3, 49, 4, 0, 0, 12, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 146, 0, 248, 45, 232, 15, 156, 28, 1, 11
+151, 15, 90, 10, 173, 14, 59, 9, 148, 13, 22, 22, 54, 5, 96, 9
+196, 6, 218, 12, 14, 5, 187, 6, 250, 4, 35, 7, 46, 4, 233, 5
+113, 9, 203, 2, 244, 3, 0, 0, 185, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 147, 0, 97, 46, 136, 18, 149, 34, 58, 13
+ 50, 18, 139, 12, 149, 17, 126, 11, 148, 16, 117, 26, 200, 6, 250, 11
+ 82, 7, 228, 13, 104, 5, 76, 7, 90, 5, 208, 7, 140, 4, 96, 6
+ 51, 10, 33, 3, 98, 4, 0, 0, 40, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 148, 0, 196, 46, 56, 18, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200, 0, 207, 46
+255, 255, 248, 255, 246, 255, 7, 11, 157, 15, 200, 23, 77, 7, 69, 12
+ 34, 7, 177, 13, 53, 5, 42, 7, 100, 5, 197, 7, 114, 4, 55, 6
+ 9, 10, 41, 3, 95, 4, 0, 0, 22, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 150, 0, 140, 47, 74, 13, 57, 26, 156, 10
+117, 13, 56, 11, 50, 13, 51, 9, 136, 12, 74, 19, 185, 6, 74, 10
+185, 6, 23, 13, 17, 5, 196, 6, 60, 5, 90, 7, 88, 4, 244, 5
+147, 9, 31, 3, 48, 4, 0, 0, 248, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 151, 0, 208, 45, 167, 14, 232, 25, 48, 10
+ 14, 14, 240, 8, 132, 13, 246, 8, 129, 12, 209, 19, 176, 4, 195, 8
+139, 6, 74, 12, 195, 4, 146, 6, 150, 4, 230, 6, 24, 4, 184, 5
+ 23, 9, 165, 2, 213, 3, 0, 0, 86, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 202, 255, 197, 255, 214, 255, 131, 31, 92, 12
+176, 16, 224, 10, 147, 16, 178, 10, 60, 15, 214, 23, 212, 5, 35, 11
+ 21, 7, 66, 13, 24, 5, 27, 7, 2, 5, 132, 7, 109, 4, 26, 6
+193, 9, 238, 2, 41, 4, 0, 0, 201, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 153, 0, 155, 46, 112, 18, 17, 35, 107, 13
+101, 18, 78, 12, 64, 18, 235, 11, 253, 16, 49, 26, 70, 7, 208, 12
+132, 7, 31, 14, 99, 5, 129, 7, 102, 5, 254, 7, 182, 4, 116, 6
+ 88, 10, 45, 3, 123, 4, 0, 0, 55, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 154, 0, 255, 46, 229, 16, 227, 32, 143, 12
+236, 16, 22, 12, 207, 16, 12, 11, 57, 16, 104, 24, 131, 7, 152, 12
+ 53, 7, 183, 13, 55, 5, 54, 7, 86, 5, 194, 7, 137, 4, 62, 6
+250, 9, 41, 3, 103, 4, 0, 0, 40, 8, 33, 10, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 201, 0, 194, 46
+250, 255, 245, 255, 249, 255, 156, 0, 232, 45, 177, 15, 226, 27, 114, 10
+247, 14, 28, 9, 187, 14, 131, 9, 56, 13, 69, 21, 88, 5, 143, 9
+241, 6, 207, 12, 241, 4, 224, 6, 214, 4, 38, 7, 75, 4, 250, 5
+ 98, 9, 207, 2, 51, 4, 0, 0, 235, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 157, 0, 78, 46, 189, 17, 151, 32, 117, 12
+ 63, 17, 121, 11, 249, 16, 47, 11, 139, 15, 138, 24, 95, 6, 140, 11
+ 70, 7, 124, 13, 37, 5, 57, 7, 28, 5, 155, 7, 120, 4, 60, 6
+223, 9, 244, 2, 110, 4, 0, 0, 19, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 158, 0, 176, 46, 142, 18, 150, 34, 113, 13
+100, 18, 225, 12, 77, 18, 68, 12, 161, 16, 243, 25, 76, 7, 20, 13
+130, 7, 246, 13, 87, 5, 108, 7, 91, 5, 238, 7, 153, 4, 101, 6
+ 48, 10, 26, 3, 159, 4, 212, 255, 201, 255, 210, 255, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 159, 0, 19, 47, 21, 17, 29, 33, 31, 13
+ 10, 17, 249, 12, 83, 17, 212, 11, 236, 15, 165, 24, 147, 7, 195, 12
+129, 7, 41, 14, 117, 5, 122, 7, 127, 5, 17, 8, 185, 4, 130, 6
+ 71, 10, 56, 3, 174, 4, 0, 0, 95, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 160, 0, 117, 47, 187, 13, 233, 26, 7, 11
+204, 13, 88, 11, 36, 14, 151, 9, 158, 12, 220, 19, 191, 6, 129, 10
+239, 6, 16, 13, 35, 5, 220, 6, 64, 5, 113, 7, 106, 4, 250, 5
+128, 9, 10, 3, 80, 4, 0, 0, 12, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 161, 0, 245, 45, 176, 15, 74, 28, 5, 11
+ 61, 15, 194, 9, 146, 14, 178, 9, 166, 13, 52, 21, 95, 5, 111, 9
+230, 6, 194, 12, 250, 4, 239, 6, 227, 4, 36, 7, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 202, 0, 201, 46
+248, 255, 246, 255, 246, 255, 0, 0, 50, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 163, 0, 194, 46, 82, 18, 189, 34, 66, 13
+ 59, 18, 236, 12, 102, 17, 227, 11, 43, 17, 131, 25, 91, 7, 254, 12
+122, 7, 14, 14, 106, 5, 144, 7, 110, 5, 241, 7, 151, 4, 160, 6
+ 82, 10, 46, 3, 158, 4, 0, 0, 38, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 164, 0, 38, 47, 56, 16, 69, 32, 29, 12
+138, 16, 130, 12, 255, 15, 205, 10, 188, 15, 162, 23, 90, 7, 116, 12
+ 53, 7, 175, 13, 59, 5, 79, 7, 84, 5, 198, 7, 102, 4, 115, 6
+ 3, 10, 38, 3, 131, 4, 0, 0, 5, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 165, 0, 137, 47, 151, 13, 46, 26, 182, 10
+190, 13, 8, 11, 9, 13, 22, 9, 208, 12, 147, 19, 175, 6, 124, 10
+249, 6, 33, 13, 39, 5, 202, 255, 196, 255, 208, 255, 80, 4, 41, 6
+168, 9, 27, 3, 95, 4, 0, 0, 3, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 166, 0, 214, 45, 23, 15, 86, 26, 141, 10
+ 53, 14, 41, 9, 5, 13, 37, 9, 191, 12, 51, 20, 157, 4, 48, 9
+181, 6, 84, 12, 239, 4, 165, 6, 179, 4, 199, 6, 50, 4, 8, 6
+ 17, 9, 174, 2, 27, 4, 0, 0, 123, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 167, 0, 63, 46, 226, 17, 107, 32, 127, 12
+ 56, 17, 121, 11, 21, 16, 216, 10, 149, 15, 75, 24, 184, 5, 178, 11
+ 24, 7, 79, 13, 69, 5, 31, 7, 33, 5, 106, 7, 101, 4, 97, 6
+194, 9, 236, 2, 106, 4, 0, 0, 189, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 168, 0, 163, 46, 15, 19, 174, 35, 179, 13
+231, 18, 63, 13, 202, 17, 31, 12, 101, 17, 105, 26, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 203, 0, 200, 46
+250, 255, 246, 255, 248, 255, 67, 7, 140, 5, 183, 7, 152, 4, 127, 6
+ 22, 10, 41, 3, 168, 4, 0, 0, 246, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 170, 0, 105, 47, 164, 14, 18, 28, 80, 11
+181, 14, 126, 11, 30, 14, 236, 9, 139, 13, 211, 20, 190, 6, 37, 11
+248, 6, 41, 13, 70, 5, 234, 6, 79, 5, 78, 7, 100, 4, 44, 6
+155, 9, 9, 3, 107, 4, 0, 0, 230, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 171, 0, 235, 45, 8, 16, 245, 27, 38, 11
+ 45, 15, 242, 9, 74, 14, 39, 10, 46, 13, 58, 21, 243, 4, 238, 9
+249, 6, 254, 12, 77, 5, 223, 6, 19, 5, 21, 7, 91, 4, 55, 6
+132, 9, 209, 2, 87, 4, 0, 0, 228, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 172, 0, 84, 46, 44, 18, 184, 32, 47, 13
+ 53, 17, 54, 12, 140, 16, 205, 255, 199, 255, 208, 255, 9, 6, 59, 12
+ 75, 7, 182, 13, 128, 5, 69, 7, 86, 5, 145, 7, 147, 4, 126, 6
+ 7, 10, 241, 2, 129, 4, 0, 0, 18, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 173, 0, 182, 46, 147, 18, 113, 34, 191, 13
+ 22, 18, 46, 13, 225, 16, 75, 12, 135, 16, 99, 25, 200, 6, 81, 13
+107, 7, 29, 14, 151, 5, 119, 7, 131, 5, 223, 7, 172, 4, 158, 6
+ 77, 10, 33, 3, 160, 4, 0, 0, 41, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 174, 0, 26, 47, 30, 17, 185, 32, 251, 12
+219, 16, 90, 13, 193, 15, 117, 11, 168, 15, 8, 24, 66, 7, 55, 13
+108, 7, 29, 14, 174, 5, 106, 7, 189, 5, 234, 7, 184, 4, 141, 6
+ 84, 10, 59, 3, 180, 4, 0, 0, 74, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 175, 0, 125, 47, 234, 13, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 204, 0, 162, 46
+244, 255, 246, 255, 245, 255, 240, 9, 64, 13, 237, 20, 254, 4, 163, 9
+223, 6, 184, 12, 61, 5, 197, 6, 15, 5, 241, 6, 73, 4, 28, 6
+ 87, 9, 184, 2, 34, 4, 0, 0, 172, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 177, 0, 89, 46, 98, 18, 68, 33, 55, 13
+141, 17, 79, 12, 153, 16, 19, 12, 216, 15, 207, 24, 96, 6, 160, 12
+122, 7, 246, 13, 165, 5, 100, 7, 135, 5, 175, 7, 176, 4, 172, 6
+ 54, 10, 15, 3, 158, 4, 0, 0, 52, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 178, 0, 187, 46, 192, 18, 209, 34, 161, 13
+248, 17, 238, 12, 55, 17, 107, 12, 176, 16, 221, 25, 0, 7, 147, 13
+141, 7, 41, 14, 173, 5, 139, 7, 159, 5, 225, 7, 178, 4, 199, 6
+106, 10, 37, 3, 191, 4, 0, 0, 62, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 211, 255, 211, 255, 219, 255, 114, 32, 208, 12
+199, 16, 171, 12, 208, 15, 136, 11, 183, 15, 9, 24, 54, 7, 146, 12
+ 79, 7, 182, 13, 142, 5, 65, 7, 140, 5, 181, 7, 143, 4, 121, 6
+ 13, 10, 23, 3, 139, 4, 0, 0, 15, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 180, 0, 130, 47, 83, 14, 41, 27, 221, 10
+ 16, 14, 20, 11, 100, 13, 134, 9, 50, 13, 32, 20, 185, 6, 104, 10
+ 8, 7, 56, 13, 98, 5, 236, 6, 117, 5, 98, 7, 110, 4, 54, 6
+186, 9, 30, 3, 110, 4, 0, 0, 32, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 181, 0, 227, 45, 186, 15, 219, 27, 169, 10
+ 14, 15, 149, 9, 29, 14, 86, 9, 142, 13, 42, 21, 243, 4, 108, 9
+201, 6, 145, 12, 26, 5, 176, 6, 227, 4, 228, 6, 56, 4, 10, 6
+ 84, 9, 192, 2, 37, 4, 0, 0, 225, 7, 32, 10, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 205, 0, 161, 46
+250, 255, 250, 255, 252, 255, 183, 0, 172, 46, 240, 18, 229, 35, 156, 13
+204, 18, 110, 13, 21, 18, 64, 12, 148, 17, 200, 26, 34, 7, 70, 13
+134, 7, 37, 14, 179, 5, 106, 7, 155, 5, 199, 7, 207, 4, 191, 6
+105, 10, 50, 3, 172, 4, 0, 0, 97, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 184, 0, 17, 47, 56, 17, 13, 33, 229, 12
+ 36, 17, 35, 13, 113, 16, 56, 11, 244, 15, 178, 24, 89, 7, 228, 12
+ 59, 7, 173, 13, 151, 5, 47, 7, 140, 5, 158, 7, 161, 4, 116, 6
+ 12, 10, 39, 3, 147, 4, 0, 0, 82, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 185, 0, 116, 47, 32, 14, 64, 27, 255, 10
+252, 13, 120, 11, 145, 13, 98, 9, 34, 13, 58, 20, 166, 6, 121, 10
+203, 6, 227, 12, 61, 5, 186, 6, 78, 5, 54, 7, 75, 4, 8, 6
+110, 9, 248, 2, 66, 4, 226, 255, 212, 255, 220, 255, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 186, 0, 230, 45, 4, 16, 20, 28, 96, 11
+103, 15, 70, 10, 152, 14, 43, 9, 67, 13, 120, 21, 244, 4, 154, 9
+226, 6, 177, 12, 49, 5, 203, 6, 12, 5, 241, 6, 70, 4, 26, 6
+109, 9, 188, 2, 52, 4, 0, 0, 228, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 187, 0, 80, 46, 33, 18, 32, 34, 79, 13
+198, 17, 15, 12, 160, 17, 30, 11, 108, 16, 209, 25, 18, 6, 243, 11
+ 73, 7, 129, 13, 115, 5, 58, 7, 92, 5, 117, 7, 144, 4, 107, 6
+253, 9, 234, 2, 111, 4, 0, 0, 49, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 188, 0, 177, 46, 10, 18, 17, 35, 26, 13
+242, 17, 156, 12, 167, 17, 90, 11, 102, 17, 248, 25, 164, 6, 216, 12
+101, 7, 201, 13, 120, 5, 74, 7, 128, 5, 165, 7, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 206, 0, 158, 46
+ 1, 0, 251, 255, 255, 255, 0, 0, 80, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 190, 0, 122, 47, 176, 13, 230, 26, 228, 10
+ 0, 14, 63, 11, 143, 13, 97, 9, 206, 12, 245, 19, 89, 6, 148, 10
+235, 6, 22, 13, 50, 5, 227, 6, 86, 5, 92, 7, 90, 4, 22, 6
+152, 9, 253, 2, 90, 4, 0, 0, 9, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 191, 0, 197, 45, 251, 11, 20, 22, 160, 8
+ 92, 12, 11, 8, 129, 11, 213, 6, 111, 10, 12, 17, 153, 3, 14, 7
+114, 5, 118, 10, 238, 3, 140, 5, 199, 3, 216, 5, 88, 3, 243, 4
+178, 7, 12, 2, 75, 3, 0, 0, 93, 6, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 192, 0, 50, 46, 209, 14, 20, 28, 178, 10
+157, 15, 17, 10, 207, 14, 24, 9, 117, 13, 114, 21, 181, 4, 133, 9
+ 5, 6, 177, 11, 99, 4, 224, 255, 209, 255, 216, 255, 197, 3, 136, 5
+151, 8, 76, 2, 181, 3, 0, 0, 3, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 193, 0, 151, 46, 65, 16, 130, 31, 189, 11
+100, 17, 120, 11, 58, 16, 177, 9, 254, 14, 24, 24, 165, 5, 229, 10
+ 59, 6, 52, 12, 140, 4, 114, 6, 138, 4, 210, 6, 232, 3, 178, 5
+253, 8, 115, 2, 222, 3, 0, 0, 53, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 194, 0, 250, 46, 247, 14, 49, 30, 53, 11
+ 83, 16, 96, 11, 87, 15, 63, 9, 100, 14, 200, 22, 218, 5, 153, 10
+253, 5, 1, 12, 114, 4, 42, 6, 132, 4, 191, 6, 212, 3, 128, 5
+200, 8, 111, 2, 192, 3, 0, 0, 20, 7, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 195, 0, 92, 47, 197, 12, 46, 26, 172, 9
+181, 13, 192, 10, 101, 13, 49, 8, 64, 12, 86, 19, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 207, 0, 180, 46
+ 0, 0, 253, 255, 6, 0, 233, 6, 12, 6, 141, 7, 111, 4, 37, 6
+ 9, 10, 48, 3, 55, 4, 0, 0, 79, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 70, 0, 112, 47, 191, 14, 187, 27, 234, 10
+187, 14, 220, 12, 99, 14, 25, 9, 141, 12, 178, 20, 192, 6, 245, 10
+193, 6, 6, 13, 86, 5, 114, 6, 211, 5, 37, 7, 50, 4, 216, 5
+126, 9, 13, 3, 11, 4, 0, 0, 10, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 71, 0, 242, 45, 119, 16, 57, 28, 227, 11
+122, 15, 45, 11, 98, 14, 41, 9, 222, 12, 231, 21, 34, 5, 111, 9
+220, 6, 167, 12, 86, 5, 129, 6, 137, 5, 209, 6, 50, 4, 214, 5
+105, 9, 231, 2, 229, 3, 0, 0, 234, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 72, 0, 88, 46, 76, 19, 144, 33, 215, 13
+226, 17, 135, 13, 248, 16, 236, 255, 237, 255, 246, 255, 30, 6, 156, 11
+ 41, 7, 81, 13, 143, 5, 203, 6, 222, 5, 54, 7, 84, 4, 30, 6
+221, 9, 255, 2, 20, 4, 0, 0, 40, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 73, 0, 188, 46, 77, 19, 127, 34, 252, 13
+ 32, 18, 110, 14, 58, 17, 86, 11, 215, 15, 56, 26, 31, 7, 217, 12
+ 50, 7, 143, 13, 162, 5, 227, 6, 16, 6, 97, 7, 104, 4, 51, 6
+ 1, 10, 38, 3, 40, 4, 0, 0, 55, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 74, 0, 31, 47, 173, 17, 75, 32, 80, 13
+ 3, 17, 86, 14, 122, 16, 211, 10, 207, 14, 69, 24, 59, 7, 233, 12
+ 43, 7, 163, 13, 156, 5, 218, 6, 56, 6, 123, 7, 96, 4, 48, 6
+255, 9, 74, 3, 58, 4, 0, 0, 107, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 75, 0, 131, 47, 235, 13, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 190, 0, 187, 46
+ 4, 0, 253, 255, 2, 0, 140, 8, 117, 12, 195, 20, 197, 4, 192, 8
+124, 6, 22, 12, 253, 4, 70, 6, 62, 5, 150, 6, 221, 3, 156, 5
+ 12, 9, 185, 2, 165, 3, 0, 0, 161, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 77, 0, 61, 46, 31, 18, 152, 32, 202, 13
+179, 16, 228, 12, 187, 16, 217, 10, 58, 15, 219, 24, 209, 5, 83, 11
+ 12, 7, 46, 13, 111, 5, 218, 6, 195, 5, 80, 7, 60, 4, 19, 6
+225, 9, 7, 3, 7, 4, 0, 0, 49, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 78, 0, 161, 46, 50, 19, 53, 35, 230, 14
+216, 17, 77, 14, 171, 17, 145, 11, 228, 16, 156, 26, 251, 6, 217, 12
+ 55, 7, 148, 13, 141, 5, 12, 7, 237, 5, 159, 7, 92, 4, 71, 6
+ 37, 10, 39, 3, 51, 4, 0, 0, 69, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 238, 255, 242, 255, 250, 255, 129, 32, 133, 13
+186, 16, 6, 14, 104, 16, 3, 11, 209, 15, 129, 24, 56, 7, 125, 12
+241, 6, 82, 13, 118, 5, 201, 6, 238, 5, 115, 7, 75, 4, 35, 6
+228, 9, 33, 3, 45, 4, 0, 0, 70, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 80, 0, 103, 47, 229, 14, 230, 27, 6, 12
+ 54, 14, 133, 12, 81, 14, 233, 9, 126, 13, 212, 20, 223, 6, 54, 11
+189, 6, 3, 13, 83, 5, 125, 6, 228, 5, 44, 7, 50, 4, 247, 5
+151, 9, 34, 3, 37, 4, 0, 0, 67, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 81, 0, 217, 45, 253, 14, 31, 26, 226, 10
+202, 13, 12, 10, 112, 13, 157, 8, 122, 12, 106, 20, 246, 4, 231, 8
+119, 6, 7, 12, 1, 5, 78, 6, 44, 5, 209, 6, 235, 3, 116, 5
+ 19, 9, 199, 2, 208, 3, 0, 0, 211, 7, 32, 10, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 191, 0, 180, 46
+ 1, 0, 255, 255, 4, 0, 83, 0, 161, 46, 22, 19, 255, 34, 221, 13
+ 89, 18, 198, 13, 5, 18, 126, 11, 88, 17, 141, 26, 94, 7, 183, 12
+ 42, 7, 200, 13, 150, 5, 25, 7, 252, 5, 245, 7, 99, 4, 43, 6
+ 61, 10, 66, 3, 99, 4, 0, 0, 146, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 84, 0, 4, 47, 199, 17, 143, 32, 18, 13
+ 70, 17, 60, 13, 184, 16, 225, 10, 87, 16, 204, 24, 72, 7, 175, 12
+239, 6, 120, 13, 123, 5, 214, 6, 4, 6, 195, 7, 89, 4, 2, 6
+245, 9, 57, 3, 79, 4, 0, 0, 115, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 85, 0, 103, 47, 91, 14, 246, 26, 90, 11
+230, 13, 227, 11, 149, 13, 61, 9, 72, 13, 50, 20, 194, 6, 170, 10
+115, 6, 166, 12, 36, 5, 70, 6, 194, 5, 64, 7, 13, 4, 150, 5
+ 70, 9, 1, 3, 16, 4, 235, 255, 238, 255, 242, 255, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 86, 0, 238, 45, 25, 15, 48, 27, 11, 11
+111, 14, 231, 10, 69, 14, 161, 8, 251, 12, 216, 20, 111, 5, 46, 9
+145, 6, 107, 12, 30, 5, 100, 6, 128, 5, 44, 7, 237, 3, 104, 5
+ 85, 9, 221, 2, 248, 3, 0, 0, 14, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 87, 0, 82, 46, 128, 17, 230, 31, 193, 12
+254, 16, 235, 12, 106, 16, 56, 10, 174, 15, 124, 24, 209, 6, 196, 11
+224, 6, 30, 13, 97, 5, 193, 6, 219, 5, 171, 7, 45, 4, 173, 5
+210, 9, 28, 3, 61, 4, 0, 0, 96, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 88, 0, 183, 46, 58, 18, 149, 33, 118, 13
+172, 17, 233, 13, 196, 16, 28, 11, 152, 16, 132, 25, 109, 7, 182, 12
+243, 6, 114, 13, 113, 5, 214, 6, 3, 6, 219, 7, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 0, 197, 46
+ 3, 0, 253, 255, 255, 255, 0, 0, 173, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 90, 0, 124, 47, 130, 13, 170, 25, 146, 10
+128, 13, 166, 11, 11, 13, 252, 8, 160, 12, 22, 19, 223, 6, 105, 10
+108, 6, 127, 12, 27, 5, 61, 6, 210, 5, 71, 7, 5, 4, 93, 5
+ 35, 9, 33, 3, 43, 4, 0, 0, 87, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 91, 0, 229, 45, 168, 14, 80, 26, 174, 10
+ 29, 14, 152, 10, 169, 13, 141, 8, 226, 12, 52, 20, 239, 4, 205, 8
+ 86, 6, 237, 11, 239, 4, 47, 6, 100, 5, 4, 7, 223, 3, 54, 5
+242, 8, 187, 2, 181, 3, 0, 0, 227, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 92, 0, 77, 46, 231, 17, 107, 32, 232, 12
+ 62, 17, 44, 13, 104, 16, 137, 10, 248, 15, 167, 24, 110, 6, 94, 11
+240, 6, 30, 13, 102, 5, 235, 255, 235, 255, 247, 255, 63, 4, 189, 5
+210, 9, 25, 3, 26, 4, 0, 0, 146, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 93, 0, 177, 46, 134, 18, 92, 34, 218, 13
+ 54, 18, 121, 14, 90, 17, 133, 11, 217, 16, 2, 26, 55, 7, 136, 12
+ 24, 7, 144, 13, 138, 5, 5, 7, 68, 6, 235, 7, 116, 4, 227, 5
+ 22, 10, 51, 3, 61, 4, 0, 0, 176, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 94, 0, 20, 47, 199, 16, 182, 31, 215, 12
+108, 16, 26, 14, 111, 15, 143, 10, 170, 15, 158, 23, 75, 7, 229, 11
+186, 6, 27, 13, 103, 5, 162, 6, 57, 6, 143, 7, 75, 4, 186, 5
+175, 9, 33, 3, 40, 4, 0, 0, 151, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 95, 0, 118, 47, 253, 13, 2, 27, 71, 11
+192, 13, 183, 12, 96, 13, 108, 9, 250, 12, 199, 19, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 193, 0, 191, 46
+ 1, 0, 249, 255, 254, 255, 62, 6, 113, 5, 216, 6, 251, 3, 135, 5
+ 29, 9, 192, 2, 206, 3, 0, 0, 218, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 97, 0, 64, 46, 161, 17, 192, 31, 138, 13
+252, 15, 187, 12, 253, 15, 140, 10, 65, 15, 51, 24, 29, 6, 147, 10
+200, 6, 205, 12, 106, 5, 121, 6, 196, 5, 44, 7, 62, 4, 207, 5
+144, 9, 244, 2, 250, 3, 0, 0, 22, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 98, 0, 165, 46, 6, 19, 199, 34, 204, 14
+165, 17, 121, 14, 188, 17, 178, 11, 131, 16, 83, 26, 54, 7, 66, 12
+ 44, 7, 137, 13, 202, 5, 219, 6, 60, 6, 154, 7, 142, 4, 35, 6
+ 27, 10, 57, 3, 85, 4, 0, 0, 121, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 99, 0, 9, 47, 196, 17, 227, 32, 249, 13
+191, 16, 253, 13, 120, 16, 228, 255, 233, 255, 243, 255, 144, 7, 30, 12
+ 14, 7, 81, 13, 173, 5, 182, 6, 53, 6, 116, 7, 115, 4, 4, 6
+227, 9, 63, 3, 82, 4, 0, 0, 84, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 100, 0, 107, 47, 75, 14, 160, 27, 127, 11
+247, 13, 12, 12, 167, 13, 92, 9, 72, 13, 143, 20, 154, 6, 23, 10
+135, 6, 122, 12, 50, 5, 65, 6, 207, 5, 239, 6, 40, 4, 188, 5
+ 64, 9, 4, 3, 8, 4, 0, 0, 1, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 101, 0, 241, 45, 247, 15, 158, 28, 34, 12
+168, 14, 97, 11, 69, 14, 0, 9, 181, 13, 26, 22, 51, 5, 245, 8
+183, 6, 94, 12, 29, 5, 111, 6, 88, 5, 189, 6, 249, 3, 239, 5
+ 83, 9, 222, 2, 232, 3, 0, 0, 238, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 102, 0, 89, 46, 152, 18, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 194, 0, 199, 46
+254, 255, 248, 255, 254, 255, 100, 11, 165, 16, 253, 25, 124, 7, 87, 12
+ 21, 7, 79, 13, 131, 5, 187, 6, 227, 5, 72, 7, 77, 4, 74, 6
+228, 9, 31, 3, 62, 4, 0, 0, 38, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 104, 0, 32, 47, 135, 17, 159, 32, 169, 13
+108, 16, 177, 14, 194, 15, 168, 10, 167, 15, 104, 24, 146, 7, 212, 11
+ 18, 7, 94, 13, 121, 5, 181, 6, 249, 5, 78, 7, 78, 4, 78, 6
+229, 9, 61, 3, 79, 4, 0, 0, 91, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 105, 0, 131, 47, 18, 14, 157, 26, 49, 11
+ 52, 13, 56, 12, 236, 12, 163, 8, 178, 12, 199, 19, 153, 6, 205, 9
+159, 6, 138, 12, 34, 5, 59, 6, 160, 5, 209, 6, 3, 4, 234, 5
+ 75, 9, 14, 3, 9, 4, 0, 0, 37, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 212, 255, 230, 255, 226, 255, 119, 25, 88, 11
+229, 12, 118, 10, 135, 12, 47, 8, 252, 11, 139, 19, 151, 4, 196, 7
+ 97, 6, 189, 11, 230, 4, 15, 6, 34, 5, 71, 6, 190, 3, 166, 5
+218, 8, 169, 2, 169, 3, 0, 0, 114, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 107, 0, 50, 46, 253, 17, 57, 31, 203, 13
+ 65, 16, 19, 13, 166, 15, 79, 10, 212, 14, 245, 23, 14, 6, 115, 10
+239, 6, 227, 12, 111, 5, 163, 6, 174, 5, 10, 7, 54, 4, 26, 6
+175, 9, 5, 3, 24, 4, 0, 0, 5, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 108, 0, 149, 46, 47, 19, 113, 34, 148, 14
+133, 17, 38, 14, 41, 17, 121, 11, 142, 16, 237, 25, 47, 7, 83, 12
+ 42, 7, 123, 13, 142, 5, 224, 6, 222, 5, 120, 7, 101, 4, 85, 6
+ 10, 10, 50, 3, 89, 4, 0, 0, 45, 8, 32, 10, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 195, 0, 199, 46
+ 1, 0, 252, 255, 252, 255, 110, 0, 92, 47, 154, 15, 175, 28, 5, 12
+150, 14, 255, 12, 110, 14, 156, 9, 177, 13, 73, 21, 71, 7, 255, 10
+195, 6, 7, 13, 80, 5, 126, 6, 200, 5, 70, 7, 46, 4, 243, 5
+158, 9, 46, 3, 53, 4, 0, 0, 42, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 111, 0, 229, 45, 201, 15, 158, 27, 215, 11
+ 74, 14, 10, 11, 245, 13, 35, 9, 194, 12, 83, 21, 27, 5, 5, 9
+133, 6, 78, 12, 43, 5, 72, 6, 68, 5, 192, 6, 14, 4, 169, 5
+ 64, 9, 214, 2, 237, 3, 0, 0, 229, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 112, 0, 73, 46, 5, 18, 192, 31, 90, 13
+199, 16, 201, 12, 72, 16, 148, 10, 33, 15, 86, 24, 34, 6, 49, 11
+183, 6, 217, 12, 93, 5, 134, 6, 121, 5, 41, 7, 52, 4, 220, 5
+157, 9, 248, 2, 25, 4, 217, 255, 217, 255, 225, 255, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 113, 0, 174, 46, 60, 19, 54, 34, 83, 14
+ 74, 18, 38, 14, 183, 17, 142, 11, 100, 16, 73, 26, 249, 6, 106, 12
+ 10, 7, 170, 13, 168, 5, 236, 6, 242, 5, 184, 7, 117, 4, 32, 6
+ 37, 10, 45, 3, 79, 4, 0, 0, 95, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 114, 0, 17, 47, 79, 17, 177, 31, 49, 13
+172, 16, 174, 13, 84, 16, 214, 10, 148, 15, 78, 24, 76, 7, 17, 12
+229, 6, 151, 13, 150, 5, 184, 6, 1, 6, 152, 7, 115, 4, 18, 6
+254, 9, 51, 3, 77, 4, 0, 0, 102, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 115, 0, 117, 47, 215, 13, 250, 25, 207, 10
+ 83, 13, 170, 11, 75, 13, 249, 8, 138, 12, 175, 19, 152, 6, 1, 10
+100, 6, 148, 12, 35, 5, 41, 6, 137, 5, 248, 6, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 196, 0, 192, 46
+ 0, 0, 252, 255, 250, 255, 0, 0, 200, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 117, 0, 74, 46, 239, 18, 113, 32, 90, 13
+223, 17, 110, 12, 34, 17, 243, 10, 197, 15, 167, 25, 57, 6, 92, 11
+239, 6, 51, 13, 113, 5, 189, 6, 190, 5, 81, 7, 95, 4, 250, 5
+217, 9, 19, 3, 51, 4, 0, 0, 65, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 118, 0, 175, 46, 37, 19, 235, 33, 169, 13
+103, 18, 49, 13, 159, 17, 152, 11, 202, 16, 136, 26, 248, 6, 137, 12
+242, 6, 81, 13, 124, 5, 197, 6, 204, 5, 126, 7, 121, 4, 7, 6
+222, 9, 40, 3, 79, 4, 0, 0, 53, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 119, 0, 19, 47, 139, 17, 38, 32, 16, 13
+ 44, 17, 140, 13, 164, 16, 248, 10, 211, 15, 201, 24, 92, 7, 95, 12
+238, 6, 107, 13, 140, 5, 212, 255, 212, 255, 220, 255, 129, 4, 13, 6
+221, 9, 64, 3, 97, 4, 0, 0, 95, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 120, 0, 117, 47, 52, 14, 161, 26, 224, 10
+ 17, 14, 190, 11, 190, 13, 110, 9, 20, 13, 86, 20, 156, 6, 85, 10
+144, 6, 213, 12, 65, 5, 103, 6, 197, 5, 36, 7, 50, 4, 202, 5
+104, 9, 36, 3, 48, 4, 0, 0, 49, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 121, 0, 224, 45, 54, 15, 179, 26, 31, 11
+104, 14, 46, 10, 178, 13, 3, 9, 10, 13, 246, 20, 212, 4, 237, 8
+104, 6, 25, 12, 3, 5, 57, 6, 25, 5, 179, 6, 10, 4, 141, 5
+ 13, 9, 190, 2, 214, 3, 0, 0, 168, 7, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 122, 0, 68, 46, 116, 17, 200, 31, 45, 13
+195, 16, 189, 12, 133, 16, 167, 10, 158, 15, 109, 24, 2, 0, 0, 0
+ 9, 20, 1, 3, 9, 20, 1, 3, 132, 1, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 197, 0, 3, 13
+ 42, 7, 183, 13, 168, 5, 252, 6, 239, 5, 159, 7, 137, 4, 75, 6
+ 40, 10, 66, 3, 103, 4, 0, 0, 132, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 124, 0, 12, 47, 212, 16, 238, 31, 40, 13
+197, 16, 175, 13, 68, 16, 200, 10, 251, 15, 105, 24, 128, 7, 156, 12
+225, 6, 68, 13, 126, 5, 169, 6, 206, 5, 81, 7, 105, 4, 17, 6
+209, 9, 59, 3, 54, 4, 0, 0, 91, 8, 33, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 125, 0, 110, 47, 82, 14, 37, 27, 47, 11
+ 18, 14, 222, 11, 157, 13, 66, 9, 153, 13, 196, 20, 243, 6, 213, 10
+171, 6, 216, 12, 81, 5, 100, 6, 175, 5, 19, 7, 54, 4, 225, 5
+119, 9, 48, 3, 26, 4, 0, 0, 79, 8, 32, 10, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 68, 70, 68, 68, 41, 19, 41, 19, 255, 127, 52, 0, 50, 0, 255, 127
+255, 127, 32, 0, 255, 127, 28, 0, 24, 0, 15, 0, 26, 0, 255, 127
+ 24, 0, 23, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 58, 0, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 38, 0, 38, 0, 43, 0
+ 41, 0, 38, 0, 26, 0, 30, 0, 25, 0, 42, 0, 6, 0, 37, 0
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 52, 0, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 31, 0, 47, 0
+ 22, 0, 255, 127, 255, 127, 255, 127, 255, 127, 19, 0, 11, 0, 39, 0
+ 36, 0, 15, 0, 253, 255, 42, 0, 4, 0, 1, 0, 255, 127, 255, 127
+ 20, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 34, 0
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 20, 0, 14, 0, 255, 127, 255, 127, 255, 127
+ 21, 0, 17, 0, 29, 0, 15, 0, 30, 0, 57, 0, 255, 127, 15, 0
+ 26, 0, 31, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+ 42, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 12, 0, 7, 0, 38, 0, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 20, 0, 12, 0, 10, 0
+ 20, 0, 5, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+ 61, 0, 59, 0, 230, 255, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 19, 0, 43, 0, 8, 0, 2, 0, 33, 0, 7, 0
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 27, 0, 14, 0
+ 23, 0, 16, 0, 20, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 40, 0, 15, 0, 213, 255, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 9, 0, 8, 0, 31, 0, 10, 0, 19, 0
+ 14, 0, 6, 0, 13, 0, 13, 0, 255, 127, 255, 127, 245, 255, 255, 127
+ 15, 0, 27, 0, 7, 0, 13, 0, 215, 255, 255, 255, 12, 0, 12, 0
+ 7, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 46, 0, 17, 0, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 15, 0, 16, 0, 5, 0, 2, 0
+ 19, 0, 10, 0, 3, 0, 6, 0, 14, 0, 255, 127, 255, 127, 255, 127
+ 6, 0, 255, 127, 8, 0, 5, 0, 16, 0, 4, 0, 255, 255, 12, 0
+ 1, 0, 3, 0, 253, 255, 1, 0, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 58, 0, 42, 0, 11, 0, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 254, 255, 6, 0
+ 11, 0, 12, 0, 11, 0, 15, 0, 7, 0, 255, 127, 21, 0, 19, 0
+255, 127, 255, 127, 255, 127, 10, 0, 17, 0, 6, 0, 5, 0, 6, 0
+ 4, 0, 249, 255, 1, 0, 6, 0, 254, 255, 5, 0, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 65, 0, 26, 0, 247, 255, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+ 11, 0, 6, 0, 4, 0, 14, 0, 10, 0, 12, 0, 12, 0, 9, 0
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 13, 0, 21, 0, 9, 0
+ 1, 0, 251, 255, 255, 255, 2, 0, 6, 0, 254, 255, 1, 0, 1, 0
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 253, 255, 54, 0
+253, 255, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 5, 0, 6, 0, 6, 0, 4, 0, 6, 0
+ 7, 0, 3, 0, 255, 127, 255, 127, 255, 127, 255, 127, 27, 0, 21, 0
+ 8, 0, 8, 0, 253, 255, 3, 0, 255, 255, 254, 255, 2, 0, 11, 0
+ 7, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+ 25, 0, 0, 0, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 14, 0, 253, 255, 248, 255
+250, 255, 6, 0, 10, 0, 255, 127, 2, 0, 22, 0, 255, 127, 255, 127
+255, 127, 10, 0, 13, 0, 3, 0, 249, 255, 5, 0, 4, 0, 1, 0
+244, 255, 248, 255, 248, 255, 204, 255, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 233, 255, 30, 0, 8, 0, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 210, 255, 255, 127, 255, 127, 255, 127, 3, 0
+ 2, 0, 246, 255, 0, 0, 250, 255, 255, 255, 237, 255, 14, 0, 255, 127
+255, 127, 246, 255, 5, 0, 4, 0, 0, 0, 253, 255, 5, 0, 255, 255
+ 3, 0, 246, 255, 245, 255, 255, 127, 237, 255, 245, 255, 240, 255, 255, 127
+255, 127, 255, 127, 229, 255, 2, 0, 11, 0, 25, 0, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+240, 255, 1, 0, 0, 0, 9, 0, 245, 255, 247, 255, 12, 0, 241, 255
+255, 127, 255, 127, 255, 127, 252, 255, 5, 0, 243, 255, 252, 255, 1, 0
+251, 255, 242, 255, 249, 255, 252, 255, 11, 0, 252, 255, 247, 255, 248, 255
+248, 255, 239, 255, 255, 127, 255, 127, 242, 255, 57, 0, 246, 255, 245, 255
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 247, 255, 248, 255, 241, 255, 248, 255, 240, 255
+251, 255, 255, 127, 255, 127, 235, 255, 250, 255, 20, 0, 2, 0, 241, 255
+246, 255, 245, 255, 249, 255, 248, 255, 241, 255, 251, 255, 243, 255, 242, 255
+245, 255, 251, 255, 234, 255, 244, 255, 231, 255, 239, 255, 236, 255, 13, 0
+ 0, 0, 209, 255, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 241, 255, 245, 255, 235, 255
+236, 255, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 240, 255, 237, 255, 244, 255, 242, 255, 235, 255, 231, 255, 241, 255
+228, 255, 229, 255, 239, 255, 237, 255, 235, 255, 234, 255, 237, 255, 224, 255
+ 0, 0, 252, 255, 245, 255, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 244, 255, 245, 255, 234, 255
+238, 255, 242, 255, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 239, 255, 243, 255, 245, 255, 242, 255
+249, 255, 249, 255, 246, 255, 243, 255, 232, 255, 231, 255, 239, 255, 237, 255
+198, 255, 226, 255, 245, 255, 242, 255, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 229, 255, 233, 255, 219, 255, 227, 255
+227, 255, 246, 255, 255, 127, 255, 127, 239, 255, 214, 255, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 238, 255, 236, 255, 237, 255, 249, 255, 243, 255
+235, 255, 238, 255, 241, 255, 249, 255, 235, 255, 245, 255, 245, 255, 234, 255
+236, 255, 239, 255, 246, 255, 227, 255, 242, 255, 241, 255, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 255, 127, 255, 127, 236, 255, 233, 255, 235, 255
+230, 255, 227, 255, 231, 255, 237, 255, 255, 127, 255, 127, 255, 127, 255, 127
+255, 127, 255, 127, 255, 127, 3, 0, 255, 127, 229, 255, 241, 255, 244, 255
+247, 255, 241, 255, 236, 255, 241, 255, 239, 255, 238, 255, 239, 255, 241, 255
+247, 255, 241, 255, 244, 255, 251, 255, 240, 255, 247, 255, 250, 255, 0, 32
+120, 1, 176, 0, 128, 0, 192, 0, 56, 1, 181, 22, 234, 255, 255, 21
+107, 173, 181, 20, 95, 169, 181, 22, 233, 255, 255, 63, 191, 53, 132, 18
+255, 255, 255, 63, 107, 173, 181, 62, 106, 173, 181, 22, 234, 255, 255, 21
+107, 173, 181, 20, 95, 169, 181, 22, 233, 255, 255, 63, 191, 53, 132, 18
+255, 255, 255, 63, 108, 173, 148, 62, 107, 45, 198, 24, 233, 255, 255, 23
+107, 45, 165, 18, 108, 173, 181, 22, 255, 255, 255, 25, 255, 255, 148, 62
+255, 255, 255, 63, 108, 173, 148, 62, 107, 45, 198, 24, 233, 255, 255, 23
+107, 45, 165, 18, 108, 173, 181, 22, 255, 255, 255, 25, 255, 255, 148, 62
+255, 255, 255, 63, 235, 255, 255, 63, 107, 173, 181, 22, 63, 37, 165, 22
+107, 45, 165, 62, 107, 173, 181, 22, 255, 255, 255, 23, 63, 165, 148, 62
+255, 255, 255, 63, 235, 255, 255, 63, 107, 173, 181, 22, 63, 37, 165, 22
+107, 45, 165, 62, 107, 173, 181, 22, 255, 255, 255, 23, 63, 165, 148, 62
+255, 255, 255, 63, 255, 255, 255, 63, 108, 45, 165, 62, 95, 169, 181, 24
+107, 173, 255, 63, 107, 173, 181, 22, 255, 255, 214, 22, 95, 169, 148, 62
+ 95, 169, 255, 63, 255, 255, 255, 63, 108, 45, 165, 62, 95, 169, 181, 24
+107, 173, 255, 63, 107, 173, 181, 22, 255, 255, 214, 22, 95, 169, 148, 62
+ 95, 169, 255, 63, 107, 173, 181, 62, 76, 169, 148, 22, 95, 169, 181, 24
+ 76, 169, 255, 63, 107, 173, 181, 24, 255, 255, 255, 23, 127, 173, 181, 62
+ 95, 169, 255, 63, 107, 173, 181, 62, 76, 169, 148, 22, 95, 169, 181, 24
+ 76, 169, 255, 63, 107, 173, 181, 24, 255, 255, 255, 23, 127, 173, 181, 62
+ 95, 169, 255, 63, 107, 45, 198, 62, 108, 173, 181, 22, 106, 173, 181, 24
+235, 255, 255, 21, 107, 173, 181, 22, 223, 152, 181, 22, 223, 185, 255, 63
+191, 181, 255, 63, 107, 45, 198, 62, 108, 173, 181, 22, 106, 173, 181, 24
+235, 255, 255, 21, 107, 173, 181, 22, 223, 152, 181, 22, 223, 185, 255, 63
+191, 181, 255, 63, 75, 169, 255, 63, 107, 45, 165, 22, 107, 173, 181, 22
+255, 255, 255, 23, 107, 173, 181, 62, 95, 169, 181, 22, 237, 255, 255, 63
+255, 255, 255, 27, 75, 169, 255, 63, 107, 45, 165, 22, 107, 173, 181, 22
+255, 255, 255, 23, 107, 173, 181, 62, 95, 169, 181, 22, 237, 255, 255, 63
+255, 255, 255, 27, 255, 255, 255, 63, 255, 255, 255, 63, 127, 173, 181, 62
+255, 255, 255, 63, 107, 173, 255, 63, 107, 173, 181, 22, 41, 37, 198, 22
+255, 255, 115, 18, 255, 255, 255, 63, 255, 255, 255, 63, 127, 173, 181, 62
+255, 255, 255, 63, 107, 173, 255, 63, 107, 173, 181, 22, 41, 37, 198, 22
+255, 255, 115, 18, 255, 255, 255, 63, 255, 255, 255, 63, 127, 173, 214, 62
+255, 255, 255, 63, 235, 255, 255, 63, 108, 173, 181, 22, 234, 127, 198, 24
+ 63, 37, 165, 20, 255, 255, 255, 63, 255, 255, 255, 63, 127, 173, 214, 62
+255, 255, 255, 63, 235, 255, 255, 63, 108, 173, 181, 22, 234, 127, 198, 24
+ 63, 37, 165, 20, 255, 255, 255, 63, 255, 255, 255, 63, 127, 173, 181, 62
+255, 255, 255, 63, 255, 255, 255, 63, 108, 173, 181, 62, 141, 49, 198, 24
+ 95, 41, 165, 26, 255, 255, 255, 63, 255, 255, 255, 63, 127, 173, 181, 62
+255, 255, 255, 63, 255, 255, 255, 63, 108, 173, 181, 62, 141, 49, 198, 24
+ 95, 41, 165, 26, 255, 255, 255, 63, 255, 255, 255, 63, 139, 49, 198, 62
+255, 255, 255, 23, 255, 255, 255, 63, 108, 173, 181, 62, 140, 49, 198, 24
+ 95, 169, 181, 24, 255, 255, 255, 63, 255, 255, 255, 63, 139, 49, 198, 62
+255, 255, 255, 23, 255, 255, 255, 63, 108, 173, 181, 62, 140, 49, 198, 24
+ 95, 169, 181, 24, 255, 255, 255, 63, 255, 255, 255, 63, 255, 255, 255, 63
+255, 255, 255, 63, 255, 255, 255, 63, 255, 255, 255, 63, 255, 255, 255, 63
+255, 255, 255, 63, 15, 1, 0, 0, 87, 66, 67, 90, 8, 20, 20, 148
+131, 131, 131, 131, 131, 131, 131, 131, 8, 20, 20, 148, 148, 136, 148, 148
+131, 131, 131, 131, 8, 8, 136, 20, 148, 136, 148, 136, 148, 148, 131, 131
+ 8, 8, 8, 20, 148, 136, 148, 136, 148, 148, 131, 131, 8, 8, 8, 8
+ 20, 136, 136, 148, 148, 131, 131, 131, 8, 8, 8, 20, 3, 136, 136, 136
+148, 148, 131, 131, 8, 8, 8, 8, 8, 136, 136, 136, 148, 148, 148, 131
+ 8, 8, 8, 8, 8, 8, 136, 136, 148, 3, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 136, 131, 148, 131, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 136, 136, 148, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 131, 131
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 148, 148, 131, 31, 0, 115, 0
+166, 0, 157, 1, 17, 2, 9, 2, 5, 2, 34, 2, 6, 2, 21, 2
+ 50, 2, 19, 2, 17, 0, 88, 0, 143, 0, 106, 1, 109, 2, 19, 3
+154, 1, 219, 2, 13, 2, 16, 2, 43, 2, 15, 2, 22, 0, 39, 0
+ 73, 1, 53, 1, 60, 2, 116, 4, 205, 1, 165, 5, 148, 2, 217, 2
+ 58, 2, 51, 2, 35, 0, 27, 0, 66, 0, 44, 1, 26, 2, 142, 2
+ 13, 2, 205, 4, 130, 2, 105, 2, 121, 2, 110, 2, 36, 0, 45, 0
+ 49, 0, 130, 0, 36, 1, 98, 1, 193, 3, 50, 3, 11, 3, 173, 2
+194, 2, 184, 2, 33, 0, 44, 0, 29, 0, 116, 0, 36, 1, 143, 1
+204, 4, 217, 2, 181, 1, 227, 2, 204, 2, 194, 2, 24, 0, 26, 0
+ 20, 0, 76, 0, 232, 0, 207, 1, 39, 4, 78, 2, 137, 1, 165, 1
+ 90, 2, 183, 2, 12, 0, 20, 0, 28, 0, 140, 0, 205, 0, 37, 1
+144, 1, 44, 1, 2, 2, 73, 1, 131, 2, 143, 2, 7, 0, 22, 0
+ 35, 0, 69, 0, 186, 0, 166, 0, 126, 0, 217, 0, 14, 2, 146, 1
+141, 2, 156, 2, 40, 0, 24, 0, 5, 0, 44, 0, 103, 0, 191, 0
+105, 0, 136, 0, 179, 0, 116, 1, 60, 2, 92, 2, 38, 0, 34, 0
+ 13, 0, 25, 0, 77, 0, 209, 0, 128, 0, 111, 0, 33, 0, 233, 0
+ 97, 1, 135, 2, 6, 0, 31, 0, 24, 0, 27, 0, 68, 0, 118, 0
+110, 0, 159, 0, 113, 0, 199, 1, 215, 2, 167, 2, 84, 0, 1, 1
+ 96, 1, 125, 3, 19, 4, 0, 4, 2, 4, 52, 4, 251, 3, 26, 4
+ 86, 4, 24, 4, 53, 0, 204, 0, 73, 1, 32, 3, 56, 5, 45, 7
+134, 3, 115, 6, 42, 4, 23, 4, 75, 4, 22, 4, 62, 0, 108, 0
+ 47, 3, 140, 2, 177, 4, 182, 10, 212, 3, 218, 13, 153, 5, 99, 6
+113, 4, 95, 4, 89, 0, 78, 0, 168, 0, 169, 2, 159, 4, 37, 6
+ 94, 4, 78, 11, 75, 5, 34, 5, 235, 4, 210, 4, 92, 0, 120, 0
+129, 0, 63, 1, 110, 2, 130, 3, 80, 9, 93, 7, 201, 6, 166, 5
+125, 5, 104, 5, 89, 0, 114, 0, 85, 0, 9, 1, 90, 2, 233, 3
+ 10, 12, 1, 7, 208, 3, 88, 6, 229, 5, 123, 5, 69, 0, 74, 0
+ 61, 0, 196, 0, 49, 2, 147, 4, 118, 10, 189, 5, 137, 3, 175, 3
+ 72, 5, 145, 5, 41, 0, 63, 0, 81, 0, 109, 1, 9, 2, 216, 2
+213, 3, 245, 2, 169, 4, 184, 2, 88, 5, 118, 5, 28, 0, 57, 0
+ 98, 0, 183, 0, 222, 1, 162, 1, 55, 1, 55, 2, 7, 5, 92, 3
+120, 5, 87, 5, 107, 0, 64, 0, 25, 0, 120, 0, 9, 1, 245, 1
+ 17, 1, 97, 1, 210, 1, 112, 3, 65, 5, 13, 5, 99, 0, 86, 0
+ 42, 0, 72, 0, 203, 0, 44, 2, 73, 1, 30, 1, 95, 0, 30, 2
+247, 2, 46, 5, 28, 0, 78, 0, 69, 0, 83, 0, 185, 0, 41, 1
+ 33, 1, 146, 1, 46, 1, 17, 4, 88, 6, 79, 5, 23, 0, 99, 0
+143, 0, 93, 1, 225, 1, 217, 1, 217, 1, 253, 1, 224, 1, 243, 1
+ 24, 2, 249, 1, 10, 0, 78, 0, 116, 0, 40, 1, 44, 2, 118, 2
+107, 1, 144, 2, 235, 1, 251, 1, 30, 2, 2, 2, 17, 0, 26, 0
+250, 0, 23, 1, 7, 2, 114, 3, 169, 1, 55, 4, 58, 2, 105, 2
+ 57, 2, 48, 2, 25, 0, 19, 0, 48, 0, 13, 1, 216, 1, 0, 2
+245, 1, 161, 3, 46, 2, 62, 2, 131, 2, 115, 2, 27, 0, 31, 0
+ 34, 0, 99, 0, 12, 1, 68, 1, 59, 3, 149, 2, 135, 2, 144, 2
+216, 2, 201, 2, 22, 0, 28, 0, 20, 0, 105, 0, 16, 1, 74, 1
+189, 3, 15, 2, 123, 1, 134, 2, 175, 2, 226, 2, 16, 0, 16, 0
+ 13, 0, 61, 0, 204, 0, 128, 1, 106, 3, 149, 1, 92, 1, 109, 1
+ 16, 2, 183, 2, 8, 0, 13, 0, 20, 0, 105, 0, 150, 0, 221, 0
+ 47, 1, 195, 0, 182, 1, 60, 1, 122, 2, 119, 2, 6, 0, 21, 0
+ 25, 0, 45, 0, 133, 0, 122, 0, 85, 0, 155, 0, 131, 1, 158, 1
+104, 2, 177, 2, 35, 0, 19, 0, 3, 0, 29, 0, 68, 0, 134, 0
+ 68, 0, 96, 0, 125, 0, 56, 1, 199, 1, 59, 2, 35, 0, 27, 0
+ 9, 0, 19, 0, 57, 0, 136, 0, 82, 0, 69, 0, 23, 0, 201, 0
+ 96, 1, 162, 2, 4, 0, 24, 0, 16, 0, 19, 0, 51, 0, 78, 0
+ 74, 0, 109, 0, 84, 0, 152, 1, 164, 2, 220, 2, 8, 8, 8, 8
+ 8, 7, 8, 8, 8, 7, 21, 20, 20, 20, 8, 8, 20, 148, 148, 19
+148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 20, 20, 20, 8, 8, 8, 20, 20, 148
+148, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 21, 3, 3, 131, 3, 7, 8, 21, 149, 135, 8
+ 8, 136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 19, 131, 3, 7, 3, 3, 20, 19, 3, 3, 148, 148, 136
+136, 136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+ 7, 20, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 21, 3, 7, 8, 20, 8, 8, 3, 7, 20, 7, 3, 148
+148, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+ 8, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 3, 8, 8, 8, 8, 8, 8, 8, 136
+136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 20, 131, 131, 131
+ 8, 20, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 21, 21, 19, 3, 19, 20, 8, 8, 8, 136, 20, 8
+ 8, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 20, 20, 131, 131
+ 19, 131, 20, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 3, 20, 19, 148, 20, 131, 20, 8, 8, 148, 135, 8
+ 8, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 148
+148, 8, 8, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 7, 8, 8, 148, 8, 20, 8, 8, 8, 148, 7, 8
+136, 148, 131, 131, 131, 131, 131, 136, 136, 136, 136, 131, 131, 131, 20, 3
+131, 148, 8, 131, 149, 136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 13, 8, 8, 8, 8, 8, 20, 7, 135
+ 8, 21, 131, 131, 131, 131, 131, 136, 136, 136, 136, 136, 148, 131, 131, 131
+ 8, 8, 136, 136, 136, 136, 148, 131, 131, 131, 131, 131, 131, 131, 148, 131
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 6, 8, 8, 20, 7, 7, 8, 8
+148, 131, 131, 131, 131, 131, 131, 136, 136, 136, 136, 136, 136, 131, 131, 131
+ 8, 8, 136, 136, 136, 136, 136, 136, 136, 131, 131, 131, 131, 136, 136, 148
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 136, 136, 20, 8, 8, 8, 8, 131
+131, 131, 131, 131, 131, 131, 131, 136, 136, 136, 136, 136, 136, 131, 131, 131
+131, 131, 148, 148, 136, 136, 136, 136, 136, 131, 131, 131, 131, 136, 136, 136
+136, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 20, 20, 8, 7, 7, 148
+131, 131, 131, 131, 131, 131, 131, 131, 131, 135, 136, 136, 136, 131, 131, 131
+131, 131, 131, 131, 136, 136, 136, 136, 136, 131, 131, 131, 131, 136, 136, 136
+136, 135, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 136, 136, 136, 136, 7, 8, 8, 8, 7
+ 8, 131, 131, 131, 131, 131, 131, 131, 131, 148, 147, 136, 136, 135, 131, 131
+ 20, 20, 131, 131, 136, 136, 135, 135, 135, 131, 131, 131, 131, 131, 135, 136
+136, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 19, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 21, 8, 8, 8, 131, 8
+ 8, 131, 131, 131, 131, 131, 131, 131, 148, 131, 148, 136, 136, 135, 3, 20
+148, 131, 131, 148, 136, 136, 149, 131, 131, 131, 131, 131, 131, 131, 148, 131
+136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 20, 8
+ 19, 8, 8, 8, 8, 8, 8, 8, 20, 8, 136, 8, 8, 8, 20, 8
+131, 131, 131, 131, 131, 131, 131, 136, 136, 134, 148, 136, 136, 135, 21, 8
+ 6, 131, 131, 136, 136, 148, 148, 148, 136, 149, 131, 131, 131, 148, 131, 131
+136, 136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 3, 7
+ 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 7, 8, 8, 8, 8, 8
+131, 131, 131, 131, 131, 131, 131, 136, 136, 149, 136, 149, 148, 8, 3, 20
+131, 136, 136, 135, 148, 131, 148, 131, 136, 135, 131, 131, 20, 131, 148, 148
+136, 136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8
+148, 131, 131, 131, 136, 131, 148, 136, 136, 136, 136, 148, 136, 8, 131, 131
+131, 136, 148, 148, 148, 148, 136, 136, 136, 148, 131, 148, 136, 131, 131, 136
+136, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 131
+136, 131, 131, 131, 148, 131, 131, 136, 136, 136, 136, 136, 136, 136, 131, 131
+131, 136, 131, 136, 136, 131, 136, 135, 136, 136, 131, 131, 148, 131, 131, 131
+148, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+136, 136, 148, 131, 131, 131, 131, 136, 136, 136, 136, 136, 136, 136, 20, 131
+131, 131, 131, 131, 148, 147, 136, 135, 135, 136, 148, 131, 148, 131, 148, 136
+149, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8
+136, 136, 8, 131, 131, 131, 131, 136, 148, 135, 135, 7, 8, 13, 131, 131
+131, 131, 131, 131, 148, 148, 136, 136, 136, 136, 131, 131, 131, 131, 131, 135
+136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 8, 8, 8, 8
+136, 136, 20, 131, 8, 131, 8, 21, 3, 20, 8, 8, 8, 136, 149, 131
+131, 131, 148, 148, 148, 131, 148, 148, 135, 136, 131, 131, 131, 131, 131, 136
+ 8, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 20, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 20, 20, 8, 20, 8, 131, 131, 148, 131, 3, 8, 7, 8, 136, 136, 136
+136, 131, 131, 131, 148, 149, 136, 148, 131, 149, 148, 131, 131, 131, 131, 136
+149, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 20, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 148, 3, 19, 131, 131, 131, 148, 3, 8, 8, 8, 8, 136, 136
+136, 148, 131, 131, 148, 148, 136, 135, 131, 136, 136, 131, 131, 131, 148, 136
+136, 148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8
+ 8, 8, 8, 8, 8, 20, 3, 3, 8, 8, 8, 149, 8, 136, 8, 136
+136, 136, 131, 131, 148, 148, 136, 136, 136, 135, 135, 131, 131, 148, 136, 136
+136, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 7, 8
+ 8, 8, 8, 8, 3, 8, 3, 148, 8, 136, 8, 8, 136, 136, 136, 136
+136, 136, 148, 148, 148, 148, 148, 136, 136, 136, 136, 131, 131, 136, 136, 136
+148, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 20, 148, 20, 19, 3, 148, 131, 131, 20, 136, 135, 8, 136, 136, 136, 136
+148, 136, 136, 136, 136, 148, 136, 136, 148, 136, 135, 131, 135, 136, 148, 148
+131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 3, 3, 6, 3, 20, 131, 3, 3, 148, 7, 8, 136, 136, 136, 136
+148, 136, 136, 136, 136, 136, 136, 136, 131, 148, 149, 149, 148, 131, 131, 136
+131, 131, 131, 136, 136, 148, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 131, 131, 131, 20, 6, 131, 3, 20, 8, 8, 20, 136, 136, 136, 136
+135, 136, 136, 136, 136, 136, 136, 136, 148, 131, 148, 131, 148, 148, 8, 136
+131, 131, 131, 148, 136, 136, 148, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8
+ 8, 8, 20, 131, 8, 134, 131, 20, 20, 8, 135, 136, 136, 136, 136, 136
+148, 136, 136, 136, 136, 136, 136, 136, 148, 148, 148, 148, 131, 148, 8, 136
+131, 131, 131, 131, 136, 136, 131, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 21, 20, 3, 3, 3, 131, 131, 7, 20, 8, 8, 148, 136, 148, 136, 148
+135, 136, 136, 136, 136, 136, 136, 136, 148, 148, 136, 148, 131, 8, 8, 136
+136, 131, 148, 148, 148, 136, 136, 148, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 21, 7, 21, 131, 20, 20, 8, 8, 8, 136, 136, 136, 148, 136, 136
+135, 136, 136, 7, 136, 148, 136, 136, 136, 148, 148, 148, 147, 148, 136, 136
+136, 148, 131, 8, 131, 136, 148, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 3, 134, 148, 8, 8, 8, 8, 136, 136, 136, 136, 136, 136
+136, 136, 136, 136, 136, 136, 136, 136, 136, 136, 8, 7, 20, 19, 149, 136
+136, 136, 131, 135, 148, 136, 136, 136, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 148, 131, 131, 20, 8, 136, 8, 136, 136, 136, 136, 136, 136
+135, 136, 136, 136, 135, 136, 136, 136, 148, 136, 136, 8, 136, 131, 131, 148
+136, 148, 7, 8, 8, 148, 136, 136, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 7, 8, 8, 8, 8, 148, 136, 136, 8, 136, 148, 136, 136, 136
+136, 136, 136, 136, 8, 136, 136, 136, 136, 135, 148, 7, 3, 131, 131, 148
+148, 148, 8, 8, 148, 136, 136, 136, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 6, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 136, 136, 8, 136, 136, 136, 149, 136, 136
+135, 135, 136, 136, 136, 136, 136, 136, 136, 7, 21, 8, 8, 148, 131, 149
+131, 20, 8, 8, 131, 131, 136, 136, 131, 131, 136, 148, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 20, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 136, 136, 136
+136, 136, 136, 8, 8, 136, 8, 136, 136, 136, 131, 148, 136, 131, 136, 131
+131, 131, 8, 149, 8, 3, 136, 136, 131, 131, 136, 136, 8, 21, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 136, 8, 8, 8, 8, 20, 8, 8, 8, 8, 136, 136, 149, 136
+136, 136, 136, 136, 8, 136, 136, 136, 136, 136, 136, 131, 148, 148, 136, 131
+131, 131, 21, 131, 136, 6, 131, 131, 131, 131, 136, 135, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8
+ 8, 136, 20, 8, 8, 8, 8, 7, 8, 8, 131, 136, 136, 135, 136, 136
+148, 135, 136, 136, 8, 136, 136, 8, 136, 136, 136, 8, 148, 136, 148, 131
+131, 3, 8, 134, 148, 19, 131, 131, 131, 131, 136, 136, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 136, 8, 8, 8, 8, 8, 8, 8, 8, 20, 135, 136, 8, 7, 136
+ 8, 136, 7, 136, 8, 136, 136, 8, 136, 136, 8, 148, 148, 136, 136, 131
+131, 20, 20, 148, 3, 131, 131, 136, 136, 148, 135, 136, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 136, 8, 8, 136, 136, 8, 8, 136, 136, 8, 8, 8, 8, 136, 136
+ 8, 8, 8, 8, 8, 7, 8, 8, 8, 136, 136, 149, 131, 131, 136, 131
+131, 131, 148, 8, 6, 131, 131, 131, 148, 131, 136, 136, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 136, 8, 8, 8, 136, 136, 136, 8, 8, 8, 8, 8
+ 8, 8, 136, 8, 8, 8, 8, 135, 8, 136, 136, 148, 131, 148, 148, 136
+131, 3, 7, 21, 20, 131, 131, 131, 131, 131, 148, 136, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 7, 8, 136, 8, 8, 8
+ 8, 8, 8, 7, 8, 8, 136, 136, 8, 136, 136, 148, 131, 136, 8, 8
+ 20, 8, 8, 6, 6, 131, 131, 131, 131, 131, 131, 136, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 136, 136, 135, 8, 136
+ 8, 8, 8, 8, 8, 8, 8, 136, 136, 136, 136, 136, 148, 20, 8, 8
+ 8, 131, 8, 131, 131, 131, 131, 148, 148, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 7, 8, 8, 7, 8, 8, 136, 136, 8, 8, 8, 20, 8, 8, 8, 20
+ 8, 8, 8, 8, 8, 8, 8, 8, 136, 136, 136, 136, 136, 148, 136, 136
+ 8, 131, 131, 131, 131, 131, 131, 148, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 20, 8, 8, 136, 136, 8, 8, 8, 8, 8, 8, 7, 8, 8
+ 8, 135, 7, 8, 8, 8, 8, 8, 136, 136, 136, 136, 135, 148, 136, 131
+ 20, 8, 131, 131, 131, 131, 148, 131, 148, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 6, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 7, 8, 8, 8, 8, 8, 136, 8, 8, 8, 8, 8, 13, 8, 8
+ 8, 7, 13, 8, 6, 8, 8, 8, 136, 136, 136, 136, 135, 148, 136, 7
+ 8, 6, 131, 131, 131, 148, 131, 136, 136, 148, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 136, 8, 8, 8, 136, 7, 7, 8, 8, 8
+ 7, 8, 8, 8, 8, 8, 8, 8, 8, 136, 136, 8, 136, 136, 136, 8
+ 8, 148, 148, 131, 148, 131, 131, 136, 136, 136, 131, 131, 8, 8, 8, 8
+ 8, 20, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 136, 8, 7
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8
+136, 136, 131, 131, 148, 148, 136, 136, 136, 136, 148, 131, 8, 8, 8, 6
+ 21, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8, 8, 8, 8, 21
+ 8, 8, 8, 8, 8, 8, 8, 8, 136, 131, 8, 8, 8, 8, 8, 8
+ 8, 136, 135, 148, 8, 136, 136, 136, 136, 136, 136, 148, 8, 8, 8, 20
+ 13, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 136, 8, 136, 135, 8, 8, 8, 8, 8, 8
+ 8, 8, 135, 8, 8, 136, 136, 136, 135, 136, 136, 131, 8, 8, 8, 7
+ 8, 21, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 7, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8
+ 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 136, 8, 136, 8, 8, 8
+136, 20, 8, 136, 131, 148, 131, 131, 131, 136, 136, 131, 8, 20, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8, 20
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 20, 8, 8, 136, 136, 8
+ 8, 131, 20, 136, 148, 136, 135, 131, 131, 148, 136, 131, 8, 8, 8, 19
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 13
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8
+ 8, 3, 131, 21, 135, 136, 136, 148, 148, 131, 148, 131, 20, 21, 3, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 136, 8, 8, 136, 8, 8
+ 8, 8, 136, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 6, 148, 8, 8, 131, 148, 131, 131, 131, 148, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8, 8
+ 8, 7, 8, 136, 136, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 131, 149, 136, 8, 3, 131, 131, 131, 148, 136, 8, 8, 8, 8
+ 3, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 136, 136, 136
+ 8, 8, 8, 8, 136, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 6, 20, 3, 135, 20, 8, 8, 131, 3, 3, 148, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 21, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 136, 8, 7
+ 8, 8, 8, 8, 8, 136, 8, 8, 7, 7, 13, 8, 8, 8, 8, 8
+ 8, 8, 8, 136, 136, 21, 131, 131, 3, 8, 8, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 3, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 136, 8, 8, 8, 8
+ 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 147, 148, 131, 21, 131, 8, 6, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 21, 3, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 136
+ 3, 21, 148, 131, 3, 131, 136, 136, 6, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 7, 17, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 3, 136, 131, 3, 149, 136, 136, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 136, 8
+ 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 136, 8, 8, 8
+136, 8, 136, 131, 148, 136, 136, 131, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 136, 8, 8, 7, 8, 8, 8
+ 13, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+136, 136, 131, 131, 136, 135, 136, 135, 131, 131, 131, 131, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8
+ 7, 8, 8, 8, 8, 8, 8, 7, 8, 8, 8, 8, 8, 8, 8, 8
+ 8, 136, 148, 131, 148, 131, 149, 149, 131, 131, 131, 131, 71, 65, 77, 67
+ 11, 11, 0, 0, 16, 0, 32, 0, 40, 0, 48, 0, 56, 0, 64, 0
+ 72, 0, 80, 0, 88, 0, 96, 0, 112, 0, 128, 0, 160, 0, 192, 0
+224, 0, 32, 1, 96, 1, 160, 1, 224, 1, 96, 2, 224, 2, 96, 3
+224, 3, 224, 4, 224, 5, 224, 6, 224, 7, 224, 9, 224, 11, 224, 13
+224, 15, 224, 17, 224, 19, 224, 21, 224, 23, 224, 25, 224, 27, 224, 29
+224, 31, 224, 33, 224, 37, 224, 41, 224, 45, 224, 49, 224, 53, 224, 57
+255, 61, 255, 63, 0, 0, 56, 0, 79, 0, 88, 0, 97, 0, 105, 0
+113, 0, 120, 0, 126, 0, 132, 0, 138, 0, 149, 0, 159, 0, 178, 0
+195, 0, 211, 0, 239, 0, 8, 1, 31, 1, 52, 1, 91, 1, 126, 1
+158, 1, 187, 1, 241, 1, 32, 2, 69, 2, 100, 2, 151, 2, 189, 2
+221, 2, 249, 2, 16, 3, 37, 3, 56, 3, 73, 3, 89, 3, 103, 3
+117, 3, 129, 3, 141, 3, 162, 3, 181, 3, 198, 3, 214, 3, 229, 3
+242, 3, 255, 3, 255, 3, 0, 0, 56, 0, 79, 0, 88, 0, 97, 0
+105, 0, 113, 0, 120, 0, 126, 0, 132, 0, 138, 0, 149, 0, 159, 0
+178, 0, 195, 0, 211, 0, 239, 0, 8, 1, 31, 1, 52, 1, 91, 1
+126, 1, 158, 1, 187, 1, 241, 1, 32, 2, 69, 2, 100, 2, 151, 2
+189, 2, 221, 2, 249, 2, 16, 3, 37, 3, 56, 3, 73, 3, 89, 3
+103, 3, 117, 3, 129, 3, 141, 3, 162, 3, 181, 3, 198, 3, 214, 3
+229, 3, 242, 3, 255, 3, 255, 3
+TAG:ExifIFD/MakerNote/0x0024= 0
+TAG:ExifIFD/MakerNote/0x0025= 88, 74, 82, 50, 53, 48, 53, 49, 52, 48, 48, 57, 57, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0026= 48, 52, 50, 53
+TAG:ExifIFD/MakerNote/0x0028= 1
+TAG:ExifIFD/MakerNote/0x0029= 8455
+TAG:ExifIFD/MakerNote/0x002A= 0
+TAG:ExifIFD/MakerNote/0x002B= 0
+TAG:ExifIFD/MakerNote/0x002C= 63
+TAG:ExifIFD/MakerNote/0x002D= 0
+TAG:ExifIFD/MakerNote/0x002E= 0
+TAG:ExifIFD/MakerNote/0x002F= 1
+TAG:ExifIFD/MakerNote/0x0030= 1
+TAG:ExifIFD/MakerNote/0x0031= 2
+TAG:ExifIFD/MakerNote/0x0033=9999:99:99 00:00:00
+TAG:ExifIFD/MakerNote/0x0034= 1
+TAG:ExifIFD/MakerNote/0x0035= 1
+TAG:ExifIFD/MakerNote/0x0036= 65535
+TAG:ExifIFD/MakerNote/0x0037= 257
+TAG:ExifIFD/MakerNote/0x0038= 1
+TAG:ExifIFD/MakerNote/0x0039= 0
+TAG:ExifIFD/MakerNote/0x003A= 1
+TAG:ExifIFD/MakerNote/0x003B= 1
+TAG:ExifIFD/MakerNote/0x003D= 1
+TAG:ExifIFD/MakerNote/0x003E= 1
+TAG:ExifIFD/MakerNote/0x003F= 0
+TAG:ExifIFD/MakerNote/0x0040= 0
+TAG:ExifIFD/MakerNote/0x0041= 0
+TAG:ExifIFD/MakerNote/0x0043= 3
+TAG:ExifIFD/MakerNote/0x0044= 3900
+TAG:ExifIFD/MakerNote/0x0045= 0
+TAG:ExifIFD/MakerNote/0x0046= 0
+TAG:ExifIFD/MakerNote/0x0047= 0
+TAG:ExifIFD/MakerNote/0x0048= 0
+TAG:ExifIFD/MakerNote/0x0049= 2
+TAG:ExifIFD/MakerNote/0x004B= 6000
+TAG:ExifIFD/MakerNote/0x004C= 4000
+TAG:ExifIFD/MakerNote/0x004D= 512:1024 , 514:1024
+TAG:ExifIFD/MakerNote/0x004E= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x004F= 0
+TAG:ExifIFD/MakerNote/0x0051=LUMIX S 24-60/F2.8
+TAG:ExifIFD/MakerNote/0x0052=3562
+TAG:ExifIFD/MakerNote/0x0053=NO-ACCESSORY
+TAG:ExifIFD/MakerNote/0x0054=0000000
+TAG:ExifIFD/MakerNote/0x0055= 1
+TAG:ExifIFD/MakerNote/0x0058= 0, 1, 1, 0
+TAG:ExifIFD/MakerNote/0x005E= 0, 0, 0, 4
+TAG:ExifIFD/MakerNote/0x005F= 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0060= 0, 1, 0, 0
+TAG:ExifIFD/MakerNote/0x0061= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 57, 57, 57, 57, 58, 57, 57, 58, 57, 57, 32, 48, 48, 58, 48, 48
+ 58, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 57, 57, 57, 57, 58, 57, 57, 58, 57, 57, 32, 48, 48, 58, 48, 48
+ 58, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 57, 57, 57, 57, 58, 57, 57, 58, 57, 57, 32, 48, 48, 58, 48, 48
+ 58, 48, 48, 0
+TAG:ExifIFD/MakerNote/0x0063= 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0065= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0066= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0067= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0068= 0
+TAG:ExifIFD/MakerNote/0x0069= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x006A= 0
+TAG:ExifIFD/MakerNote/0x006B= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x006C= 0
+TAG:ExifIFD/MakerNote/0x006D= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x006E= 0
+TAG:ExifIFD/MakerNote/0x006F= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0070= 0
+TAG:ExifIFD/MakerNote/0x0071= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0072= 0
+TAG:ExifIFD/MakerNote/0x0073= 0
+TAG:ExifIFD/MakerNote/0x0074= 0
+TAG:ExifIFD/MakerNote/0x0075= 0
+TAG:ExifIFD/MakerNote/0x0076= 0
+TAG:ExifIFD/MakerNote/0x0077= 0
+TAG:ExifIFD/MakerNote/0x0078= 0
+TAG:ExifIFD/MakerNote/0x0079= 0
+TAG:ExifIFD/MakerNote/0x007A= 0
+TAG:ExifIFD/MakerNote/0x007C= 0
+TAG:ExifIFD/MakerNote/0x007D= 0
+TAG:ExifIFD/MakerNote/0x007F= 0
+TAG:ExifIFD/MakerNote/0x0080= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0081= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0082= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0083= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0084= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0085= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x0087= 65535
+TAG:ExifIFD/MakerNote/0x0089= 4097
+TAG:ExifIFD/MakerNote/0x008A= 1
+TAG:ExifIFD/MakerNote/0x008B= 0
+TAG:ExifIFD/MakerNote/0x008C= 123
+TAG:ExifIFD/MakerNote/0x008D= 103
+TAG:ExifIFD/MakerNote/0x008E= 231
+TAG:ExifIFD/MakerNote/0x008F= 0
+TAG:ExifIFD/MakerNote/0x0090= 376
+TAG:ExifIFD/MakerNote/0x0091= 517
+TAG:ExifIFD/MakerNote/0x0092= 0
+TAG:ExifIFD/MakerNote/0x0093= 0
+TAG:ExifIFD/MakerNote/0x0094= 0
+TAG:ExifIFD/MakerNote/0x0096= 0
+TAG:ExifIFD/MakerNote/0x0097= 0
+TAG:ExifIFD/MakerNote/0x0099= 0
+TAG:ExifIFD/MakerNote/0x009A= 1
+TAG:ExifIFD/MakerNote/0x009B= 100:100
+TAG:ExifIFD/MakerNote/0x009D= 0:128
+TAG:ExifIFD/MakerNote/0x009E= 0
+TAG:ExifIFD/MakerNote/0x009F= 2
+TAG:ExifIFD/MakerNote/0x00A0= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x00A1= 0:0
+TAG:ExifIFD/MakerNote/0x00A2= 0, 0
+TAG:ExifIFD/MakerNote/0x00A3= 0:0
+TAG:ExifIFD/MakerNote/0x00A4= 0
+TAG:ExifIFD/MakerNote/0x00A5= 0
+TAG:ExifIFD/MakerNote/0x00A6= 0
+TAG:ExifIFD/MakerNote/0x00AA= 0
+TAG:ExifIFD/MakerNote/0x00AB= 0
+TAG:ExifIFD/MakerNote/0x00AC= 0
+TAG:ExifIFD/MakerNote/0x00AD= 0, 0
+TAG:ExifIFD/MakerNote/0x00AF=2025:07:18 18:21:55
+TAG:ExifIFD/MakerNote/0x00B0= 59940
+TAG:ExifIFD/MakerNote/0x00B1= 0
+TAG:ExifIFD/MakerNote/0x00B2= 0
+TAG:ExifIFD/MakerNote/0x00B3= 1
+TAG:ExifIFD/MakerNote/0x00B4= 1
+TAG:ExifIFD/MakerNote/0x00B5= 0:100
+TAG:ExifIFD/MakerNote/0x00B6= 0, 0, 0, 4
+TAG:ExifIFD/MakerNote/0x00B7= 0
+TAG:ExifIFD/MakerNote/0x00B8= 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x00B9= 0
+TAG:ExifIFD/MakerNote/0x00BA= 0
+TAG:ExifIFD/MakerNote/0x00BB= 1
+TAG:ExifIFD/MakerNote/0x00BC= 1
+TAG:ExifIFD/MakerNote/0x00BD= 0
+TAG:ExifIFD/MakerNote/0x00BE= 1
+TAG:ExifIFD/MakerNote/0x00BF= 0, 0
+TAG:ExifIFD/MakerNote/0x00C0= 0
+TAG:ExifIFD/MakerNote/0x00C1= 0
+TAG:ExifIFD/MakerNote/0x00C2= 0
+TAG:ExifIFD/MakerNote/0x00C3= 0, 0
+TAG:ExifIFD/MakerNote/0x00C4= 65535
+TAG:ExifIFD/MakerNote/0x00C5= 16413
+TAG:ExifIFD/MakerNote/0x00C6= 600
+TAG:ExifIFD/MakerNote/0x00C7= 0
+TAG:ExifIFD/MakerNote/0x00C8= 5940
+TAG:ExifIFD/MakerNote/0x00C9= 0
+TAG:ExifIFD/MakerNote/0x00CA= 1
+TAG:ExifIFD/MakerNote/0x00CF= 0
+TAG:ExifIFD/MakerNote/0x00D1= 400
+TAG:ExifIFD/MakerNote/0x00D2= 0
+TAG:ExifIFD/MakerNote/0x00D4= 0
+TAG:ExifIFD/MakerNote/0x00D5= 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x00D6= 0:100
+TAG:ExifIFD/MakerNote/0x00D7= 0:100
+TAG:ExifIFD/MakerNote/0x00D8= 0:100
+TAG:ExifIFD/MakerNote/0x00D9= 0:100
+TAG:ExifIFD/MakerNote/0x00DA= 0:100
+TAG:ExifIFD/MakerNote/0x00DB= 0:100 , 0:100
+TAG:ExifIFD/MakerNote/0x00DC= 1
+TAG:ExifIFD/MakerNote/0x00DD= 0:100
+TAG:ExifIFD/MakerNote/0x00DE= 166:1024 , 249:1024
+TAG:ExifIFD/MakerNote/0x00DF= 0
+TAG:ExifIFD/MakerNote/0x00E0= 0
+TAG:ExifIFD/MakerNote/0x00E1= 1, 0, 0, 1
+TAG:ExifIFD/MakerNote/0x00E2= 1
+TAG:ExifIFD/MakerNote/0x00E3= 0
+TAG:ExifIFD/MakerNote/0x00E4= 16413
+TAG:ExifIFD/MakerNote/0x00E8= 400
+TAG:ExifIFD/MakerNote/0x00E9= 3
+TAG:ExifIFD/MakerNote/0x00EA= 0
+TAG:ExifIFD/MakerNote/0x00EB= 0
+TAG:ExifIFD/MakerNote/0x00EC= 2
+TAG:ExifIFD/MakerNote/0x00ED= 0
+TAG:ExifIFD/MakerNote/0x00EE= 0
+TAG:ExifIFD/MakerNote/0x00EF= 32768, 2, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x00F0= 1
+TAG:ExifIFD/MakerNote/0x00F1=
+TAG:ExifIFD/MakerNote/0x00F3= 0
+TAG:ExifIFD/MakerNote/0x00F4=
+TAG:ExifIFD/MakerNote/0x00F5= 0
+TAG:ExifIFD/MakerNote/0x00F6= 0
+TAG:ExifIFD/MakerNote/0x00F8= 1
+TAG:ExifIFD/MakerNote/0x00F9= 0
+TAG:ExifIFD/MakerNote/0x00FA= 0
+TAG:ExifIFD/MakerNote/0x00FB= 0
+TAG:ExifIFD/MakerNote/0x2003= 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255
+ 0, 0, 0, 0
+TAG:ExifIFD/MakerNote/0x8000= 48, 49, 54, 53
+TAG:ExifIFD/MakerNote/0x8001= 0
+TAG:ExifIFD/MakerNote/0x8002= 1
+TAG:ExifIFD/MakerNote/0x8003= 1
+TAG:ExifIFD/MakerNote/0x8004= 2059
+TAG:ExifIFD/MakerNote/0x8005= 1024
+TAG:ExifIFD/MakerNote/0x8006= 2118
+TAG:ExifIFD/MakerNote/0x8007= 1
+TAG:ExifIFD/MakerNote/0x8008= 1
+TAG:ExifIFD/MakerNote/0x8009= 1
+TAG:ExifIFD/MakerNote/0x8010=9999:99:99 00:00:00
+TAG:ExifIFD/SubSecTime=384
+TAG:ExifIFD/SubSecTimeOriginal=384
+TAG:ExifIFD/SubSecTimeDigitized=384
+TAG:ExifIFD/FlashpixVersion= 48, 49, 48, 48
+TAG:ExifIFD/ColorSpace= 65535
+TAG:ExifIFD/PixelXDimension= 1920
+TAG:ExifIFD/PixelYDimension= 1280
+TAG:ExifIFD/InteropIFD/GPSLatitudeRef=R03
+TAG:ExifIFD/InteropIFD/GPSLatitude= 48, 49, 48, 48
+TAG:ExifIFD/SensingMethod= 2
+TAG:ExifIFD/FileSource= 3
+TAG:ExifIFD/SceneType= 1
+TAG:ExifIFD/CustomRendered= 0
+TAG:ExifIFD/ExposureMode= 1
+TAG:ExifIFD/WhiteBalance= 0
+TAG:ExifIFD/DigitalZoomRatio= 0:10
+TAG:ExifIFD/FocalLengthIn35mmFilm= 187
+TAG:ExifIFD/SceneCaptureType= 0
+TAG:ExifIFD/GainControl= 2
+TAG:ExifIFD/Contrast= 0
+TAG:ExifIFD/Saturation= 0
+TAG:ExifIFD/Sharpness= 0
+TAG:ExifIFD/0xA431=WJ5EA003345
+TAG:ExifIFD/0xA434=LUMIX S 24-60/F2.8
+TAG:0x9006= 39936
+TAG:0x9007= 65536
+TAG:0x9008= 272401
+[SIDE_DATA]
+side_data_type=EXIF metadata
+size=30308
+[/SIDE_DATA]
+[SIDE_DATA]
+side_data_type=H.26[45] User Data Unregistered SEI message
+[/SIDE_DATA]
+[/FRAME]
[STREAM]
index=0
id=0x100
commit fa1938c5747fa3a91596e467ba0af624f367c5a9
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Thu Aug 21 20:03:03 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:41 2025 -0300
avcodec/decode: use ff_frame_new_side_data() to export Exif side data
Otherwise, the user requested priority of packet side data will be ignored.
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index 2877010682..fa76dba51c 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -2268,12 +2268,10 @@ static int attach_displaymatrix(AVCodecContext *avctx, AVFrame *frame, int orien
return ret;
}
-static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef *og)
+static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMetadata *ifd, AVBufferRef **pbuf)
{
const AVExifEntry *orient = NULL;
- AVFrameSideData *sd;
AVExifMetadata *cloned = NULL;
- AVBufferRef *written = NULL;
int ret;
for (size_t i = 0; i < ifd->count; i++) {
@@ -2305,25 +2303,21 @@ static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMe
if (ret < 0)
goto end;
- if (cloned || !og) {
- ret = av_exif_write(avctx, ifd, &written, AV_EXIF_TIFF_HEADER);
+ if (cloned || !*pbuf) {
+ av_buffer_unref(pbuf);
+ ret = av_exif_write(avctx, ifd, pbuf, AV_EXIF_TIFF_HEADER);
if (ret < 0)
goto end;
}
- sd = av_frame_new_side_data_from_buf(frame, AV_FRAME_DATA_EXIF, written ? written : og);
- if (!sd) {
- if (written)
- av_buffer_unref(&written);
- ret = AVERROR(ENOMEM);
+ ret = ff_frame_new_side_data_from_buf(avctx, frame, AV_FRAME_DATA_EXIF, pbuf);
+ if (ret < 0)
goto end;
- }
ret = 0;
end:
- if (og && written && ret >= 0)
- av_buffer_unref(&og); // as though we called new_side_data on og;
+ av_buffer_unref(pbuf);
av_exif_free(cloned);
av_free(cloned);
return ret;
@@ -2331,22 +2325,25 @@ end:
int ff_decode_exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMetadata *ifd)
{
- return exif_attach_ifd(avctx, frame, ifd, NULL);
+ AVBufferRef *dummy = NULL;
+ return exif_attach_ifd(avctx, frame, ifd, &dummy);
}
-int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef *data,
+int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef **pbuf,
enum AVExifHeaderMode header_mode)
{
int ret;
+ AVBufferRef *data = *pbuf;
AVExifMetadata ifd = { 0 };
ret = av_exif_parse_buffer(avctx, data->data, data->size, &ifd, header_mode);
if (ret < 0)
goto end;
- ret = exif_attach_ifd(avctx, frame, &ifd, data);
+ ret = exif_attach_ifd(avctx, frame, &ifd, pbuf);
end:
+ av_buffer_unref(pbuf);
av_exif_free(&ifd);
return ret;
}
diff --git a/libavcodec/decode.h b/libavcodec/decode.h
index f285da924d..561dc6a69c 100644
--- a/libavcodec/decode.h
+++ b/libavcodec/decode.h
@@ -230,11 +230,10 @@ enum AVExifHeaderMode;
* attaches that information as an AV_FRAME_DATA_DISPLAYMATRIX instead
* of including it in the AV_FRAME_DATA_EXIF side data buffer.
*
- * On a success, the caller loses ownership of the data buffer. Either it is
- * unrefed, or its ownership is transferred to the frame directly. On failure,
- * the data buffer is left owned by the caller.
+ * *buf is ALWAYS consumed by this function and NULL written in its place, even
+ * on failure.
*/
-int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef *data,
+int ff_decode_exif_attach_buffer(AVCodecContext *avctx, AVFrame *frame, AVBufferRef **buf,
enum AVExifHeaderMode header_mode);
struct AVExifMetadata;
diff --git a/libavcodec/pngdec.c b/libavcodec/pngdec.c
index 8a63f0ad90..ce817d44fa 100644
--- a/libavcodec/pngdec.c
+++ b/libavcodec/pngdec.c
@@ -1753,15 +1753,12 @@ exit_loop:
if (s->exif_data) {
// we swap because ff_decode_exif_attach_buffer adds to p->metadata
FFSWAP(AVDictionary *, p->metadata, s->frame_metadata);
- ret = ff_decode_exif_attach_buffer(avctx, p, s->exif_data, AV_EXIF_TIFF_HEADER);
+ ret = ff_decode_exif_attach_buffer(avctx, p, &s->exif_data, AV_EXIF_TIFF_HEADER);
FFSWAP(AVDictionary *, p->metadata, s->frame_metadata);
if (ret < 0) {
av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF buffer\n");
return ret;
}
- // ff_decode_exif_attach_buffer takes ownership so
- // we do not want to call av_buffer_unref here
- s->exif_data = NULL;
}
if (s->color_type == PNG_COLOR_TYPE_PALETTE && avctx->codec_id == AV_CODEC_ID_APNG) {
diff --git a/libavcodec/webp.c b/libavcodec/webp.c
index 5776c6d90c..62967a394d 100644
--- a/libavcodec/webp.c
+++ b/libavcodec/webp.c
@@ -1479,12 +1479,9 @@ FF_ENABLE_DEPRECATION_WARNINGS
s->has_exif = 1;
memcpy(exif_buf->data, gb.buffer, chunk_size);
- /* if this succeeds then exif_buf is either freed or transferred to the AVFrame */
- ret = ff_decode_exif_attach_buffer(avctx, p, exif_buf, AV_EXIF_TIFF_HEADER);
- if (ret < 0) {
+ ret = ff_decode_exif_attach_buffer(avctx, p, &exif_buf, AV_EXIF_TIFF_HEADER);
+ if (ret < 0)
av_log(avctx, AV_LOG_WARNING, "unable to attach EXIF buffer\n");
- av_buffer_unref(&exif_buf);
- }
exif_end:
bytestream2_skip(&gb, chunk_size);
commit 75bd5056a44119d98a8b4876c8d46065fbb81467
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Fri Aug 22 20:30:40 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:41 2025 -0300
avcodec/decode: use av_exif_get_tag_id() where useful
Removes dependency on exif_internal.h
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavcodec/decode.c b/libavcodec/decode.c
index b0ce94cadd..2877010682 100644
--- a/libavcodec/decode.c
+++ b/libavcodec/decode.c
@@ -47,7 +47,7 @@
#include "codec_desc.h"
#include "codec_internal.h"
#include "decode.h"
-#include "exif_internal.h"
+#include "exif.h"
#include "hwaccel_internal.h"
#include "hwconfig.h"
#include "internal.h"
@@ -2278,7 +2278,8 @@ static int exif_attach_ifd(AVCodecContext *avctx, AVFrame *frame, const AVExifMe
for (size_t i = 0; i < ifd->count; i++) {
const AVExifEntry *entry = &ifd->entries[i];
- if (entry->id == ORIENTATION_TAG && entry->count > 0 && entry->type == AV_TIFF_SHORT) {
+ if (entry->id == av_exif_get_tag_id("Orientation") &&
+ entry->count > 0 && entry->type == AV_TIFF_SHORT) {
orient = entry;
break;
}
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 69e77cd976..f9ad3e1bdb 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -46,6 +46,13 @@
#define IFD_EXTRA_SIZE 6
#define EXIF_TAG_NAME_LENGTH 32
+#define MAKERNOTE_TAG 0x927c
+#define ORIENTATION_TAG 0x112
+#define EXIFIFD_TAG 0x8769
+#define IMAGE_WIDTH_TAG 0x100
+#define IMAGE_LENGTH_TAG 0x101
+#define PIXEL_X_TAG 0xa002
+#define PIXEL_Y_TAG 0xa003
struct exif_tag {
const char name[EXIF_TAG_NAME_LENGTH];
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
index ad5260d329..c0d2b3ef62 100644
--- a/libavcodec/exif_internal.h
+++ b/libavcodec/exif_internal.h
@@ -42,14 +42,6 @@ int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata);
#endif
-#define MAKERNOTE_TAG 0x927c
-#define ORIENTATION_TAG 0x112
-#define EXIFIFD_TAG 0x8769
-#define IMAGE_WIDTH_TAG 0x100
-#define IMAGE_LENGTH_TAG 0x101
-#define PIXEL_X_TAG 0xa002
-#define PIXEL_Y_TAG 0xa003
-
/**
* Compares values in the IFD with data in the provided AVFrame and sets the values
* in that IFD to match the ones in that AVFrame. This is mostly useful for an
commit 310be1c5a402cc77d58a4c1b21af2654a87f6228
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Wed Aug 20 19:53:53 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:35 2025 -0300
avformat/mov: export Exif metadata from HEIF streams
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 5bf5a87055..81753d04e9 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -51,6 +51,7 @@
#include "libavutil/timecode.h"
#include "libavutil/uuid.h"
#include "libavcodec/ac3tab.h"
+#include "libavcodec/exif.h"
#include "libavcodec/flac.h"
#include "libavcodec/hevc/hevc.h"
#include "libavcodec/mpegaudiodecheader.h"
@@ -9085,12 +9086,12 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version)
return 0;
}
-static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
+static int mov_read_iref_cdsc(MOVContext *c, AVIOContext *pb, uint32_t type, int version)
{
HEIFItem *from_item = NULL;
int entries;
int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
- const HEIFItemRef ref = { MKTAG('t','h','m','b'), from_item_id };
+ const HEIFItemRef ref = { type, from_item_id };
from_item = get_heif_item(c, from_item_id);
if (!from_item) {
@@ -9103,7 +9104,8 @@ static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
for (int i = 0; i < entries; i++) {
HEIFItem *item = get_heif_item(c, version ? avio_rb32(pb) : avio_rb16(pb));
if (!item) {
- av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by thmb item\n");
+ av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by %s item\n",
+ av_fourcc2str(type));
continue;
}
@@ -9112,8 +9114,8 @@ static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
return AVERROR(ENOMEM);
}
- av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d\n",
- from_item_id, entries);
+ av_log(c->fc, AV_LOG_TRACE, "%s: from_item_id %d, entries %d\n",
+ av_fourcc2str(type), from_item_id, entries);
return 0;
}
@@ -9142,8 +9144,9 @@ static int mov_read_iref(MOVContext *c, AVIOContext *pb, MOVAtom atom)
case MKTAG('d','i','m','g'):
mov_read_iref_dimg(c, pb, version);
break;
+ case MKTAG('c','d','s','c'):
case MKTAG('t','h','m','b'):
- mov_read_iref_thmb(c, pb, version);
+ mov_read_iref_cdsc(c, pb, type, version);
break;
default:
av_log(c->fc, AV_LOG_DEBUG, "Unknown iref type %s size %"PRIu32"\n",
@@ -10308,6 +10311,86 @@ fail:
return ret;
}
+static int mov_parse_exif_item(AVFormatContext *s,
+ AVPacketSideData **coded_side_data, int *nb_coded_side_data,
+ const HEIFItem *ref)
+{
+ MOVContext *c = s->priv_data;
+ AVPacketSideData *sd;
+ AVExifMetadata ifd = { 0 };
+ AVExifEntry *entry = NULL;
+ AVBufferRef *buf;
+ int64_t offset = 0, pos = avio_tell(s->pb);
+ unsigned orientation_id = av_exif_get_tag_id("Orientation");
+ int err;
+
+ if (!(s->pb->seekable & AVIO_SEEKABLE_NORMAL)) {
+ av_log(c->fc, AV_LOG_WARNING, "Exif metadata with non seekable input\n");
+ return AVERROR_PATCHWELCOME;
+ }
+ if (ref->is_idat_relative) {
+ if (!c->idat_offset) {
+ av_log(c->fc, AV_LOG_ERROR, "missing idat box required by the Exif metadata\n");
+ return AVERROR_INVALIDDATA;
+ }
+ offset = c->idat_offset;
+ }
+
+ buf = av_buffer_alloc(ref->extent_length);
+ if (!buf)
+ return AVERROR(ENOMEM);
+
+ avio_seek(s->pb, ref->extent_offset + offset, SEEK_SET);
+ err = avio_read(s->pb, buf->data, ref->extent_length);
+ if (err != ref->extent_length) {
+ if (err > 0)
+ err = AVERROR_INVALIDDATA;
+ goto fail;
+ }
+
+ // HEIF spec states that Exif metadata is informative. The irot item property is
+ // the normative source of rotation information. So we remove any Orientation tag
+ // present in the Exif buffer.
+ err = av_exif_parse_buffer(s, buf->data, ref->extent_length, &ifd, AV_EXIF_T_OFF);
+ if (err < 0) {
+ av_log(s, AV_LOG_ERROR, "Unable to parse Exif metadata\n");
+ goto fail;
+ }
+
+ err = av_exif_remove_entry(s, &ifd, orientation_id, 0);
+ if (err < 0)
+ goto fail;
+ else if (!err)
+ goto finish;
+
+ av_buffer_unref(&buf);
+ err = av_exif_write(s, &ifd, &buf, AV_EXIF_T_OFF);
+ if (err < 0)
+ goto fail;
+
+finish:
+ offset = AV_RB32(buf->data) + 4;
+ if (offset >= buf->size) {
+ err = AVERROR_INVALIDDATA;
+ goto fail;
+ }
+ sd = av_packet_side_data_new(coded_side_data, nb_coded_side_data,
+ AV_PKT_DATA_EXIF, buf->size - offset, 0);
+ if (!sd) {
+ err = AVERROR(ENOMEM);
+ goto fail;
+ }
+ memcpy(sd->data, buf->data + offset, buf->size - offset);
+
+ err = 0;
+fail:
+ av_buffer_unref(&buf);
+ av_exif_free(&ifd);
+ avio_seek(s->pb, pos, SEEK_SET);
+
+ return err;
+}
+
static int mov_parse_tiles(AVFormatContext *s)
{
MOVContext *mov = s->priv_data;
@@ -10393,6 +10476,22 @@ static int mov_parse_tiles(AVFormatContext *s)
if (err < 0)
return err;
+ for (int j = 0; j < grid->item->nb_iref_list; j++) {
+ HEIFItem *ref = get_heif_item(mov, grid->item->iref_list[j].item_id);
+
+ av_assert0(ref);
+ switch(ref->type) {
+ case MKTAG('E','x','i','f'):
+ err = mov_parse_exif_item(s, &tile_grid->coded_side_data,
+ &tile_grid->nb_coded_side_data, ref);
+ if (err < 0 && (s->error_recognition & AV_EF_EXPLODE))
+ return err;
+ break;
+ default:
+ break;
+ }
+ }
+
/* rotation */
if (grid->item->rotation || grid->item->hflip || grid->item->vflip) {
err = set_display_matrix_from_item(&tile_grid->coded_side_data,
@@ -10459,6 +10558,22 @@ static int mov_parse_heif_items(AVFormatContext *s)
if (item->item_id == mov->primary_item_id)
st->disposition |= AV_DISPOSITION_DEFAULT;
+ for (int j = 0; j < item->nb_iref_list; j++) {
+ HEIFItem *ref = get_heif_item(mov, item->iref_list[j].item_id);
+
+ av_assert0(ref);
+ switch(ref->type) {
+ case MKTAG('E','x','i','f'):
+ err = mov_parse_exif_item(s, &st->codecpar->coded_side_data,
+ &st->codecpar->nb_coded_side_data, ref);
+ if (err < 0 && (s->error_recognition & AV_EF_EXPLODE))
+ return err;
+ break;
+ default:
+ break;
+ }
+ }
+
if (item->rotation || item->hflip || item->vflip) {
err = set_display_matrix_from_item(&st->codecpar->coded_side_data,
&st->codecpar->nb_coded_side_data, item);
diff --git a/libavformat/version.h b/libavformat/version.h
index cc56b7cf5c..858a94cc5d 100644
--- a/libavformat/version.h
+++ b/libavformat/version.h
@@ -32,7 +32,7 @@
#include "version_major.h"
#define LIBAVFORMAT_VERSION_MINOR 4
-#define LIBAVFORMAT_VERSION_MICRO 101
+#define LIBAVFORMAT_VERSION_MICRO 102
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
LIBAVFORMAT_VERSION_MINOR, \
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 8e0988000d..c95ed42621 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -188,7 +188,7 @@ fate-mov-heic-demux-clap-irot-imir: CMD = stream_demux mov $(TARGET_SAMPLES)/hei
FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC MJPEG, HEVC_PARSER) \
+= fate-mov-heic-demux-still-image-multiple-thumb
fate-mov-heic-demux-still-image-multiple-thumb: CMD = stream_demux mov $(TARGET_SAMPLES)/heif/P1001091.HIF "" "-c:v copy -map 0" \
- "-show_entries stream=index,id:stream_disposition"
+ "-show_entries stream=index,id:stream_disposition:stream_side_data_list"
# heic demuxing - still image with multiple items in a grid.
FATE_MOV_FFMPEG_FFPROBE_SAMPLES-$(call FRAMECRC, MOV, HEVC, HEVC_PARSER) \
diff --git a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
index 88263f0d0b..e5764f6de8 100644
--- a/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
+++ b/tests/ref/fate/mov-heic-demux-still-image-multiple-thumb
@@ -39,6 +39,10 @@ DISPOSITION:metadata=0
DISPOSITION:dependent=0
DISPOSITION:still_image=0
DISPOSITION:multilayer=0
+[SIDE_DATA]
+side_data_type=EXIF metadata
+size=30308
+[/SIDE_DATA]
[/STREAM]
[STREAM]
index=1
commit 74e430202d933284fc38b591d4b3a12464e8aec6
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Wed Aug 20 17:07:50 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:14 2025 -0300
avformat/mov: make items referencing items generic
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavformat/isom.h b/libavformat/isom.h
index 94c9c65989..55bc2827b4 100644
--- a/libavformat/isom.h
+++ b/libavformat/isom.h
@@ -286,8 +286,15 @@ typedef struct MOVStreamContext {
int iamf_stream_offset;
} MOVStreamContext;
+typedef struct HEIFItemRef {
+ uint32_t type;
+ int item_id;
+} HEIFItemRef;
+
typedef struct HEIFItem {
AVStream *st;
+ HEIFItemRef *iref_list;
+ int nb_iref_list;
char *name;
int item_id;
int64_t extent_length;
@@ -376,8 +383,6 @@ typedef struct MOVContext {
int nb_heif_item;
HEIFGrid *heif_grid;
int nb_heif_grid;
- int* thmb_item_id;
- int nb_thmb_item;
int64_t idat_offset;
int interleaved_read;
} MOVContext;
diff --git a/libavformat/mov.c b/libavformat/mov.c
index 2e227a1062..5bf5a87055 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -188,14 +188,14 @@ static int mov_read_mac_string(MOVContext *c, AVIOContext *pb, int len,
}
/**
- * Get the current item in the parsing process.
+ * Get the requested item.
*/
-static HEIFItem *heif_cur_item(MOVContext *c)
+static HEIFItem *get_heif_item(MOVContext *c, unsigned id)
{
HEIFItem *item = NULL;
for (int i = 0; i < c->nb_heif_item; i++) {
- if (!c->heif_item[i] || c->heif_item[i]->item_id != c->cur_item_id)
+ if (!c->heif_item[i] || c->heif_item[i]->item_id != id)
continue;
item = c->heif_item[i];
@@ -220,7 +220,7 @@ static AVStream *get_curr_st(MOVContext *c)
if (c->cur_item_id == -1)
return c->fc->streams[c->fc->nb_streams-1];
- item = heif_cur_item(c);
+ item = get_heif_item(c, c->cur_item_id);
if (item)
st = item->st;
@@ -1245,7 +1245,7 @@ static int mov_read_clap(MOVContext *c, AVIOContext *pb, MOVAtom atom)
AVRational pc_x, pc_y;
uint64_t top, bottom, left, right;
- item = heif_cur_item(c);
+ item = get_heif_item(c, c->cur_item_id);
st = get_curr_st(c);
if (!st)
return 0;
@@ -2078,7 +2078,7 @@ static int mov_read_colr(MOVContext *c, AVIOContext *pb, MOVAtom atom)
st = get_curr_st(c);
if (!st) {
- item = heif_cur_item(c);
+ item = get_heif_item(c, c->cur_item_id);
if (!item)
return 0;
}
@@ -9087,30 +9087,33 @@ static int mov_read_iref_dimg(MOVContext *c, AVIOContext *pb, int version)
static int mov_read_iref_thmb(MOVContext *c, AVIOContext *pb, int version)
{
- int *thmb_item_id;
+ HEIFItem *from_item = NULL;
int entries;
- int to_item_id, from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
+ int from_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
+ const HEIFItemRef ref = { MKTAG('t','h','m','b'), from_item_id };
- entries = avio_rb16(pb);
- if (entries > 1) {
- avpriv_request_sample(c->fc, "thmb in iref referencing several items");
- return AVERROR_PATCHWELCOME;
+ from_item = get_heif_item(c, from_item_id);
+ if (!from_item) {
+ av_log(c->fc, AV_LOG_ERROR, "Missing stream referenced by thmb item\n");
+ return AVERROR_INVALIDDATA;
}
- /* 'to' item ids */
- to_item_id = version ? avio_rb32(pb) : avio_rb16(pb);
- if (to_item_id != c->primary_item_id)
- return 0;
+ entries = avio_rb16(pb);
+ /* 'to' item ids */
+ for (int i = 0; i < entries; i++) {
+ HEIFItem *item = get_heif_item(c, version ? avio_rb32(pb) : avio_rb16(pb));
+ if (!item) {
+ av_log(c->fc, AV_LOG_WARNING, "Missing stream referenced by thmb item\n");
+ continue;
+ }
- /* Put thumnbail IDs into an array */
- thmb_item_id = av_dynarray2_add((void **)&c->thmb_item_id, &c->nb_thmb_item,
- sizeof(*c->thmb_item_id),
- (const void *)&from_item_id);
- if (!thmb_item_id)
- return AVERROR(ENOMEM);
+ if (!av_dynarray2_add((void **)&item->iref_list, &item->nb_iref_list,
+ sizeof(*item->iref_list), (const uint8_t *)&ref))
+ return AVERROR(ENOMEM);
+ }
- av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d, nb_thmb: %d\n",
- from_item_id, entries, c->nb_thmb_item);
+ av_log(c->fc, AV_LOG_TRACE, "thmb: from_item_id %d, entries %d\n",
+ from_item_id, entries);
return 0;
}
@@ -9166,7 +9169,7 @@ static int mov_read_ispe(MOVContext *c, AVIOContext *pb, MOVAtom atom)
av_log(c->fc, AV_LOG_TRACE, "ispe: item_id %d, width %u, height %u\n",
c->cur_item_id, width, height);
- item = heif_cur_item(c);
+ item = get_heif_item(c, c->cur_item_id);
if (item) {
item->width = width;
item->height = height;
@@ -9185,7 +9188,7 @@ static int mov_read_irot(MOVContext *c, AVIOContext *pb, MOVAtom atom)
av_log(c->fc, AV_LOG_TRACE, "irot: item_id %d, angle %u\n",
c->cur_item_id, angle);
- item = heif_cur_item(c);
+ item = get_heif_item(c, c->cur_item_id);
if (item) {
// angle * 90 specifies the angle (in anti-clockwise direction)
// in units of degrees.
@@ -9205,7 +9208,7 @@ static int mov_read_imir(MOVContext *c, AVIOContext *pb, MOVAtom atom)
av_log(c->fc, AV_LOG_TRACE, "imir: item_id %d, axis %u\n",
c->cur_item_id, axis);
- item = heif_cur_item(c);
+ item = get_heif_item(c, c->cur_item_id);
if (item) {
item->hflip = axis;
item->vflip = !axis;
@@ -9970,6 +9973,7 @@ static int mov_read_close(AVFormatContext *s)
if (!mov->heif_item[i])
continue;
av_freep(&mov->heif_item[i]->name);
+ av_freep(&mov->heif_item[i]->iref_list);
av_freep(&mov->heif_item[i]->icc_profile);
av_freep(&mov->heif_item[i]);
}
@@ -9980,7 +9984,6 @@ static int mov_read_close(AVFormatContext *s)
av_freep(&mov->heif_grid[i].tile_item_list);
}
av_freep(&mov->heif_grid);
- av_freep(&mov->thmb_item_id);
return 0;
}
@@ -10429,13 +10432,6 @@ static int mov_parse_heif_items(AVFormatContext *s)
if (!item)
continue;
if (!item->st) {
- for (int j = 0; j < mov->nb_thmb_item; j++) {
- if (item->item_id == mov->thmb_item_id[j]) {
- av_log(s, AV_LOG_ERROR, "HEIF thumbnail ID %d doesn't reference a stream\n",
- item->item_id);
- return AVERROR_INVALIDDATA;
- }
- }
continue;
}
if (item->is_idat_relative) {
@@ -10587,7 +10583,6 @@ static int mov_read_header(AVFormatContext *s)
mov->fc = s;
mov->trak_index = -1;
- mov->thmb_item_id = NULL;
mov->primary_item_id = -1;
mov->cur_item_id = -1;
/* .mov and .mp4 aren't streamable anyway (only progressive download if moov is before mdat) */
commit 6a115e20664329b5bff92fab781903e55e8d4acd
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Sat Aug 23 11:04:48 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:14 2025 -0300
avformat/mov: reduce code duplication when setting tile group properties
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavformat/mov.c b/libavformat/mov.c
index ac0c01a67f..2e227a1062 100644
--- a/libavformat/mov.c
+++ b/libavformat/mov.c
@@ -10188,21 +10188,6 @@ static int read_image_grid(AVFormatContext *s, const HEIFGrid *grid,
tile_grid->width = (flags & 1) ? avio_rb32(s->pb) : avio_rb16(s->pb);
tile_grid->height = (flags & 1) ? avio_rb32(s->pb) : avio_rb16(s->pb);
- /* ICC profile */
- if (item->icc_profile_size) {
- int ret = set_icc_profile_from_item(&tile_grid->coded_side_data,
- &tile_grid->nb_coded_side_data, item);
- if (ret < 0)
- return ret;
- }
- /* rotation */
- if (item->rotation || item->hflip || item->vflip) {
- int ret = set_display_matrix_from_item(&tile_grid->coded_side_data,
- &tile_grid->nb_coded_side_data, item);
- if (ret < 0)
- return ret;
- }
-
av_log(c->fc, AV_LOG_TRACE, "grid: grid_rows %d grid_cols %d output_width %d output_height %d\n",
tile_rows, tile_cols, tile_grid->width, tile_grid->height);
@@ -10294,22 +10279,6 @@ static int read_image_iovl(AVFormatContext *s, const HEIFGrid *grid,
tile_grid->height =
tile_grid->coded_height = (flags & 1) ? avio_rb32(s->pb) : avio_rb16(s->pb);
- /* rotation */
- if (item->rotation || item->hflip || item->vflip) {
- int ret = set_display_matrix_from_item(&tile_grid->coded_side_data,
- &tile_grid->nb_coded_side_data, item);
- if (ret < 0)
- return ret;
- }
-
- /* ICC profile */
- if (item->icc_profile_size) {
- int ret = set_icc_profile_from_item(&tile_grid->coded_side_data,
- &tile_grid->nb_coded_side_data, item);
- if (ret < 0)
- return ret;
- }
-
av_log(c->fc, AV_LOG_TRACE, "iovl: output_width %d, output_height %d\n",
tile_grid->width, tile_grid->height);
@@ -10421,6 +10390,21 @@ static int mov_parse_tiles(AVFormatContext *s)
if (err < 0)
return err;
+ /* rotation */
+ if (grid->item->rotation || grid->item->hflip || grid->item->vflip) {
+ err = set_display_matrix_from_item(&tile_grid->coded_side_data,
+ &tile_grid->nb_coded_side_data, grid->item);
+ if (err < 0)
+ return err;
+ }
+
+ /* ICC profile */
+ if (grid->item->icc_profile_size) {
+ err = set_icc_profile_from_item(&tile_grid->coded_side_data,
+ &tile_grid->nb_coded_side_data, grid->item);
+ if (err < 0)
+ return err;
+ }
if (grid->item->name)
av_dict_set(&stg->metadata, "title", grid->item->name, 0);
commit 11d9d7b8f31f7fac704359576bb13faac17be42b
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Wed Aug 20 13:23:49 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:14 2025 -0300
avformat/dump: print side data type names generically
Based on vf_showinfo behavior.
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavformat/dump.c b/libavformat/dump.c
index 02f69f9a3a..dfa33fd3c9 100644
--- a/libavformat/dump.c
+++ b/libavformat/dump.c
@@ -342,7 +342,7 @@ static void dump_mastering_display_metadata(void *ctx, const AVPacketSideData *s
{
const AVMasteringDisplayMetadata *metadata =
(const AVMasteringDisplayMetadata *)sd->data;
- av_log(ctx, log_level, "Mastering Display Metadata, "
+ av_log(ctx, log_level,
"has_primaries:%d has_luminance:%d "
"r(%5.4f,%5.4f) g(%5.4f,%5.4f) b(%5.4f %5.4f) wp(%5.4f, %5.4f) "
"min_luminance=%f, max_luminance=%f",
@@ -362,7 +362,7 @@ static void dump_content_light_metadata(void *ctx, const AVPacketSideData *sd,
{
const AVContentLightMetadata *metadata =
(const AVContentLightMetadata *)sd->data;
- av_log(ctx, log_level, "Content Light Level Metadata, "
+ av_log(ctx, log_level,
"MaxCLL=%d, MaxFALL=%d",
metadata->MaxCLL, metadata->MaxFALL);
}
@@ -371,7 +371,7 @@ static void dump_ambient_viewing_environment_metadata(void *ctx, const AVPacketS
{
const AVAmbientViewingEnvironment *ambient =
(const AVAmbientViewingEnvironment *)sd->data;
- av_log(ctx, AV_LOG_INFO, "Ambient Viewing Environment, "
+ av_log(ctx, AV_LOG_INFO,
"ambient_illuminance=%f, ambient_light_x=%f, ambient_light_y=%f",
av_q2d(ambient->ambient_illuminance),
av_q2d(ambient->ambient_light_x),
@@ -481,81 +481,66 @@ static void dump_sidedata(void *ctx, const AVPacketSideData *side_data, int nb_s
for (i = 0; i < nb_side_data; i++) {
const AVPacketSideData *sd = &side_data[i];
- av_log(ctx, log_level, "%s ", indent);
+ const char *name = av_packet_side_data_name(sd->type);
+ av_log(ctx, log_level, "%s ", indent);
+ if (name)
+ av_log(ctx, AV_LOG_INFO, "%s: ", name);
switch (sd->type) {
- case AV_PKT_DATA_PALETTE:
- av_log(ctx, log_level, "palette");
- break;
- case AV_PKT_DATA_NEW_EXTRADATA:
- av_log(ctx, log_level, "new extradata");
- break;
case AV_PKT_DATA_PARAM_CHANGE:
- av_log(ctx, log_level, "paramchange: ");
dump_paramchange(ctx, sd, log_level);
break;
- case AV_PKT_DATA_H263_MB_INFO:
- av_log(ctx, log_level, "H.263 macroblock info");
- break;
case AV_PKT_DATA_REPLAYGAIN:
- av_log(ctx, log_level, "replaygain: ");
dump_replaygain(ctx, sd, log_level);
break;
case AV_PKT_DATA_DISPLAYMATRIX:
- av_log(ctx, log_level, "displaymatrix: rotation of %.2f degrees",
+ av_log(ctx, log_level, "rotation of %.2f degrees",
av_display_rotation_get((const int32_t *)sd->data));
break;
case AV_PKT_DATA_STEREO3D:
- av_log(ctx, log_level, "stereo3d: ");
dump_stereo3d(ctx, sd, log_level);
break;
case AV_PKT_DATA_AUDIO_SERVICE_TYPE:
- av_log(ctx, log_level, "audio service type: ");
dump_audioservicetype(ctx, sd, log_level);
break;
case AV_PKT_DATA_QUALITY_STATS:
- av_log(ctx, log_level, "quality factor: %"PRId32", pict_type: %c",
+ av_log(ctx, log_level, "%"PRId32", pict_type: %c",
AV_RL32(sd->data), av_get_picture_type_char(sd->data[4]));
break;
case AV_PKT_DATA_CPB_PROPERTIES:
- av_log(ctx, log_level, "cpb: ");
dump_cpb(ctx, sd, log_level);
break;
case AV_PKT_DATA_MASTERING_DISPLAY_METADATA:
dump_mastering_display_metadata(ctx, sd, log_level);
break;
case AV_PKT_DATA_SPHERICAL:
- av_log(ctx, log_level, "spherical: ");
dump_spherical(ctx, w, h, sd, log_level);
break;
case AV_PKT_DATA_CONTENT_LIGHT_LEVEL:
dump_content_light_metadata(ctx, sd, log_level);
break;
- case AV_PKT_DATA_ICC_PROFILE:
- av_log(ctx, log_level, "ICC Profile");
- break;
case AV_PKT_DATA_DOVI_CONF:
- av_log(ctx, log_level, "DOVI configuration record: ");
dump_dovi_conf(ctx, sd, log_level);
break;
case AV_PKT_DATA_S12M_TIMECODE:
- av_log(ctx, log_level, "SMPTE ST 12-1:2014: ");
dump_s12m_timecode(ctx, avg_frame_rate, sd, log_level);
break;
case AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT:
dump_ambient_viewing_environment_metadata(ctx, sd);
break;
case AV_PKT_DATA_FRAME_CROPPING:
- av_log(ctx, AV_LOG_INFO, "Frame cropping: ");
dump_cropping(ctx, sd);
break;
case AV_PKT_DATA_3D_REFERENCE_DISPLAYS:
- av_log(ctx, log_level, "3D Reference Displays Information: ");
dump_tdrdi(ctx, sd);
break;
default:
- av_log(ctx, log_level, "unknown side data type %d "
- "(%"SIZE_SPECIFIER" bytes)", sd->type, sd->size);
+ if (name)
+ av_log(ctx, log_level,
+ "(%"SIZE_SPECIFIER" bytes)", sd->size);
+ else
+ av_log(ctx, log_level, "unknown side data type %d "
+ "(%"SIZE_SPECIFIER" bytes)", sd->type, sd->size);
break;
}
commit e62f9a071b5c912c5a8cabea5e89943aee5510d7
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Wed Aug 20 22:00:33 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:14 2025 -0300
ffprobe: print EXIF packet side data size
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c
index 018111318e..f4eb87aefa 100644
--- a/fftools/ffprobe.c
+++ b/fftools/ffprobe.c
@@ -1063,6 +1063,8 @@ static void print_pkt_side_data(AVTextFormatContext *tfc,
print_int("crop_right", AV_RL32(sd->data + 12));
} else if (sd->type == AV_PKT_DATA_AFD && sd->size > 0) {
print_int("active_format", *sd->data);
+ } else if (sd->type == AV_PKT_DATA_EXIF) {
+ print_int("size", sd->size);
}
}
commit 4ffd621523e89a90d94bc2fbeeacdd59a65d66a1
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Wed Aug 20 13:23:05 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:07:07 2025 -0300
avcodec/packet: add an Exif side data type
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/doc/APIchanges b/doc/APIchanges
index 5d4fb8d127..7fbe3a0769 100644
--- a/doc/APIchanges
+++ b/doc/APIchanges
@@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28
API changes, most recent first:
+2025-09-01 - xxxxxxxx - lavc 62.14.100 - packet.h
+ Add AV_PKT_DATA_EXIF
+
2025-08-xx - xxxxxxxx - lavc 62.13.101 - exif.h
Add AV_EXIF_FLAG_RECURSIVE
diff --git a/libavcodec/avcodec.c b/libavcodec/avcodec.c
index 834b7ad242..2181439113 100644
--- a/libavcodec/avcodec.c
+++ b/libavcodec/avcodec.c
@@ -66,6 +66,7 @@ const SideDataMap ff_sd_global_map[] = {
{ AV_PKT_DATA_ICC_PROFILE, AV_FRAME_DATA_ICC_PROFILE },
{ AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT,AV_FRAME_DATA_AMBIENT_VIEWING_ENVIRONMENT },
{ AV_PKT_DATA_3D_REFERENCE_DISPLAYS, AV_FRAME_DATA_3D_REFERENCE_DISPLAYS },
+ { AV_PKT_DATA_EXIF, AV_FRAME_DATA_EXIF },
{ AV_PKT_DATA_NB },
};
diff --git a/libavcodec/options_table.h b/libavcodec/options_table.h
index 25da169343..43aec402ba 100644
--- a/libavcodec/options_table.h
+++ b/libavcodec/options_table.h
@@ -406,6 +406,7 @@ static const AVOption avcodec_options[] = {
{"mastering_display_metadata", .default_val.i64 = AV_PKT_DATA_MASTERING_DISPLAY_METADATA, .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
{"content_light_level", .default_val.i64 = AV_PKT_DATA_CONTENT_LIGHT_LEVEL, .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
{"icc_profile", .default_val.i64 = AV_PKT_DATA_ICC_PROFILE, .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
+ {"exif", .default_val.i64 = AV_PKT_DATA_EXIF, .type = AV_OPT_TYPE_CONST, .flags = A|D, .unit = "side_data_pkt" },
{NULL},
};
diff --git a/libavcodec/packet.c b/libavcodec/packet.c
index 2d1f282927..9420c10be0 100644
--- a/libavcodec/packet.c
+++ b/libavcodec/packet.c
@@ -310,6 +310,7 @@ const char *av_packet_side_data_name(enum AVPacketSideDataType type)
case AV_PKT_DATA_LCEVC: return "LCEVC NAL data";
case AV_PKT_DATA_3D_REFERENCE_DISPLAYS: return "3D Reference Displays Info";
case AV_PKT_DATA_RTCP_SR: return "RTCP Sender Report";
+ case AV_PKT_DATA_EXIF: return "EXIF metadata";
}
return NULL;
}
diff --git a/libavcodec/packet.h b/libavcodec/packet.h
index 55389a957d..5e27f9ceb5 100644
--- a/libavcodec/packet.h
+++ b/libavcodec/packet.h
@@ -362,6 +362,12 @@ enum AVPacketSideDataType {
*/
AV_PKT_DATA_RTCP_SR,
+ /**
+ * Extensible image file format metadata. The payload is a buffer containing
+ * EXIF metadata, starting with either 49 49 2a 00, or 4d 4d 00 2a.
+ */
+ AV_PKT_DATA_EXIF,
+
/**
* The number of side data types.
* This is not part of the public API/ABI in the sense that it may
diff --git a/libavcodec/version.h b/libavcodec/version.h
index d394a022a6..7531c6c42a 100644
--- a/libavcodec/version.h
+++ b/libavcodec/version.h
@@ -29,8 +29,8 @@
#include "version_major.h"
-#define LIBAVCODEC_VERSION_MINOR 13
-#define LIBAVCODEC_VERSION_MICRO 101
+#define LIBAVCODEC_VERSION_MINOR 14
+#define LIBAVCODEC_VERSION_MICRO 100
#define LIBAVCODEC_VERSION_INT AV_VERSION_INT(LIBAVCODEC_VERSION_MAJOR, \
LIBAVCODEC_VERSION_MINOR, \
-----------------------------------------------------------------------
Summary of changes:
doc/APIchanges | 3 +
fftools/ffprobe.c | 2 +
libavcodec/avcodec.c | 1 +
libavcodec/decode.c | 128 +-
libavcodec/decode.h | 7 +-
libavcodec/exif.c | 7 +
libavcodec/exif_internal.h | 8 -
libavcodec/options_table.h | 1 +
libavcodec/packet.c | 1 +
libavcodec/packet.h | 6 +
libavcodec/pngdec.c | 5 +-
libavcodec/version.h | 2 +-
libavcodec/webp.c | 7 +-
libavformat/dump.c | 45 +-
libavformat/isom.h | 9 +-
libavformat/mov.c | 232 ++-
libavformat/version.h | 2 +-
tests/fate/mov.mak | 2 +-
.../fate/mov-heic-demux-still-image-multiple-thumb | 1890 ++++++++++++++++++++
19 files changed, 2209 insertions(+), 149 deletions(-)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. 45dcfb2d60 avcodec/version_major: remove unnecessary define for an internal function
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via 45dcfb2d6039c2f40f1d54d01219526cad8633d4 (commit)
from a66a260ae9da037b1f14ad4d110de3bceb5b42e9 (commit)
- Log -----------------------------------------------------------------
commit 45dcfb2d6039c2f40f1d54d01219526cad8633d4
Author: James Almer <jamrial(a)gmail.com>
AuthorDate: Mon Sep 1 11:00:44 2025 -0300
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 11:00:44 2025 -0300
avcodec/version_major: remove unnecessary define for an internal function
It being avpriv and not public, it can be removed in the next bump regardless of when it
happens.
Signed-off-by: James Almer <jamrial(a)gmail.com>
diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index 2ac54e51af..69e77cd976 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -909,7 +909,7 @@ int av_exif_ifd_to_dict(void *logctx, const AVExifMetadata *ifd, AVDictionary **
return exif_ifd_to_dict(logctx, "", ifd, metadata);
}
-#if FF_API_OLD_EXIF
+#if LIBAVCODEC_VERSION_MAJOR < 63
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata)
{
@@ -924,7 +924,7 @@ int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
av_exif_free(&ifd);
return ret;
}
-#endif /* FF_API_OLD_EXIF */
+#endif
#define EXIF_COPY(fname, srcname) do { \
size_t sz; \
diff --git a/libavcodec/exif_internal.h b/libavcodec/exif_internal.h
index 565e747353..ad5260d329 100644
--- a/libavcodec/exif_internal.h
+++ b/libavcodec/exif_internal.h
@@ -36,11 +36,11 @@
#include "exif.h"
#include "version_major.h"
-#if FF_API_OLD_EXIF
+#if LIBAVCODEC_VERSION_MAJOR < 63
/* Used by the AVI demuxer */
int avpriv_exif_decode_ifd(void *logctx, const uint8_t *buf, int size,
int le, int depth, AVDictionary **metadata);
-#endif /* FF_API_OLD_EXIF */
+#endif
#define MAKERNOTE_TAG 0x927c
#define ORIENTATION_TAG 0x112
diff --git a/libavcodec/version_major.h b/libavcodec/version_major.h
index 6ffe82e2e7..97e4e12065 100644
--- a/libavcodec/version_major.h
+++ b/libavcodec/version_major.h
@@ -42,7 +42,6 @@
#define FF_API_V408_CODECID (LIBAVCODEC_VERSION_MAJOR < 63)
#define FF_API_CODEC_PROPS (LIBAVCODEC_VERSION_MAJOR < 63)
#define FF_API_EXR_GAMMA (LIBAVCODEC_VERSION_MAJOR < 63)
-#define FF_API_OLD_EXIF (LIBAVCODEC_VERSION_MAJOR < 63)
#define FF_API_NVDEC_OLD_PIX_FMTS (LIBAVCODEC_VERSION_MAJOR < 63)
-----------------------------------------------------------------------
Summary of changes:
libavcodec/exif.c | 4 ++--
libavcodec/exif_internal.h | 4 ++--
libavcodec/version_major.h | 1 -
3 files changed, 4 insertions(+), 5 deletions(-)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. a66a260ae9 vp9: Add 8bpc intra prediction AVX2 asm
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via a66a260ae9da037b1f14ad4d110de3bceb5b42e9 (commit)
from 9a34ddc345ddbc4810799fad65c7fa9d65b15eb1 (commit)
- Log -----------------------------------------------------------------
commit a66a260ae9da037b1f14ad4d110de3bceb5b42e9
Author: Henrik Gramner <gramner(a)twoorioles.com>
AuthorDate: Mon Sep 1 02:03:00 2025 +0200
Commit: Henrik Gramner <henrik(a)gramner.com>
CommitDate: Mon Sep 1 13:54:52 2025 +0000
vp9: Add 8bpc intra prediction AVX2 asm
diff --git a/libavcodec/x86/vp9dsp_init.c b/libavcodec/x86/vp9dsp_init.c
index 9836b3321c..bbabcf38c3 100644
--- a/libavcodec/x86/vp9dsp_init.c
+++ b/libavcodec/x86/vp9dsp_init.c
@@ -207,11 +207,8 @@ ipred_dir_tm_h_funcs(8, avx);
ipred_dir_tm_h_funcs(16, avx);
ipred_dir_tm_h_funcs(32, avx);
-ipred_func(32, v, avx);
-
-ipred_dc_funcs(32, avx2);
-ipred_func(32, h, avx2);
-ipred_func(32, tm, avx2);
+ipred_all_funcs(32, avx2);
+ipred_func(32, v, avx2);
#undef ipred_func
#undef ipred_dir_tm_h_funcs
@@ -388,7 +385,6 @@ av_cold void ff_vp9dsp_init_x86(VP9DSPContext *dsp, int bpp, int bitexact)
if (EXTERNAL_AVX_FAST(cpu_flags)) {
init_fpel_func(1, 0, 32, put, , avx);
init_fpel_func(0, 0, 64, put, , avx);
- init_ipred(32, avx, v, VERT);
}
if (EXTERNAL_AVX2_FAST(cpu_flags)) {
@@ -408,9 +404,8 @@ av_cold void ff_vp9dsp_init_x86(VP9DSPContext *dsp, int bpp, int bitexact)
init_subpel3_32_64(1, avg, 8, avx2);
#endif
}
- init_dc_ipred(32, avx2);
- init_ipred(32, avx2, h, HOR);
- init_ipred(32, avx2, tm, TM_VP8);
+ init_all_ipred(32, avx2);
+ init_ipred(32, avx2, v, VERT);
}
#if ARCH_X86_64
diff --git a/libavcodec/x86/vp9intrapred.asm b/libavcodec/x86/vp9intrapred.asm
index 31f7d449fd..b67addd7e3 100644
--- a/libavcodec/x86/vp9intrapred.asm
+++ b/libavcodec/x86/vp9intrapred.asm
@@ -2,6 +2,7 @@
;* VP9 Intra prediction SIMD optimizations
;*
;* Copyright (c) 2013 Ronald S. Bultje <rsbultje gmail com>
+;* Copyright (c) 2025 Two Orioles, LLC
;*
;* Parts based on:
;* H.264 intra prediction asm optimizations
@@ -230,40 +231,6 @@ DC_16to32_FUNCS
INIT_XMM ssse3
DC_16to32_FUNCS
-%if HAVE_AVX2_EXTERNAL
-INIT_YMM avx2
-cglobal vp9_ipred_dc_32x32, 4, 4, 3, dst, stride, l, a
- mova m0, [lq]
- mova m1, [aq]
- DEFINE_ARGS dst, stride, stride3, cnt
- lea stride3q, [strideq*3]
- pxor m2, m2
- psadbw m0, m2
- psadbw m1, m2
- paddw m0, m1
- vextracti128 xm1, m0, 1
- paddw xm0, xm1
- movhlps xm1, xm0
- paddw xm0, xm1
- pmulhrsw xm0, [pw_512]
- vpbroadcastb m0, xm0
- mov cntd, 4
-.loop:
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m0
- mova [dstq+strideq*2], m0
- mova [dstq+stride3q ], m0
- lea dstq, [dstq+strideq*4]
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m0
- mova [dstq+strideq*2], m0
- mova [dstq+stride3q ], m0
- lea dstq, [dstq+strideq*4]
- dec cntd
- jg .loop
- RET
-%endif
-
; dc_top/left_NxN(uint8_t *dst, ptrdiff_t stride, const uint8_t *l, const uint8_t *a)
%macro DC_1D_4to8_FUNCS 2 ; dir (top or left), arg (a or l)
@@ -395,44 +362,6 @@ INIT_XMM ssse3
DC_1D_16to32_FUNCS top, a
DC_1D_16to32_FUNCS left, l
-%macro DC_1D_AVX2_FUNCS 2 ; dir (top or left), arg (a or l)
-%if HAVE_AVX2_EXTERNAL
-cglobal vp9_ipred_dc_%1_32x32, 4, 4, 3, dst, stride, l, a
- mova m0, [%2q]
- DEFINE_ARGS dst, stride, stride3, cnt
- lea stride3q, [strideq*3]
- pxor m2, m2
- psadbw m0, m2
- vextracti128 xm1, m0, 1
- paddw xm0, xm1
- movhlps xm1, xm0
- paddw xm0, xm1
- pmulhrsw xm0, [pw_1024]
- vpbroadcastb m0, xm0
- mov cntd, 4
-.loop:
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m0
- mova [dstq+strideq*2], m0
- mova [dstq+stride3q ], m0
- lea dstq, [dstq+strideq*4]
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m0
- mova [dstq+strideq*2], m0
- mova [dstq+stride3q ], m0
- lea dstq, [dstq+strideq*4]
- dec cntd
- jg .loop
- RET
-%endif
-%endmacro
-
-INIT_YMM avx2
-DC_1D_AVX2_FUNCS top, a
-DC_1D_AVX2_FUNCS left, l
-
-; v
-
INIT_MMX mmx
cglobal vp9_ipred_v_8x8, 4, 4, 0, dst, stride, l, a
movq m0, [aq]
@@ -486,29 +415,6 @@ cglobal vp9_ipred_v_32x32, 4, 4, 2, dst, stride, l, a
jg .loop
RET
-INIT_YMM avx
-cglobal vp9_ipred_v_32x32, 4, 4, 1, dst, stride, l, a
- mova m0, [aq]
- DEFINE_ARGS dst, stride, stride3, cnt
- lea stride3q, [strideq*3]
- mov cntd, 4
-.loop:
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m0
- mova [dstq+strideq*2], m0
- mova [dstq+stride3q ], m0
- lea dstq, [dstq+strideq*4]
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m0
- mova [dstq+strideq*2], m0
- mova [dstq+stride3q ], m0
- lea dstq, [dstq+strideq*4]
- dec cntd
- jg .loop
- RET
-
-; h
-
%macro H_XMM_FUNCS 2
%if notcpuflag(avx)
cglobal vp9_ipred_h_4x4, 3, 4, 1, dst, stride, l, stride3
@@ -642,34 +548,6 @@ H_XMM_FUNCS 4, 8
INIT_XMM avx
H_XMM_FUNCS 4, 8
-%if HAVE_AVX2_EXTERNAL
-INIT_YMM avx2
-cglobal vp9_ipred_h_32x32, 3, 5, 8, dst, stride, l, stride3, cnt
- mova m5, [pb_1]
- mova m6, [pb_2]
- mova m7, [pb_3]
- pxor m4, m4
- lea stride3q, [strideq*3]
- mov cntq, 7
-.loop:
- movd xm3, [lq+cntq*4]
- vinserti128 m3, m3, xm3, 1
- pshufb m0, m3, m7
- pshufb m1, m3, m6
- mova [dstq+strideq*0], m0
- mova [dstq+strideq*1], m1
- pshufb m2, m3, m5
- pshufb m3, m4
- mova [dstq+strideq*2], m2
- mova [dstq+stride3q ], m3
- lea dstq, [dstq+strideq*4]
- dec cntq
- jge .loop
- RET
-%endif
-
-; tm
-
%macro TM_MMX_FUNCS 0
cglobal vp9_ipred_tm_4x4, 4, 4, 0, dst, stride, l, a
pxor m1, m1
@@ -898,46 +776,9 @@ TM_XMM_FUNCS
INIT_XMM avx
TM_XMM_FUNCS
-%if HAVE_AVX2_EXTERNAL
-INIT_YMM avx2
-cglobal vp9_ipred_tm_32x32, 4, 4, 8, dst, stride, l, a
- pxor m3, m3
- pinsrw xm2, [aq-1], 0
- vinserti128 m2, m2, xm2, 1
- mova m0, [aq]
- DEFINE_ARGS dst, stride, l, cnt
- mova m4, [pw_m256]
- mova m5, [pw_m255]
- pshufb m2, m4
- punpckhbw m1, m0, m3
- punpcklbw m0, m3
- psubw m1, m2
- psubw m0, m2
- mov cntq, 15
-.loop:
- pinsrw xm7, [lq+cntq*2], 0
- vinserti128 m7, m7, xm7, 1
- pshufb m3, m7, m5
- pshufb m7, m4
- paddw m2, m3, m0
- paddw m3, m1
- paddw m6, m7, m0
- paddw m7, m1
- packuswb m2, m3
- packuswb m6, m7
- mova [dstq+strideq*0], m2
- mova [dstq+strideq*1], m6
- lea dstq, [dstq+strideq*2]
- dec cntq
- jge .loop
- RET
-%endif
-
-; dl
-
-%macro LOWPASS 4 ; left [dst], center, right, tmp
+%macro LOWPASS 4-5 [pb_1] ; left [dst], center, right, tmp, pb_1
pxor m%4, m%1, m%3
- pand m%4, [pb_1]
+ pand m%4, %5
pavgb m%1, m%3
psubusb m%1, m%4
pavgb m%1, m%2
@@ -2041,4 +1882,306 @@ HU_XMM_FUNCS 7
INIT_XMM avx
HU_XMM_FUNCS 7
+%if HAVE_AVX2_EXTERNAL
+INIT_YMM avx2
+cglobal vp9_ipred_dc_32x32, 4, 4, 3, dst, stride, l, a
+ pxor m1, m1
+ psadbw m0, m1, [lq]
+ psadbw m1, [aq]
+ movd xm2, [pw_512]
+ paddw m0, m1
+ vextracti128 xm1, m0, 1
+.main:
+ paddw xm0, xm1
+ punpckhqdq xm1, xm0, xm0
+ paddw xm0, xm1
+ pmulhrsw xm0, xm2
+ vpbroadcastb m0, xm0
+.main2:
+ lea r2, [strideq*3]
+ mov r3d, 8
+.loop:
+ mova [dstq+strideq*0], m0
+ mova [dstq+strideq*1], m0
+ mova [dstq+strideq*2], m0
+ mova [dstq+r2 ], m0
+ lea dstq, [dstq+strideq*4]
+ dec r3d
+ jg .loop
+ RET
+
+cglobal vp9_ipred_dc_top_32x32, 0, 4, 3, dst, stride, l, a
+ mov lq, amp
+%if ARCH_X86_32
+ jmp mangle(private_prefix %+ _vp9_ipred_dc_left_32x32 %+ SUFFIX).main
+%endif
+
+%assign function_align 1
+cglobal vp9_ipred_dc_left_32x32, 0, 4, 3, dst, stride, l, a
+ movifnidn lq, lmp
+.main:
+ movifnidn dstq, dstmp
+ movifnidn strideq, stridemp
+ pxor xm1, xm1
+ psadbw xm0, xm1, [lq]
+ psadbw xm1, [lq+16]
+ movd xm2, [pw_1024]
+ jmp mangle(private_prefix %+ _vp9_ipred_dc_32x32 %+ SUFFIX).main
+
+cglobal vp9_ipred_v_32x32, 2, 4, 3, dst, stride, l, a
+ movifnidn aq, amp
+ mova m0, [aq]
+ jmp mangle(private_prefix %+ _vp9_ipred_dc_32x32 %+ SUFFIX).main2
+
+%assign function_align 16
+cglobal vp9_ipred_h_32x32, 3, 5, 6, dst, stride, l
+ vpbroadcastd m2, [pb_3]
+ mov r3d, 7
+ vpbroadcastd m3, [pb_2]
+ pxor m5, m5
+ vpbroadcastd m4, [pb_1]
+ lea r4, [strideq*3]
+.loop:
+ vpbroadcastd m1, [lq+r3*4]
+ pshufb m0, m1, m2
+ mova [dstq+strideq*0], m0
+ pshufb m0, m1, m3
+ mova [dstq+strideq*1], m0
+ pshufb m0, m1, m4
+ mova [dstq+strideq*2], m0
+ pshufb m1, m5
+ mova [dstq+r4 ], m1
+ lea dstq, [dstq+strideq*4]
+ dec r3d
+ jge .loop
+ RET
+
+cglobal vp9_ipred_tm_32x32, 4, 4, 8, dst, stride, l, a
+ vpbroadcastd m0, [aq-1]
+ mova m7, [aq]
+ pxor m1, m1
+ vpbroadcastd m4, [pw_m255]
+ mov r3d, 15
+ vpbroadcastd m5, [pw_m256]
+ pshufb m0, m5
+ punpcklbw m6, m7, m1
+ punpckhbw m7, m1
+ psubw m6, m0
+ psubw m7, m0
+.loop:
+ vpbroadcastd m3, [lq+r3*2]
+ pshufb m2, m3, m4
+ pshufb m3, m5
+ paddw m0, m2, m6
+ paddw m2, m7
+ paddw m1, m3, m6
+ paddw m3, m7
+ packuswb m0, m2
+ packuswb m1, m3
+ mova [dstq+strideq*0], m0
+ mova [dstq+strideq*1], m1
+ lea dstq, [dstq+strideq*2]
+ dec r3d
+ jge .loop
+ RET
+
+cglobal vp9_ipred_dl_32x32, 2, 5, 6, dst, stride, l, a
+ movifnidn aq, amp
+ vpbroadcastb m2, [aq+31]
+ vinserti128 m3, m2, [aq+16], 0
+ mova m0, [aq+ 0]
+ vpbroadcastd m5, [pb_1]
+ palignr m4, m3, m0, 2
+ lea r3, [strideq*2]
+ palignr m3, m0, 1
+ LOWPASS 0, 3, 4, 1, m5
+ lea r4, [strideq*3]
+ vperm2i128 m1, m0, m2, 0x31
+ mov r2d, 8
+.loop:
+ shufpd m3, m0, m1, 0x05
+ mova [dstq+r3*0], m0
+ punpckhqdq m4, m1, m2
+ mova [dstq+r3*4], m3
+ palignr m0, m1, m0, 1
+ mova [dstq+r3*8], m1
+ palignr m1, m2, m1, 1
+ mova [dstq+r4*8], m4
+ add dstq, strideq
+ dec r2d
+ jg .loop
+ RET
+
+cglobal vp9_ipred_dr_32x32, 4, 5, 7, dst, stride, l, a
+ mova m3, [lq+ 0]
+ movu m1, [aq- 1]
+ mova m0, [aq+ 0]
+ vpbroadcastd m6, [pb_1]
+ vperm2i128 m2, m3, m1, 0x21
+ lea r3, [strideq*2]
+ palignr m4, m1, m2, 15
+ LOWPASS 0, 1, 4, 5, m6
+ pslldq xm4, xm3, 1
+ palignr m2, m3, 1
+ vinserti128 m4, [lq+15], 1
+ LOWPASS 2, 3, 4, 5, m6
+ lea r4, [strideq*3]
+ vperm2i128 m1, m2, m0, 0x21
+ mov r2d, 8
+.loop:
+ shufpd m3, m1, m0, 0x05
+ mova [dstq+r3*0], m0
+ shufpd m4, m2, m1, 0x05
+ mova [dstq+r3*4], m3
+ palignr m0, m1, 15
+ mova [dstq+r3*8], m1
+ palignr m1, m2, 15
+ mova [dstq+r4*8], m4
+ add dstq, strideq
+ pslldq m2, 1
+ dec r2d
+ jg .loop
+ RET
+
+cglobal vp9_ipred_hd_32x32, 4, 6, 7, dst, stride, l, a
+ movu m1, [aq-1]
+ mova m0, [lq]
+ vpbroadcastd m6, [pb_1]
+ vperm2i128 m4, m0, m1, 0x21
+ palignr m3, m4, m0, 1
+ palignr m4, m0, 2
+ LOWPASS 4, 3, 0, 2, m6
+ pavgb m3, m0
+ movu xm0, [aq+15]
+ punpcklbw m2, m3, m4
+ punpckhbw m3, m4
+ palignr m4, m0, m1, 2
+ palignr m0, m1, 1
+ LOWPASS 4, 0, 1, 5, m6
+ lea r2, [strideq*8]
+ vinserti128 m0, m2, xm3, 1
+ lea r3, [dstq+r2*1]
+ vpblendd m1, m2, m3, 0x0f
+ lea r4, [dstq+r2*2]
+ vperm2i128 m2, m3, 0x31
+ lea r5, [r3 +r2*2]
+ vperm2i128 m3, m4, 0x21
+.loop:
+ sub r2, strideq
+ mova [r5 +r2], m0
+ palignr m0, m1, m0, 2
+ mova [r4 +r2], m1
+ palignr m1, m2, m1, 2
+ mova [r3 +r2], m2
+ palignr m2, m3, m2, 2
+ mova [dstq+r2], m3
+ palignr m3, m4, m3, 2
+ psrldq m4, 2
+ jg .loop
+ RET
+
+cglobal vp9_ipred_hu_32x32, 3, 5, 6, dst, stride, l, a
+ mova m0, [lq]
+ vpbroadcastb xm3, [lq+31]
+ vpbroadcastd m1, [pb_1]
+ vbroadcasti128 m4, [pb_2toE_3xF]
+ vperm2i128 m3, m0, 0x03
+ palignr m5, m3, m0, 2
+ palignr m3, m0, 1
+ LOWPASS 5, 3, 0, 2, m1
+ vpbroadcastd m1, [pb_15]
+ pavgb m3, m0
+ punpcklbw m2, m3, m5
+ punpckhbw m3, m5
+ vinserti128 m0, m2, xm3, 1
+ pshufb m5, m1
+ vperm2i128 m1, m2, m3, 0x12
+ lea r3, [strideq*2]
+ vperm2i128 m2, m3, 0x31
+ lea r4, [strideq*3]
+ vperm2i128 m3, m5, 0x31
+ mov r2d, 8
+.loop:
+ mova [dstq+r3*0], m0
+ palignr m0, m1, m0, 2
+ mova [dstq+r3*4], m1
+ palignr m1, m2, m1, 2
+ mova [dstq+r3*8], m2
+ palignr m2, m3, m2, 2
+ mova [dstq+r4*8], m3
+ pshufb m3, m4
+ add dstq, strideq
+ dec r2d
+ jg .loop
+ RET
+
+cglobal vp9_ipred_vl_32x32, 2, 5, 6, dst, stride, l, a
+ movifnidn aq, amp
+ vpbroadcastb m4, [aq+31]
+ vinserti128 m0, m4, [aq+16], 0
+ mova m1, [aq+ 0]
+ vpbroadcastd m5, [pb_1]
+ palignr m2, m0, m1, 2
+ palignr m0, m1, 1
+ LOWPASS 2, 0, 1, 3, m5
+ pavgb m0, m1
+ lea r3, [strideq*2]
+ vperm2i128 m1, m0, m4, 0x31
+ lea r4, [strideq+r3*8]
+ vperm2i128 m3, m2, m4, 0x31
+ mov r2d, 8
+.loop:
+ shufpd m4, m0, m1, 0x05
+ mova [dstq+strideq*0], m0
+ shufpd m5, m2, m3, 0x05
+ mova [dstq+strideq*1], m2
+ palignr m0, m1, m0, 1
+ mova [dstq+r3*8 ], m4
+ psrldq m1, 1
+ mova [dstq+r4 ], m5
+ palignr m2, m3, m2, 1
+ add dstq, r3
+ psrldq m3, 1
+ dec r2d
+ jg .loop
+ RET
+
+cglobal vp9_ipred_vr_32x32, 4, 5, 7, dst, stride, l, a
+ mova m4, [lq+ 0]
+ movu m0, [aq- 1]
+ vpbroadcastd m6, [pb_1]
+ vperm2i128 m2, m4, m0, 0x21
+ pslldq xm5, xm4, 1
+ palignr m3, m2, m4, 1
+ vinserti128 m5, [lq+15], 1
+ LOWPASS 3, 4, 5, 1, m6
+ mova m1, [aq+ 0]
+ vbroadcasti128 m4, [pb_02468ACE_13579BDF]
+ palignr m2, m0, m2, 15
+ LOWPASS 2, 0, 1, 5, m6
+ pshufb m3, m4
+ lea r3, [strideq*2]
+ vpermq m3, m3, q2031
+ pavgb m0, m1
+ vinserti128 m1, m3, xm0, 1
+ lea r4, [strideq+r3*8]
+ vperm2i128 m3, m2, 0x21
+ mov r2d, 8
+.loop:
+ shufpd m4, m1, m0, 0x05
+ mova [dstq+strideq*0], m0
+ shufpd m5, m3, m2, 0x05
+ mova [dstq+strideq*1], m2
+ palignr m0, m1, 15
+ mova [dstq+r3*8 ], m4
+ pslldq m1, 1
+ mova [dstq+r4 ], m5
+ palignr m2, m3, 15
+ add dstq, r3
+ pslldq m3, 1
+ dec r2d
+ jg .loop
+ RET
+%endif
+
; FIXME 127, 128, 129 ?
-----------------------------------------------------------------------
Summary of changes:
libavcodec/x86/vp9dsp_init.c | 13 +-
libavcodec/x86/vp9intrapred.asm | 467 ++++++++++++++++++++++++++--------------
2 files changed, 309 insertions(+), 171 deletions(-)
hooks/post-receive
--
1
0
[ffmpeg] branch master updated. 9a34ddc345 avformat/movenc: remove mvex from final mp4 in hybrid_fragmented mode
by ffmpeg-git@ffmpeg.org 01 Sep '25
by ffmpeg-git@ffmpeg.org 01 Sep '25
01 Sep '25
The branch, master has been updated
via 9a34ddc345ddbc4810799fad65c7fa9d65b15eb1 (commit)
from 126671e73065e29c9582e47bc74774af2200fc8f (commit)
- Log -----------------------------------------------------------------
commit 9a34ddc345ddbc4810799fad65c7fa9d65b15eb1
Author: Zhao Zhili <quinkblack(a)foxmail.com>
AuthorDate: Mon Sep 1 14:55:44 2025 +0800
Commit: James Almer <jamrial(a)gmail.com>
CommitDate: Mon Sep 1 12:41:38 2025 +0000
avformat/movenc: remove mvex from final mp4 in hybrid_fragmented mode
In hybrid_fragmented mode, the first moov at start should contain
mvex tag, since it's fmp4. When writing the moov at the second time,
it's not fmp4 any more, so mvex should be skipped.
diff --git a/libavformat/movenc.c b/libavformat/movenc.c
index efd6329d70..e67a14a7d6 100644
--- a/libavformat/movenc.c
+++ b/libavformat/movenc.c
@@ -5183,7 +5183,11 @@ static int mov_write_moov_tag(AVIOContext *pb, MOVMuxContext *mov,
return ret;
}
}
- if (mov->flags & FF_MOV_FLAG_FRAGMENT)
+ /* Don't write mvex for hybrid_fragmented during mov_write_trailer
+ * (mov->moov_written != 0)
+ */
+ if (mov->flags & FF_MOV_FLAG_FRAGMENT &&
+ !(mov->flags & FF_MOV_FLAG_HYBRID_FRAGMENTED && mov->moov_written))
mov_write_mvex_tag(pb, mov); /* QuickTime requires trak to precede this */
if (mov->mode == MODE_PSP)
diff --git a/tests/fate/mov.mak b/tests/fate/mov.mak
index 421845969b..8e0988000d 100644
--- a/tests/fate/mov.mak
+++ b/tests/fate/mov.mak
@@ -92,7 +92,7 @@ fate-mov-frag-overlap: CMD = framemd5 -i $(TARGET_SAMPLES)/mov/frag_overlap.mp4
fate-mov-mp4-frag-flush: CMD = md5 -f lavfi -i color=blue,format=rgb24,trim=duration=0.04 -f lavfi -i anullsrc,aformat=s16,atrim=duration=2 -c:v png -c:a pcm_s16le -movflags +empty_moov+hybrid_fragmented -frag_duration 1000000 -frag_interleave 1 -bitexact -f mp4
fate-mov-mp4-frag-flush: CMP = oneline
-fate-mov-mp4-frag-flush: REF = c9d0236bde4a0b24a01f6a032fd72e04
+fate-mov-mp4-frag-flush: REF = a1ac687d15552505f3c01a43285f32d0
FATE_MOV_FFMPEG-$(call ALLYES, LAVFI_INDEV COLOR_FILTER FORMAT_FILTER TRIM_FILTER \
ANULLSRC_FILTER AFORMAT_FILTER ATRIM_FILTER \
WRAPPED_AVFRAME_DECODER PCM_S16LE_DECODER PCM_S16BE_DECODER \
diff --git a/tests/ref/lavf/mov_hybrid_frag b/tests/ref/lavf/mov_hybrid_frag
index cd449712b9..64f4e9085e 100644
--- a/tests/ref/lavf/mov_hybrid_frag
+++ b/tests/ref/lavf/mov_hybrid_frag
@@ -1,3 +1,3 @@
-4871796f41234350f1b050317d0288a3 *tests/data/lavf/lavf.mov_hybrid_frag
-358508 tests/data/lavf/lavf.mov_hybrid_frag
+7f79311fa73287c093aa029f58b71476 *tests/data/lavf/lavf.mov_hybrid_frag
+358436 tests/data/lavf/lavf.mov_hybrid_frag
tests/data/lavf/lavf.mov_hybrid_frag CRC=0xbb2b949b
-----------------------------------------------------------------------
Summary of changes:
libavformat/movenc.c | 6 +++++-
tests/fate/mov.mak | 2 +-
tests/ref/lavf/mov_hybrid_frag | 4 ++--
3 files changed, 8 insertions(+), 4 deletions(-)
hooks/post-receive
--
1
0