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]

Reply via email to