FFmpeg
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
vf_hue.c
Go to the documentation of this file.
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 HUE_DEFAULT_VAL 0
40 #define SAT_DEFAULT_VAL 1
41 
42 #define HUE_DEFAULT_VAL_STRING AV_STRINGIFY(HUE_DEFAULT_VAL)
43 #define SAT_DEFAULT_VAL_STRING AV_STRINGIFY(SAT_DEFAULT_VAL)
44 
45 #define SAT_MIN_VAL -10
46 #define SAT_MAX_VAL 10
47 
48 static const char *const var_names[] = {
49  "n", // frame count
50  "pts", // presentation timestamp expressed in AV_TIME_BASE units
51  "r", // frame rate
52  "t", // timestamp expressed in seconds
53  "tb", // timebase
54  NULL
55 };
56 
57 enum var_name {
64 };
65 
66 typedef struct {
67  const AVClass *class;
68  float hue_deg; /* hue expressed in degrees */
69  float hue; /* hue expressed in radians */
70  char *hue_deg_expr;
71  char *hue_expr;
74  float saturation;
77  int hsub;
78  int vsub;
82  double var_values[VAR_NB];
83 } HueContext;
84 
85 #define OFFSET(x) offsetof(HueContext, x)
86 #define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
87 static const AVOption hue_options[] = {
88  { "h", "set the hue angle degrees expression", OFFSET(hue_deg_expr), AV_OPT_TYPE_STRING,
89  { .str = NULL }, .flags = FLAGS },
90  { "H", "set the hue angle radians expression", OFFSET(hue_expr), AV_OPT_TYPE_STRING,
91  { .str = NULL }, .flags = FLAGS },
92  { "s", "set the saturation expression", OFFSET(saturation_expr), AV_OPT_TYPE_STRING,
93  { .str = NULL }, .flags = FLAGS },
94  { NULL }
95 };
96 
98 
99 static inline void compute_sin_and_cos(HueContext *hue)
100 {
101  /*
102  * Scale the value to the norm of the resulting (U,V) vector, that is
103  * the saturation.
104  * This will be useful in the process_chrominance function.
105  */
106  hue->hue_sin = rint(sin(hue->hue) * (1 << 16) * hue->saturation);
107  hue->hue_cos = rint(cos(hue->hue) * (1 << 16) * hue->saturation);
108 }
109 
110 #define SET_EXPRESSION(attr, name) do { \
111  if (hue->attr##_expr) { \
112  if ((ret = av_expr_parse(&hue->attr##_pexpr, hue->attr##_expr, var_names, \
113  NULL, NULL, NULL, NULL, 0, ctx)) < 0) { \
114  av_log(ctx, AV_LOG_ERROR, \
115  "Parsing failed for expression " #name "='%s'", \
116  hue->attr##_expr); \
117  hue->attr##_expr = old_##attr##_expr; \
118  hue->attr##_pexpr = old_##attr##_pexpr; \
119  return AVERROR(EINVAL); \
120  } else if (old_##attr##_pexpr) { \
121  av_freep(&old_##attr##_expr); \
122  av_expr_free(old_##attr##_pexpr); \
123  old_##attr##_pexpr = NULL; \
124  } \
125  } else { \
126  hue->attr##_expr = old_##attr##_expr; \
127  } \
128 } while (0)
129 
130 static inline int set_options(AVFilterContext *ctx, const char *args)
131 {
132  HueContext *hue = ctx->priv;
133  int ret;
134  char *old_hue_expr, *old_hue_deg_expr, *old_saturation_expr;
135  AVExpr *old_hue_pexpr, *old_hue_deg_pexpr, *old_saturation_pexpr;
136  static const char *shorthand[] = { "h", "s", NULL };
137  old_hue_expr = hue->hue_expr;
138  old_hue_deg_expr = hue->hue_deg_expr;
139  old_saturation_expr = hue->saturation_expr;
140 
141  old_hue_pexpr = hue->hue_pexpr;
142  old_hue_deg_pexpr = hue->hue_deg_pexpr;
143  old_saturation_pexpr = hue->saturation_pexpr;
144 
145  hue->hue_expr = NULL;
146  hue->hue_deg_expr = NULL;
147  hue->saturation_expr = NULL;
148 
149  if ((ret = av_opt_set_from_string(hue, args, shorthand, "=", ":")) < 0)
150  return ret;
151  if (hue->hue_expr && hue->hue_deg_expr) {
152  av_log(ctx, AV_LOG_ERROR,
153  "H and h options are incompatible and cannot be specified "
154  "at the same time\n");
155  hue->hue_expr = old_hue_expr;
156  hue->hue_deg_expr = old_hue_deg_expr;
157 
158  return AVERROR(EINVAL);
159  }
160 
161  SET_EXPRESSION(hue_deg, h);
162  SET_EXPRESSION(hue, H);
163  SET_EXPRESSION(saturation, s);
164 
165  hue->flat_syntax = 0;
166 
167  av_log(ctx, AV_LOG_VERBOSE,
168  "H_expr:%s h_deg_expr:%s s_expr:%s\n",
169  hue->hue_expr, hue->hue_deg_expr, hue->saturation_expr);
170 
171  compute_sin_and_cos(hue);
172 
173  return 0;
174 }
175 
176 static av_cold int init(AVFilterContext *ctx, const char *args)
177 {
178  HueContext *hue = ctx->priv;
179 
180  hue->class = &hue_class;
181  av_opt_set_defaults(hue);
182 
184  hue->hue = HUE_DEFAULT_VAL;
185  hue->hue_deg_pexpr = NULL;
186  hue->hue_pexpr = NULL;
187  hue->flat_syntax = 1;
188 
189  return set_options(ctx, args);
190 }
191 
192 static av_cold void uninit(AVFilterContext *ctx)
193 {
194  HueContext *hue = ctx->priv;
195 
196  av_opt_free(hue);
197 
198  av_free(hue->hue_deg_expr);
200  av_free(hue->hue_expr);
201  av_expr_free(hue->hue_pexpr);
202  av_free(hue->saturation_expr);
204 }
205 
207 {
208  static const enum AVPixelFormat pix_fmts[] = {
214  };
215 
217 
218  return 0;
219 }
220 
221 static int config_props(AVFilterLink *inlink)
222 {
223  HueContext *hue = inlink->dst->priv;
224  const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
225 
226  hue->hsub = desc->log2_chroma_w;
227  hue->vsub = desc->log2_chroma_h;
228 
229  hue->var_values[VAR_N] = 0;
230  hue->var_values[VAR_TB] = av_q2d(inlink->time_base);
231  hue->var_values[VAR_R] = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
232  NAN : av_q2d(inlink->frame_rate);
233 
234  return 0;
235 }
236 
237 static void process_chrominance(uint8_t *udst, uint8_t *vdst, const int dst_linesize,
238  uint8_t *usrc, uint8_t *vsrc, const int src_linesize,
239  int w, int h,
240  const int32_t c, const int32_t s)
241 {
242  int32_t u, v, new_u, new_v;
243  int i;
244 
245  /*
246  * If we consider U and V as the components of a 2D vector then its angle
247  * is the hue and the norm is the saturation
248  */
249  while (h--) {
250  for (i = 0; i < w; i++) {
251  /* Normalize the components from range [16;140] to [-112;112] */
252  u = usrc[i] - 128;
253  v = vsrc[i] - 128;
254  /*
255  * Apply the rotation of the vector : (c * u) - (s * v)
256  * (s * u) + (c * v)
257  * De-normalize the components (without forgetting to scale 128
258  * by << 16)
259  * Finally scale back the result by >> 16
260  */
261  new_u = ((c * u) - (s * v) + (1 << 15) + (128 << 16)) >> 16;
262  new_v = ((s * u) + (c * v) + (1 << 15) + (128 << 16)) >> 16;
263 
264  /* Prevent a potential overflow */
265  udst[i] = av_clip_uint8_c(new_u);
266  vdst[i] = av_clip_uint8_c(new_v);
267  }
268 
269  usrc += src_linesize;
270  vsrc += src_linesize;
271  udst += dst_linesize;
272  vdst += dst_linesize;
273  }
274 }
275 
276 #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
277 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
278 
279 static int filter_frame(AVFilterLink *inlink, AVFilterBufferRef *inpic)
280 {
281  HueContext *hue = inlink->dst->priv;
282  AVFilterLink *outlink = inlink->dst->outputs[0];
283  AVFilterBufferRef *outpic;
284 
285  outpic = ff_get_video_buffer(outlink, AV_PERM_WRITE, outlink->w, outlink->h);
286  if (!outpic) {
287  avfilter_unref_bufferp(&inpic);
288  return AVERROR(ENOMEM);
289  }
290  avfilter_copy_buffer_ref_props(outpic, inpic);
291 
292  if (!hue->flat_syntax) {
293  hue->var_values[VAR_T] = TS2T(inpic->pts, inlink->time_base);
294  hue->var_values[VAR_PTS] = TS2D(inpic->pts);
295 
296  if (hue->saturation_expr) {
298 
299  if (hue->saturation < SAT_MIN_VAL || hue->saturation > SAT_MAX_VAL) {
300  hue->saturation = av_clip(hue->saturation, SAT_MIN_VAL, SAT_MAX_VAL);
301  av_log(inlink->dst, AV_LOG_WARNING,
302  "Saturation value not in range [%d,%d]: clipping value to %0.1f\n",
304  }
305  }
306 
307  if (hue->hue_deg_expr) {
308  hue->hue_deg = av_expr_eval(hue->hue_deg_pexpr, hue->var_values, NULL);
309  hue->hue = hue->hue_deg * M_PI / 180;
310  } else if (hue->hue_expr) {
311  hue->hue = av_expr_eval(hue->hue_pexpr, hue->var_values, NULL);
312  }
313 
314  av_log(inlink->dst, AV_LOG_DEBUG,
315  "H:%0.1f s:%0.f t:%0.1f n:%d\n",
316  hue->hue, hue->saturation,
317  hue->var_values[VAR_T], (int)hue->var_values[VAR_N]);
318 
319  compute_sin_and_cos(hue);
320  }
321 
322  hue->var_values[VAR_N] += 1;
323 
324  av_image_copy_plane(outpic->data[0], outpic->linesize[0],
325  inpic->data[0], inpic->linesize[0],
326  inlink->w, inlink->h);
327 
328  process_chrominance(outpic->data[1], outpic->data[2], outpic->linesize[1],
329  inpic->data[1], inpic->data[2], inpic->linesize[1],
330  inlink->w >> hue->hsub, inlink->h >> hue->vsub,
331  hue->hue_cos, hue->hue_sin);
332 
333  avfilter_unref_bufferp(&inpic);
334  return ff_filter_frame(outlink, outpic);
335 }
336 
337 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
338  char *res, int res_len, int flags)
339 {
340  if (!strcmp(cmd, "reinit"))
341  return set_options(ctx, args);
342  else
343  return AVERROR(ENOSYS);
344 }
345 
346 static const AVFilterPad hue_inputs[] = {
347  {
348  .name = "default",
349  .type = AVMEDIA_TYPE_VIDEO,
350  .filter_frame = filter_frame,
351  .config_props = config_props,
352  .min_perms = AV_PERM_READ,
353  },
354  { NULL }
355 };
356 
357 static const AVFilterPad hue_outputs[] = {
358  {
359  .name = "default",
360  .type = AVMEDIA_TYPE_VIDEO,
361  },
362  { NULL }
363 };
364 
366  .name = "hue",
367  .description = NULL_IF_CONFIG_SMALL("Adjust the hue and saturation of the input video."),
368 
369  .priv_size = sizeof(HueContext),
370 
371  .init = init,
372  .uninit = uninit,
375  .inputs = hue_inputs,
376  .outputs = hue_outputs,
377  .priv_class = &hue_class,
378 };