lavfi/hue: extend debugging message
[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, const char *args)
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_YUVA420P,
169         AV_PIX_FMT_NONE
170     };
171
172     ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
173
174     return 0;
175 }
176
177 static int config_props(AVFilterLink *inlink)
178 {
179     HueContext *hue = inlink->dst->priv;
180     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
181
182     hue->hsub = desc->log2_chroma_w;
183     hue->vsub = desc->log2_chroma_h;
184
185     hue->var_values[VAR_N]  = 0;
186     hue->var_values[VAR_TB] = av_q2d(inlink->time_base);
187     hue->var_values[VAR_R]  = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
188         NAN : av_q2d(inlink->frame_rate);
189
190     return 0;
191 }
192
193 static void process_chrominance(uint8_t *udst, uint8_t *vdst, const int dst_linesize,
194                                 uint8_t *usrc, uint8_t *vsrc, const int src_linesize,
195                                 int w, int h,
196                                 const int32_t c, const int32_t s)
197 {
198     int32_t u, v, new_u, new_v;
199     int i;
200
201     /*
202      * If we consider U and V as the components of a 2D vector then its angle
203      * is the hue and the norm is the saturation
204      */
205     while (h--) {
206         for (i = 0; i < w; i++) {
207             /* Normalize the components from range [16;140] to [-112;112] */
208             u = usrc[i] - 128;
209             v = vsrc[i] - 128;
210             /*
211              * Apply the rotation of the vector : (c * u) - (s * v)
212              *                                    (s * u) + (c * v)
213              * De-normalize the components (without forgetting to scale 128
214              * by << 16)
215              * Finally scale back the result by >> 16
216              */
217             new_u = ((c * u) - (s * v) + (1 << 15) + (128 << 16)) >> 16;
218             new_v = ((s * u) + (c * v) + (1 << 15) + (128 << 16)) >> 16;
219
220             /* Prevent a potential overflow */
221             udst[i] = av_clip_uint8_c(new_u);
222             vdst[i] = av_clip_uint8_c(new_v);
223         }
224
225         usrc += src_linesize;
226         vsrc += src_linesize;
227         udst += dst_linesize;
228         vdst += dst_linesize;
229     }
230 }
231
232 #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
233 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
234
235 static int filter_frame(AVFilterLink *inlink, AVFrame *inpic)
236 {
237     HueContext *hue = inlink->dst->priv;
238     AVFilterLink *outlink = inlink->dst->outputs[0];
239     AVFrame *outpic;
240     int direct = 0;
241
242     if (av_frame_is_writable(inpic)) {
243         direct = 1;
244         outpic = inpic;
245     } else {
246         outpic = ff_get_video_buffer(outlink, outlink->w, outlink->h);
247         if (!outpic) {
248             av_frame_free(&inpic);
249             return AVERROR(ENOMEM);
250         }
251         av_frame_copy_props(outpic, inpic);
252     }
253
254     hue->var_values[VAR_T]   = TS2T(inpic->pts, inlink->time_base);
255     hue->var_values[VAR_PTS] = TS2D(inpic->pts);
256
257     if (hue->saturation_expr) {
258         hue->saturation = av_expr_eval(hue->saturation_pexpr, hue->var_values, NULL);
259
260         if (hue->saturation < SAT_MIN_VAL || hue->saturation > SAT_MAX_VAL) {
261             hue->saturation = av_clip(hue->saturation, SAT_MIN_VAL, SAT_MAX_VAL);
262             av_log(inlink->dst, AV_LOG_WARNING,
263                    "Saturation value not in range [%d,%d]: clipping value to %0.1f\n",
264                    SAT_MIN_VAL, SAT_MAX_VAL, hue->saturation);
265         }
266     }
267
268     if (hue->hue_deg_expr) {
269         hue->hue_deg = av_expr_eval(hue->hue_deg_pexpr, hue->var_values, NULL);
270         hue->hue = hue->hue_deg * M_PI / 180;
271     } else if (hue->hue_expr) {
272         hue->hue = av_expr_eval(hue->hue_pexpr, hue->var_values, NULL);
273         hue->hue_deg = hue->hue * 180 / M_PI;
274     }
275
276     av_log(inlink->dst, AV_LOG_DEBUG,
277            "H:%0.1f*PI h:%0.1f s:%0.f t:%0.1f n:%d\n",
278            hue->hue/M_PI, hue->hue_deg, hue->saturation,
279            hue->var_values[VAR_T], (int)hue->var_values[VAR_N]);
280
281     compute_sin_and_cos(hue);
282
283     hue->var_values[VAR_N] += 1;
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
290     process_chrominance(outpic->data[1], outpic->data[2], outpic->linesize[1],
291                         inpic->data[1],  inpic->data[2],  inpic->linesize[1],
292                         inlink->w >> hue->hsub, inlink->h >> hue->vsub,
293                         hue->hue_cos, hue->hue_sin);
294
295     if (!direct)
296         av_frame_free(&inpic);
297     return ff_filter_frame(outlink, outpic);
298 }
299
300 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
301                            char *res, int res_len, int flags)
302 {
303     HueContext *hue = ctx->priv;
304
305 #define SET_CMD(expr, option)                                          \
306     if (!strcmp(cmd, option)) do {                                     \
307             return set_expr(&hue->expr##_pexpr, args, cmd, ctx);       \
308     } while (0)
309     SET_CMD(hue_deg,    "h");
310     SET_CMD(hue,        "H");
311     SET_CMD(saturation, "s");
312
313     return AVERROR(ENOSYS);
314 }
315
316 static const AVFilterPad hue_inputs[] = {
317     {
318         .name         = "default",
319         .type         = AVMEDIA_TYPE_VIDEO,
320         .filter_frame = filter_frame,
321         .config_props = config_props,
322     },
323     { NULL }
324 };
325
326 static const AVFilterPad hue_outputs[] = {
327     {
328         .name = "default",
329         .type = AVMEDIA_TYPE_VIDEO,
330     },
331     { NULL }
332 };
333
334 AVFilter avfilter_vf_hue = {
335     .name        = "hue",
336     .description = NULL_IF_CONFIG_SMALL("Adjust the hue and saturation of the input video."),
337
338     .priv_size = sizeof(HueContext),
339
340     .init          = init,
341     .uninit        = uninit,
342     .query_formats = query_formats,
343     .process_command = process_command,
344     .inputs          = hue_inputs,
345     .outputs         = hue_outputs,
346     .priv_class      = &hue_class,
347 };