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

This makes all arguments of the colorize filter expressions making this filter 
support timeline editing.

This allows for things like `colorize=hue=2*n` to change the color over time 
creating of rainbow effect.


>From 0b79effa81c529ff0215a679f58d649eca3e51e7 Mon Sep 17 00:00:00 2001
From: Yannis Gerlach <[email protected]>
Date: Tue, 20 Jan 2026 22:36:25 +0100
Subject: [PATCH] add timeline editing to colorize filter

---
 libavfilter/vf_colorize.c | 165 +++++++++++++++++++++++++++++++++-----
 1 file changed, 145 insertions(+), 20 deletions(-)

diff --git a/libavfilter/vf_colorize.c b/libavfilter/vf_colorize.c
index 1ab71b2b87..57c33e2a19 100644
--- a/libavfilter/vf_colorize.c
+++ b/libavfilter/vf_colorize.c
@@ -16,20 +16,69 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */
 
+#include "libavutil/eval.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
 #include "avfilter.h"
 #include "filters.h"
 #include "video.h"
 
+
+static const char *const var_names[] = {
+    "TB",                ///< timebase
+
+    "pts",               ///< original pts in the file of the frame
+    "start_pts",         ///< first PTS in the stream, expressed in TB units
+    "prev_pts",          ///< previous frame PTS
+
+    "t",                 ///< timestamp expressed in seconds
+    "start_t",           ///< first PTS in the stream, expressed in seconds
+    "prev_t",            ///< previous frame time
+
+    "n",                 ///< frame number (starting from zero)
+
+    "ih",                ///< ih: Represents the height of the input video 
frame.
+    "iw",                ///< iw: Represents the width of the input video 
frame.
+
+    NULL
+};
+
+enum var_name {
+    VAR_TB,
+
+    VAR_PTS,
+    VAR_START_PTS,
+    VAR_PREV_PTS,
+
+    VAR_T,
+    VAR_START_T,
+    VAR_PREV_T,
+
+    VAR_N,
+
+    VAR_IH,
+    VAR_IW,
+
+    VAR_VARS_NB
+};
+
 typedef struct ColorizeContext {
     const AVClass *class;
 
-    float hue;
-    float saturation;
-    float lightness;
+    char *hue_str;
+    char *saturation_str;
+    char *lightness_str;
+    char *mix_str;
+
+    AVExpr *hue_expr;
+    AVExpr *saturation_expr;
+    AVExpr *lightness_expr;
+    AVExpr *mix_expr;
+
     float mix;
 
+    double var_values[VAR_VARS_NB];
+
     int depth;
     int c[3];
     int planewidth[4];
@@ -193,21 +242,82 @@ static void rgb2yuv(float r, float g, float b, int *y, 
int *u, int *v, int depth
          (0.04585*224.0/255.0) * b + 0.5) * ((1 << depth) - 1);
 }
 
+#define PARSE_EXPR(NAME) \
+    if ((ret = av_expr_parse(&colorize->NAME ## _expr, colorize->NAME ## _str, 
\
+                             var_names, NULL, NULL, NULL, NULL, 0, ctx)) < 0) 
{ \
+        av_log(ctx, AV_LOG_ERROR, "Error while parsing " #NAME " expression 
'%s'\n", \
+               colorize->NAME ## _str); \
+        return ret; \
+    }
+
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    ColorizeContext *colorize = ctx->priv;
+    int ret;
+
+    PARSE_EXPR(hue);
+    PARSE_EXPR(saturation);
+    PARSE_EXPR(lightness);
+    PARSE_EXPR(mix);
+
+    return 0;
+}
+
+#undef PARSE_EXPR
+
 static int filter_frame(AVFilterLink *inlink, AVFrame *frame)
 {
     AVFilterContext *ctx = inlink->dst;
-    ColorizeContext *s = ctx->priv;
+    ColorizeContext *colorize = ctx->priv;
+    FilterLink *inl = ff_filter_link(inlink);
+    float hue;
+    float saturation;
+    float lightness;
     float c[3];
 
-    hsl2rgb(s->hue, s->saturation, s->lightness, &c[0], &c[1], &c[2]);
-    rgb2yuv(c[0], c[1], c[2], &s->c[0], &s->c[1], &s->c[2], s->depth);
+    // prepare vars
+    if (isnan(colorize->var_values[VAR_START_PTS]))
+        colorize->var_values[VAR_START_PTS] = TS2D(frame->pts);
+    if (isnan(colorize->var_values[VAR_START_T]))
+        colorize->var_values[VAR_START_T] = TS2D(frame->pts) * 
av_q2d(inlink->time_base);
+
+    colorize->var_values[VAR_N  ] = inl->frame_count_out - 1;
+    colorize->var_values[VAR_PTS] = TS2D(frame->pts);
+    colorize->var_values[VAR_T  ] = TS2D(frame->pts) * 
av_q2d(inlink->time_base);
+
+    // eval expr
+    hue           = av_expr_eval(colorize->hue_expr,        
colorize->var_values, NULL);
+    saturation    = av_expr_eval(colorize->saturation_expr, 
colorize->var_values, NULL);
+    lightness     = av_expr_eval(colorize->lightness_expr,  
colorize->var_values, NULL);
+    colorize->mix = av_expr_eval(colorize->mix_expr,        
colorize->var_values, NULL);
+
+    hsl2rgb(hue, saturation, lightness, &c[0], &c[1], &c[2]);
+    rgb2yuv(c[0], c[1], c[2], &colorize->c[0], &colorize->c[1], 
&colorize->c[2], colorize->depth);
 
     ff_filter_execute(ctx, do_slice, frame, NULL,
-                      FFMIN(s->planeheight[1], ff_filter_get_nb_threads(ctx)));
+                      FFMIN(colorize->planeheight[1], 
ff_filter_get_nb_threads(ctx)));
+
+    colorize->var_values[VAR_PREV_PTS] = colorize->var_values[VAR_PTS];
+    colorize->var_values[VAR_PREV_T]   = colorize->var_values[VAR_T];
 
     return ff_filter_frame(ctx->outputs[0], frame);
 }
 
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    ColorizeContext *colorize = ctx->priv;
+
+    av_expr_free(colorize->hue_expr);
+    colorize->hue_expr = NULL;
+    av_expr_free(colorize->saturation_expr);
+    colorize->saturation_expr = NULL;
+    av_expr_free(colorize->lightness_expr);
+    colorize->lightness_expr = NULL;
+    av_expr_free(colorize->mix_expr);
+    colorize->mix_expr = NULL;
+}
+
 static const enum AVPixelFormat pixel_fmts[] = {
     AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P,
     AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P,
@@ -232,19 +342,32 @@ static const enum AVPixelFormat pixel_fmts[] = {
 static av_cold int config_input(AVFilterLink *inlink)
 {
     AVFilterContext *ctx = inlink->dst;
-    ColorizeContext *s = ctx->priv;
+    ColorizeContext *colorize = ctx->priv;
     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
     int depth;
 
-    s->depth = depth = desc->comp[0].depth;
+    colorize->depth = depth = desc->comp[0].depth;
 
-    s->planewidth[1] = s->planewidth[2] = AV_CEIL_RSHIFT(inlink->w, 
desc->log2_chroma_w);
-    s->planewidth[0] = s->planewidth[3] = inlink->w;
-    s->planeheight[1] = s->planeheight[2] = AV_CEIL_RSHIFT(inlink->h, 
desc->log2_chroma_h);
-    s->planeheight[0] = s->planeheight[3] = inlink->h;
+    colorize->planewidth[1] = colorize->planewidth[2] = 
AV_CEIL_RSHIFT(inlink->w, desc->log2_chroma_w);
+    colorize->planewidth[0] = colorize->planewidth[3] = inlink->w;
+    colorize->planeheight[1] = colorize->planeheight[2] = 
AV_CEIL_RSHIFT(inlink->h, desc->log2_chroma_h);
+    colorize->planeheight[0] = colorize->planeheight[3] = inlink->h;
 
-    s->do_plane_slice[0] = depth <= 8 ? colorizey_slice8 : colorizey_slice16;
-    s->do_plane_slice[1] = depth <= 8 ? colorize_slice8 : colorize_slice16;
+    colorize->do_plane_slice[0] = depth <= 8 ? colorizey_slice8 : 
colorizey_slice16;
+    colorize->do_plane_slice[1] = depth <= 8 ? colorize_slice8 : 
colorize_slice16;
+
+    // set expression variables
+    colorize->var_values[VAR_TB]        = av_q2d(inlink->time_base);
+
+    colorize->var_values[VAR_PREV_PTS]  = NAN;
+    colorize->var_values[VAR_PREV_T]    = NAN;
+    colorize->var_values[VAR_START_PTS] = NAN;
+    colorize->var_values[VAR_START_T]   = NAN;
+
+    colorize->var_values[VAR_N]         = 0.0;
+
+    colorize->var_values[VAR_IH]        = NAN;
+    colorize->var_values[VAR_IW]        = NAN;
 
     return 0;
 }
@@ -260,13 +383,13 @@ static const AVFilterPad colorize_inputs[] = {
 };
 
 #define OFFSET(x) offsetof(ColorizeContext, x)
-#define VF 
AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM
+#define VF AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
 
 static const AVOption colorize_options[] = {
-    { "hue",        "set the hue",                     OFFSET(hue),        
AV_OPT_TYPE_FLOAT, {.dbl=0},  0, 360, VF },
-    { "saturation", "set the saturation",              OFFSET(saturation), 
AV_OPT_TYPE_FLOAT, {.dbl=0.5},0,   1, VF },
-    { "lightness",  "set the lightness",               OFFSET(lightness),  
AV_OPT_TYPE_FLOAT, {.dbl=0.5},0,   1, VF },
-    { "mix",        "set the mix of source lightness", OFFSET(mix),        
AV_OPT_TYPE_FLOAT, {.dbl=1},  0,   1, VF },
+    { "hue",        "set the hue",                     OFFSET(hue_str),        
AV_OPT_TYPE_STRING, {.str="0"},   0, 0, .flags=VF },
+    { "saturation", "set the saturation",              OFFSET(saturation_str), 
AV_OPT_TYPE_STRING, {.str="0.5"}, 0, 0, .flags=VF },
+    { "lightness",  "set the lightness",               OFFSET(lightness_str),  
AV_OPT_TYPE_STRING, {.str="0.5"}, 0, 0, .flags=VF },
+    { "mix",        "set the mix of source lightness", OFFSET(mix_str),        
AV_OPT_TYPE_STRING, {.str="1"},   0, 0, .flags=VF },
     { NULL }
 };
 
@@ -282,4 +405,6 @@ const FFFilter ff_vf_colorize = {
     FILTER_OUTPUTS(ff_video_default_filterpad),
     FILTER_PIXFMTS_ARRAY(pixel_fmts),
     .process_command = ff_filter_process_command,
+    .init = init,
+    .uninit = uninit,
 };
-- 
2.52.0

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

Reply via email to