lavfi: add timeline support.
[ffmpeg.git] / libavfilter / vf_hue.c
1 /*
2  * Copyright (c) 2003 Michael Niedermayer
3  * Copyright (c) 2012 Jeremy Tran
4  *
5  * This file is part of FFmpeg.
6  *
7  * FFmpeg is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * FFmpeg is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 /**
23  * @file
24  * Apply a hue/saturation filter to the input video
25  * Ported from MPlayer libmpcodecs/vf_hue.c.
26  */
27
28 #include <float.h>
29 #include "libavutil/eval.h"
30 #include "libavutil/imgutils.h"
31 #include "libavutil/opt.h"
32 #include "libavutil/pixdesc.h"
33
34 #include "avfilter.h"
35 #include "formats.h"
36 #include "internal.h"
37 #include "video.h"
38
39 #define SAT_MIN_VAL -10
40 #define SAT_MAX_VAL 10
41
42 static const char *const var_names[] = {
43     "n",   // frame count
44     "pts", // presentation timestamp expressed in AV_TIME_BASE units
45     "r",   // frame rate
46     "t",   // timestamp expressed in seconds
47     "tb",  // timebase
48     NULL
49 };
50
51 enum var_name {
52     VAR_N,
53     VAR_PTS,
54     VAR_R,
55     VAR_T,
56     VAR_TB,
57     VAR_NB
58 };
59
60 typedef struct {
61     const    AVClass *class;
62     float    hue_deg; /* hue expressed in degrees */
63     float    hue; /* hue expressed in radians */
64     char     *hue_deg_expr;
65     char     *hue_expr;
66     AVExpr   *hue_deg_pexpr;
67     AVExpr   *hue_pexpr;
68     float    saturation;
69     char     *saturation_expr;
70     AVExpr   *saturation_pexpr;
71     int      hsub;
72     int      vsub;
73     int32_t hue_sin;
74     int32_t hue_cos;
75     double   var_values[VAR_NB];
76 } HueContext;
77
78 #define OFFSET(x) offsetof(HueContext, x)
79 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
80 static const AVOption hue_options[] = {
81     { "h", "set the hue angle degrees expression", OFFSET(hue_deg_expr), AV_OPT_TYPE_STRING,
82       { .str = NULL }, .flags = FLAGS },
83     { "s", "set the saturation expression", OFFSET(saturation_expr), AV_OPT_TYPE_STRING,
84       { .str = "1" }, .flags = FLAGS },
85     { "H", "set the hue angle radians expression", OFFSET(hue_expr), AV_OPT_TYPE_STRING,
86       { .str = NULL }, .flags = FLAGS },
87     { NULL }
88 };
89
90 AVFILTER_DEFINE_CLASS(hue);
91
92 static inline void compute_sin_and_cos(HueContext *hue)
93 {
94     /*
95      * Scale the value to the norm of the resulting (U,V) vector, that is
96      * the saturation.
97      * This will be useful in the process_chrominance function.
98      */
99     hue->hue_sin = rint(sin(hue->hue) * (1 << 16) * hue->saturation);
100     hue->hue_cos = rint(cos(hue->hue) * (1 << 16) * hue->saturation);
101 }
102
103 static int set_expr(AVExpr **pexpr, const char *expr, const char *option, void *log_ctx)
104 {
105     int ret;
106     AVExpr *old = NULL;
107
108     if (*pexpr)
109         old = *pexpr;
110     ret = av_expr_parse(pexpr, expr, var_names,
111                         NULL, NULL, NULL, NULL, 0, log_ctx);
112     if (ret < 0) {
113         av_log(log_ctx, AV_LOG_ERROR,
114                "Error when evaluating the expression '%s' for %s\n",
115                expr, option);
116         *pexpr = old;
117         return ret;
118     }
119     av_expr_free(old);
120     return 0;
121 }
122
123 static av_cold int init(AVFilterContext *ctx)
124 {
125     HueContext *hue = ctx->priv;
126     int ret;
127
128     if (hue->hue_expr && hue->hue_deg_expr) {
129         av_log(ctx, AV_LOG_ERROR,
130                "H and h options are incompatible and cannot be specified "
131                "at the same time\n");
132         return AVERROR(EINVAL);
133     }
134
135 #define SET_EXPR(expr, option)                                          \
136     if (hue->expr##_expr) do {                                          \
137         ret = set_expr(&hue->expr##_pexpr, hue->expr##_expr, option, ctx); \
138         if (ret < 0)                                                    \
139             return ret;                                                 \
140     } while (0)
141     SET_EXPR(saturation, "s");
142     SET_EXPR(hue_deg,    "h");
143     SET_EXPR(hue,        "H");
144
145     av_log(ctx, AV_LOG_VERBOSE,
146            "H_expr:%s h_deg_expr:%s s_expr:%s\n",
147            hue->hue_expr, hue->hue_deg_expr, hue->saturation_expr);
148     compute_sin_and_cos(hue);
149
150     return 0;
151 }
152
153 static av_cold void uninit(AVFilterContext *ctx)
154 {
155     HueContext *hue = ctx->priv;
156
157     av_expr_free(hue->hue_deg_pexpr);
158     av_expr_free(hue->hue_pexpr);
159     av_expr_free(hue->saturation_pexpr);
160 }
161
162 static int query_formats(AVFilterContext *ctx)
163 {
164     static const enum AVPixelFormat pix_fmts[] = {
165         AV_PIX_FMT_YUV444P,      AV_PIX_FMT_YUV422P,
166         AV_PIX_FMT_YUV420P,      AV_PIX_FMT_YUV411P,
167         AV_PIX_FMT_YUV410P,      AV_PIX_FMT_YUV440P,
168         AV_PIX_FMT_YUVA444P,     AV_PIX_FMT_YUVA422P,
169         AV_PIX_FMT_YUVA420P,
170         AV_PIX_FMT_NONE
171     };
172
173     ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
174
175     return 0;
176 }
177
178 static int config_props(AVFilterLink *inlink)
179 {
180     HueContext *hue = inlink->dst->priv;
181     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
182
183     hue->hsub = desc->log2_chroma_w;
184     hue->vsub = desc->log2_chroma_h;
185
186     hue->var_values[VAR_N]  = 0;
187     hue->var_values[VAR_TB] = av_q2d(inlink->time_base);
188     hue->var_values[VAR_R]  = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
189         NAN : av_q2d(inlink->frame_rate);
190
191     return 0;
192 }
193
194 static void process_chrominance(uint8_t *udst, uint8_t *vdst, const int dst_linesize,
195                                 uint8_t *usrc, uint8_t *vsrc, const int src_linesize,
196                                 int w, int h,
197                                 const int32_t c, const int32_t s)
198 {
199     int32_t u, v, new_u, new_v;
200     int i;
201
202     /*
203      * If we consider U and V as the components of a 2D vector then its angle
204      * is the hue and the norm is the saturation
205      */
206     while (h--) {
207         for (i = 0; i < w; i++) {
208             /* Normalize the components from range [16;140] to [-112;112] */
209             u = usrc[i] - 128;
210             v = vsrc[i] - 128;
211             /*
212              * Apply the rotation of the vector : (c * u) - (s * v)
213              *                                    (s * u) + (c * v)
214              * De-normalize the components (without forgetting to scale 128
215              * by << 16)
216              * Finally scale back the result by >> 16
217              */
218             new_u = ((c * u) - (s * v) + (1 << 15) + (128 << 16)) >> 16;
219             new_v = ((s * u) + (c * v) + (1 << 15) + (128 << 16)) >> 16;
220
221             /* Prevent a potential overflow */
222             udst[i] = av_clip_uint8_c(new_u);
223             vdst[i] = av_clip_uint8_c(new_v);
224         }
225
226         usrc += src_linesize;
227         vsrc += src_linesize;
228         udst += dst_linesize;
229         vdst += dst_linesize;
230     }
231 }
232
233 #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
234 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
235
236 static int filter_frame(AVFilterLink *inlink, AVFrame *inpic)
237 {
238     HueContext *hue = inlink->dst->priv;
239     AVFilterLink *outlink = inlink->dst->outputs[0];
240     AVFrame *outpic;
241     int direct = 0;
242
243     if (av_frame_is_writable(inpic)) {
244         direct = 1;
245         outpic = inpic;
246     } else {
247         outpic = ff_get_video_buffer(outlink, outlink->w, outlink->h);
248         if (!outpic) {
249             av_frame_free(&inpic);
250             return AVERROR(ENOMEM);
251         }
252         av_frame_copy_props(outpic, inpic);
253     }
254
255     hue->var_values[VAR_N]   = inlink->frame_count;
256     hue->var_values[VAR_T]   = TS2T(inpic->pts, inlink->time_base);
257     hue->var_values[VAR_PTS] = TS2D(inpic->pts);
258
259     if (hue->saturation_expr) {
260         hue->saturation = av_expr_eval(hue->saturation_pexpr, hue->var_values, NULL);
261
262         if (hue->saturation < SAT_MIN_VAL || hue->saturation > SAT_MAX_VAL) {
263             hue->saturation = av_clip(hue->saturation, SAT_MIN_VAL, SAT_MAX_VAL);
264             av_log(inlink->dst, AV_LOG_WARNING,
265                    "Saturation value not in range [%d,%d]: clipping value to %0.1f\n",
266                    SAT_MIN_VAL, SAT_MAX_VAL, hue->saturation);
267         }
268     }
269
270     if (hue->hue_deg_expr) {
271         hue->hue_deg = av_expr_eval(hue->hue_deg_pexpr, hue->var_values, NULL);
272         hue->hue = hue->hue_deg * M_PI / 180;
273     } else if (hue->hue_expr) {
274         hue->hue = av_expr_eval(hue->hue_pexpr, hue->var_values, NULL);
275         hue->hue_deg = hue->hue * 180 / M_PI;
276     }
277
278     av_log(inlink->dst, AV_LOG_DEBUG,
279            "H:%0.1f*PI h:%0.1f s:%0.f t:%0.1f n:%d\n",
280            hue->hue/M_PI, hue->hue_deg, hue->saturation,
281            hue->var_values[VAR_T], (int)hue->var_values[VAR_N]);
282
283     compute_sin_and_cos(hue);
284
285     if (!direct) {
286         av_image_copy_plane(outpic->data[0], outpic->linesize[0],
287                             inpic->data[0],  inpic->linesize[0],
288                             inlink->w, inlink->h);
289         if (inpic->data[3])
290             av_image_copy_plane(outpic->data[3], outpic->linesize[3],
291                                 inpic->data[3],  inpic->linesize[3],
292                                 inlink->w, inlink->h);
293     }
294
295     process_chrominance(outpic->data[1], outpic->data[2], outpic->linesize[1],
296                         inpic->data[1],  inpic->data[2],  inpic->linesize[1],
297                         inlink->w >> hue->hsub, inlink->h >> hue->vsub,
298                         hue->hue_cos, hue->hue_sin);
299
300     if (!direct)
301         av_frame_free(&inpic);
302     return ff_filter_frame(outlink, outpic);
303 }
304
305 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
306                            char *res, int res_len, int flags)
307 {
308     HueContext *hue = ctx->priv;
309
310 #define SET_CMD(expr, option) do {                                 \
311     if (!strcmp(cmd, option))                                      \
312         return set_expr(&hue->expr##_pexpr, args, cmd, ctx);       \
313 } while (0)
314     SET_CMD(hue_deg,    "h");
315     SET_CMD(hue,        "H");
316     SET_CMD(saturation, "s");
317
318     return AVERROR(ENOSYS);
319 }
320
321 static const AVFilterPad hue_inputs[] = {
322     {
323         .name         = "default",
324         .type         = AVMEDIA_TYPE_VIDEO,
325         .filter_frame = filter_frame,
326         .config_props = config_props,
327     },
328     { NULL }
329 };
330
331 static const AVFilterPad hue_outputs[] = {
332     {
333         .name = "default",
334         .type = AVMEDIA_TYPE_VIDEO,
335     },
336     { NULL }
337 };
338
339 AVFilter avfilter_vf_hue = {
340     .name        = "hue",
341     .description = NULL_IF_CONFIG_SMALL("Adjust the hue and saturation of the input video."),
342
343     .priv_size = sizeof(HueContext),
344
345     .init          = init,
346     .uninit        = uninit,
347     .query_formats = query_formats,
348     .process_command = process_command,
349     .inputs          = hue_inputs,
350     .outputs         = hue_outputs,
351     .priv_class      = &hue_class,
352     .flags           = AVFILTER_FLAG_SUPPORT_TIMELINE,
353 };