PR #25201 opened by nmoinvaz URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25201 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25201.patch
Each output pixel sums a distance term over its nine chroma neighbours, and each term paid a division and a `sqrt()`, so a 4:2:0 frame costed every chroma sample up to 36 times. 1. Look up the term of every 8-bit (u, v) pair in a table of doubles, rebuilt when the key changes. The sum adds the same values in the same order. 2. Read the neighbours of interior pixels from three precomputed rows instead of nine bounds-checked lookups. Edge pixels keep the current path, including its reuse of the previous pixel's values for neighbours outside the frame. The alpha plane is identical to master for 4:2:0, 4:2:2 and 4:4:4 input, with and without blend, at 1 and 3 slice threads, and on frames as small as 2x5. The 16-bit path is unchanged. | 1080p, 48 frames, 1 thread | master | this PR | time | |---|---|---|---| | `chromakey=green` | 3.67 s | 1.27 s | -65% | | `chromakey=0x3a5f8a:0.2:0.1` | 4.09 s | 1.53 s | -63% | Measured on an i7-1185G7 with GCC 16. #21327 dropped the `sqrt()` by taking one root of the summed squares instead, which changes the output. >From f36eb758011fe3e209a1e0c4afb70ffb4712a90f Mon Sep 17 00:00:00 2001 From: Nathan Moinvaziri <[email protected]> Date: Sun, 18 Oct 2026 12:56:36 -0700 Subject: [PATCH 1/2] avfilter/vf_chromakey: look up the distance of 8-bit chroma pairs Every pixel summed nine neighbours, each with a division and a sqrt(), so each chroma sample was costed up to 36 times. A table of the distance of every (u, v) pair, built when the key changes, keeps the output identical and makes the filter about 40% faster. --- libavfilter/vf_chromakey.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/libavfilter/vf_chromakey.c b/libavfilter/vf_chromakey.c index 7ef75f2d07..c5074a2c3b 100644 --- a/libavfilter/vf_chromakey.c +++ b/libavfilter/vf_chromakey.c @@ -18,6 +18,7 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "libavutil/mem.h" #include "libavutil/opt.h" #include "libavutil/intreadwrite.h" #include "libavutil/pixdesc.h" @@ -41,6 +42,9 @@ typedef struct ChromakeyContext { int hsub_log2; int vsub_log2; + /* distance term of every 8-bit (u, v) pair, indexed by u << 8 | v */ + double *dist; + int (*do_slice)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); } ChromakeyContext; @@ -48,14 +52,10 @@ typedef struct ChromakeyContext { static uint8_t do_chromakey_pixel(ChromakeyContext *ctx, uint8_t u[9], uint8_t v[9]) { double diff = 0.0; - int du, dv, i; + int i; - for (i = 0; i < 9; ++i) { - du = (int)u[i] - ctx->chromakey_uv[0]; - dv = (int)v[i] - ctx->chromakey_uv[1]; - - diff += sqrt((du * du + dv * dv) / (255.0 * 255.0 * 2)); - } + for (i = 0; i < 9; ++i) + diff += ctx->dist[u[i] << 8 | v[i]]; diff /= 9.0; @@ -295,6 +295,17 @@ static av_cold int config_output(AVFilterLink *outlink) if (!strcmp(avctx->filter->name, "chromakey")) { outlink->alpha_mode = AVALPHA_MODE_STRAIGHT; ctx->do_slice = ctx->depth <= 8 ? do_chromakey_slice : do_chromakey16_slice; + if (ctx->depth <= 8) { + if (!ctx->dist && !(ctx->dist = av_malloc_array(256 * 256, sizeof(*ctx->dist)))) + return AVERROR(ENOMEM); + for (int u = 0; u < 256; u++) { + for (int v = 0; v < 256; v++) { + int du = u - ctx->chromakey_uv[0]; + int dv = v - ctx->chromakey_uv[1]; + ctx->dist[u << 8 | v] = sqrt((du * du + dv * dv) / (255.0 * 255.0 * 2)); + } + } + } } else { ctx->do_slice = ctx->depth <= 8 ? do_chromahold_slice: do_chromahold16_slice; } @@ -314,6 +325,13 @@ static av_cold int config_input(AVFilterLink *inlink) return 0; } +static av_cold void uninit(AVFilterContext *avctx) +{ + ChromakeyContext *ctx = avctx->priv; + + av_freep(&ctx->dist); +} + static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, char *res, int res_len, int flags) { @@ -374,6 +392,7 @@ const FFFilter ff_vf_chromakey = { .p.priv_class = &chromakey_class, .p.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, .priv_size = sizeof(ChromakeyContext), + .uninit = uninit, FILTER_INPUTS(inputs), FILTER_OUTPUTS(outputs), FILTER_PIXFMTS_ARRAY(chromakey_fmts), -- 2.54.0 >From 36386d0144a5ad4354b022bd5423b838b85e3068 Mon Sep 17 00:00:00 2001 From: Nathan Moinvaziri <[email protected]> Date: Sun, 18 Oct 2026 12:58:34 -0700 Subject: [PATCH 2/2] avfilter/vf_chromakey: read the neighbours of interior pixels directly Each of the nine neighbours of every pixel went through a bounds check and its own address computation. Pixels away from the frame edges now read them from three precomputed rows, which keeps the output identical and makes the filter another 40% faster. --- libavfilter/vf_chromakey.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/libavfilter/vf_chromakey.c b/libavfilter/vf_chromakey.c index c5074a2c3b..7ef99d319d 100644 --- a/libavfilter/vf_chromakey.c +++ b/libavfilter/vf_chromakey.c @@ -128,10 +128,28 @@ static int do_chromakey_slice(AVFilterContext *avctx, void *arg, int jobnr, int memset(v, ctx->chromakey_uv[1], sizeof(v)); for (y = slice_start; y < slice_end; ++y) { + const int interior = y > 0 && y < frame->height - 1; + const uint8_t *urow[3], *vrow[3]; + + for (yo = 0; yo < 3 && interior; ++yo) { + urow[yo] = frame->data[1] + frame->linesize[1] * ((y + yo - 1) >> ctx->vsub_log2); + vrow[yo] = frame->data[2] + frame->linesize[2] * ((y + yo - 1) >> ctx->vsub_log2); + } + for (x = 0; x < frame->width; ++x) { - for (yo = 0; yo < 3; ++yo) { - for (xo = 0; xo < 3; ++xo) { - get_pixel_uv(frame, ctx->hsub_log2, ctx->vsub_log2, x + xo - 1, y + yo - 1, &u[yo * 3 + xo], &v[yo * 3 + xo]); + if (interior && x > 0 && x < frame->width - 1) { + /* all nine neighbours lie inside the frame */ + for (yo = 0; yo < 3; ++yo) { + for (xo = 0; xo < 3; ++xo) { + u[yo * 3 + xo] = urow[yo][(x + xo - 1) >> ctx->hsub_log2]; + v[yo * 3 + xo] = vrow[yo][(x + xo - 1) >> ctx->hsub_log2]; + } + } + } else { + for (yo = 0; yo < 3; ++yo) { + for (xo = 0; xo < 3; ++xo) { + get_pixel_uv(frame, ctx->hsub_log2, ctx->vsub_log2, x + xo - 1, y + yo - 1, &u[yo * 3 + xo], &v[yo * 3 + xo]); + } } } -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
