From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 89A964B2D0 for ; Fri, 23 Jan 2026 18:36:27 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'/CTwxyDATfDXL+Rutyud/JVeybIzxhAjeeKuw/q2ABE=', expected b'0H9H7oGmg2lPrlw1OgYoFscRwPGCA7e1LQxUudqT7Ek=')) header.d=ffmpeg.org header.i=@ffmpeg.org header.a=rsa-sha256 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1769193370; h=mime-version : to : date : message-id : reply-to : subject : list-id : list-archive : list-archive : list-help : list-owner : list-post : list-subscribe : list-unsubscribe : from : cc : content-type : content-transfer-encoding : from; bh=/CTwxyDATfDXL+Rutyud/JVeybIzxhAjeeKuw/q2ABE=; b=CA1m94m9n2qfbCJWyAK6Sl3KSEzmlBT6ahzmz5TS4s/k+XyT0Nrft/O9JLa5gAPq0qkre 0RUY5zktTjHP4d9visrIF3UxEC4xmffXjgX71aN9CEE9ldRzvOI4htU3yu81GEogaWZBiR6 uwBDC8yJ9Ffy6MtEdZc021+LZzlsujuu4PS5/YxsZfpAsNGtwfrb3LqpiafFZBSW9P5JdZX fB4/ZuKHV6HtK/ZLz5xIzmPtzdAmZ4Su7+VbSny51Eq55Lb5WmokmRX976+KsmKWRkatZnE FPReWXKDpTVeExkzfhB9h82GbFufA5f26//lhMH6qeKLo/4K03Tu2PJues/Q== Received: from [172.20.0.4] (unknown [172.20.0.4]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 2DD0969171C; Fri, 23 Jan 2026 20:36:10 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1769193354; b=ZlhWNtkoVcuujvDerZzNQch/bWEG1n2njhcTVivW3A2V4vXApiQwXepHPEIihKhlyjC09 o0xM7PmaUKGk+0gkTBjOtXQ02ZcWM+1ivDNx/m1yARCfLHeBMpsbGjs6WriAysCwg8F2ooN 9/alIl8ZwNrHFX8SX5X45aQRTaM7P1tY0b7W54ZNPlfWDz7dl1wOHUxG21Q3i8FImQi1OL3 wyuVwXGIR+SMHw1WhNhiFnHSSgFxWqF48l5X2PZs61SFC71kl2nzbOr1DL9tsrpkBen+pCm Fr6aUE5JyB86w/isqbdiMOCfqOSk+ux4A26NjzEa9GecGVXrPSDhQWGqksWQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1769193354; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=MdRHT4QT1+R8xFElRFUPuSnaIiFYVMR0vvGKJXuigEM=; b=V8B/bZh2/EUI+8Y1j5/FpzEvyxeO1SR1Yh3BxeGAvB9gqyli7y1oOQCPcsWQO1U7Cep1I EsA0JneuoIs7ckoYma69tGarnKMCQ58oH+j3MS/OpeKPD3UlaiQ9Ekt655NZNr62gUNmozM Wx2da7hjOX/ogUcb5r4HyzCyQbn8pQ0TLsdQ/FNiZiE9nltxLe7PVk4E7nBh/vYw1BH1b50 rlgGISzqH21IVdAaP/Wq9kLKFTzhMTg0JzaHRll5EGqYNlznSN+npr5JsvFMKTkxa6uAQmJ ihkMOXTrXFsaka/EHLijN/BrkRyI1dnKSUmvFYcVJuCP1k+KfRbGM99NuxWg== ARC-Authentication-Results: i=1; ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none; dmarc=pass header.from=ffmpeg.org policy.dmarc=quarantine Authentication-Results: ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=ffmpeg.org policy.dmarc=quarantine DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1769193345; h=content-type : mime-version : content-transfer-encoding : from : to : reply-to : subject : date : from; bh=0H9H7oGmg2lPrlw1OgYoFscRwPGCA7e1LQxUudqT7Ek=; b=z/RTD1Zjlu+sSsa7HZ+s5EJEK1oAIQDBX4qvmc/sSHG7vchtvnDK8O5m/we5H0tatzAM6 gzApuTB850ACE/em2Q1hfHUcQ6Rg5pag+U9wTlnQOCFGJxdKP2DHo09Ydo+fiw3F1upZ9Q4 3v/rLYj7gxPp2ZR569u3POpAJxUi4piY5BE99qlPnQJinXXpMXwWULe7CsP7LSxFk8f08Tu u7xEmRVF+naBEH+JcsC5qCUirdjYVwb0z1TfuD+UeDhTcCcEQUO5OhmQbT+w0Om2zo/C2cp qNJ9MQLrUa2vUVWogC16Zh5xxh49OgAnQF7AvPc+TdELzvP73+AZqCvLynPg== Received: from 69dab402ede7 (code.ffmpeg.org [188.245.149.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTPS id 3B805691615 for ; Fri, 23 Jan 2026 20:35:45 +0200 (EET) MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Date: Fri, 23 Jan 2026 18:35:44 -0000 Message-ID: <176919334539.25.16162750501945514725@4457048688e7> Message-ID-Hash: XQV4TXJMBA75U47KZIQRQSNBWKATQCNK X-Message-ID-Hash: XQV4TXJMBA75U47KZIQRQSNBWKATQCNK X-MailFrom: code@ffmpeg.org X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-ffmpeg-devel.ffmpeg.org-0; header-match-ffmpeg-devel.ffmpeg.org-1; header-match-ffmpeg-devel.ffmpeg.org-2; header-match-ffmpeg-devel.ffmpeg.org-3; emergency; member-moderation X-Mailman-Version: 3.3.10 Precedence: list Reply-To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PR] avfilter/vf_colorize: add support for timeline editing (PR #21564) List-Id: FFmpeg development discussions and patches Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: ygerlach via ffmpeg-devel Cc: ygerlach Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Archived-At: List-Archive: List-Post: 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 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 -- ffmpeg-devel@ffmpeg.org To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org