PR #25097 opened by romainhedouin
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25097
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25097.patch

# Summary of changes

The luma coefficients (av_q2d of cr/cg/cb, used by the desaturation step)
and the mobius constants only depend on the options and the peak of the
frame, but they were recalculated for every pixel. Compute them once per
frame in filter_frame(). The expressions are unchanged.

1080p, one thread, arm64, desat=2(default):
none is 1.33x faster
hable is 1.39x faster
reinhard is 1.64x faster
mobius is 1.69x faster
gamma is 1.1x faster

With desat=0 the biggest change is mobius being 1.11x faster

Output is byte-identical to master on 5376 combinations of algorithm,
param, desat, peak, colourspace, pixel format (gbrpf32/gbrapf32) and
thread count.
Only tested on arm64 with clang: the result relies on the
default floating point contraction (with -ffp-contract=off only NaN
payloads differ).

Noticed during a code review sweep of ffmpeg using an LLM.

<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->



>From 0a850e8f1fd2f8dae7e89411cc1bdd673c433c7c Mon Sep 17 00:00:00 2001
From: romainhedouin <[email protected]>
Date: Thu, 15 Oct 2026 13:23:30 +0200
Subject: [PATCH] avfilter/vf_tonemap: compute per-frame values once per frame

The luma coefficients and the mobius constants only depend on the
options and the peak of the frame, but they were recalculated for every
pixel. Calculate them once per frame. The expressions are unchanged.

Found during a code review sweep of ffmpeg using an LLM.
---
 libavfilter/vf_tonemap.c | 34 +++++++++++++++++++++++++++-------
 1 file changed, 27 insertions(+), 7 deletions(-)

diff --git a/libavfilter/vf_tonemap.c b/libavfilter/vf_tonemap.c
index c5be4b1510..0a14c51e3c 100644
--- a/libavfilter/vf_tonemap.c
+++ b/libavfilter/vf_tonemap.c
@@ -60,6 +60,10 @@ typedef struct TonemapContext {
     double peak;
 
     const AVLumaCoefficients *coeffs;
+
+    /* constant for a whole frame, computed in filter_frame() */
+    double cr, cg, cb;
+    float mobius_a, mobius_b, mobius_k;
 } TonemapContext;
 
 static av_cold int init(AVFilterContext *ctx)
@@ -93,17 +97,24 @@ static float hable(float in)
     return (in * (in * a + b * c) + d * e) / (in * (in * a + b) + d * f) - e / 
f;
 }
 
-static float mobius(float in, float j, double peak)
+static void mobius_params(TonemapContext *s, float j, double peak)
 {
     float a, b;
 
-    if (in <= j)
-        return in;
-
     a = -j * j * (peak - 1.0f) / (j * j - 2.0f * j + peak);
     b = (j * j - 2.0f * j * peak + peak) / FFMAX(peak - 1.0f, 1e-6);
 
-    return (b * b + 2.0f * b * j + j * j) / (b - a) * (in + a) / (in + b);
+    s->mobius_a = a;
+    s->mobius_b = b;
+    s->mobius_k = (b * b + 2.0f * b * j + j * j) / (b - a);
+}
+
+static float mobius(float in, float j, float a, float b, float k)
+{
+    if (in <= j)
+        return in;
+
+    return k * (in + a) / (in + b);
 }
 
 #define MIX(x,y,a) (x) * (1 - (a)) + (y) * (a)
@@ -126,7 +137,7 @@ static void tonemap(TonemapContext *s, AVFrame *out, const 
AVFrame *in,
 
     /* desaturate to prevent unnatural colors */
     if (s->desat > 0) {
-        float luma = av_q2d(s->coeffs->cr) * *r_in + av_q2d(s->coeffs->cg) * 
*g_in + av_q2d(s->coeffs->cb) * *b_in;
+        float luma = s->cr * *r_in + s->cg * *g_in + s->cb * *b_in;
         float overbright = FFMAX(luma - s->desat, 1e-6) / FFMAX(luma, 1e-6);
         *r_out = MIX(*r_in, luma, overbright);
         *g_out = MIX(*g_in, luma, overbright);
@@ -161,7 +172,7 @@ static void tonemap(TonemapContext *s, AVFrame *out, const 
AVFrame *in,
         sig = sig / (sig + s->param) * (peak + s->param) / peak;
         break;
     case TONEMAP_MOBIUS:
-        sig = mobius(sig, s->param, peak);
+        sig = mobius(sig, s->param, s->mobius_a, s->mobius_b, s->mobius_k);
         break;
     }
 
@@ -251,6 +262,15 @@ static int filter_frame(AVFilterLink *link, AVFrame *in)
         s->desat = 0;
     }
 
+    /* compute what is the same for every pixel of the frame only once */
+    if (s->desat > 0) {
+        s->cr = av_q2d(s->coeffs->cr);
+        s->cg = av_q2d(s->coeffs->cg);
+        s->cb = av_q2d(s->coeffs->cb);
+    }
+    if (s->tonemap == TONEMAP_MOBIUS)
+        mobius_params(s, s->param, peak);
+
     /* do the tone map */
     td.out = out;
     td.in = in;
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to