FFmpeg
 All Data Structures Namespaces 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 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 {
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;
68  float saturation;
71  int hsub;
72  int vsub;
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 
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_ptr, char **expr_ptr,
104  const char *expr, const char *option, void *log_ctx)
105 {
106  int ret;
107  AVExpr *new_pexpr;
108  char *new_expr;
109 
110  new_expr = av_strdup(expr);
111  if (!new_expr)
112  return AVERROR(ENOMEM);
113  ret = av_expr_parse(&new_pexpr, expr, var_names,
114  NULL, NULL, NULL, NULL, 0, log_ctx);
115  if (ret < 0) {
116  av_log(log_ctx, AV_LOG_ERROR,
117  "Error when evaluating the expression '%s' for %s\n",
118  expr, option);
119  av_free(new_expr);
120  return ret;
121  }
122 
123  if (*pexpr_ptr)
124  av_expr_free(*pexpr_ptr);
125  *pexpr_ptr = new_pexpr;
126  av_freep(expr_ptr);
127  *expr_ptr = new_expr;
128 
129  return 0;
130 }
131 
132 static av_cold int init(AVFilterContext *ctx)
133 {
134  HueContext *hue = ctx->priv;
135  int ret;
136 
137  if (hue->hue_expr && hue->hue_deg_expr) {
138  av_log(ctx, AV_LOG_ERROR,
139  "H and h options are incompatible and cannot be specified "
140  "at the same time\n");
141  return AVERROR(EINVAL);
142  }
143 
144 #define SET_EXPR(expr, option) \
145  if (hue->expr##_expr) do { \
146  ret = set_expr(&hue->expr##_pexpr, &hue->expr##_expr, \
147  hue->expr##_expr, option, ctx); \
148  if (ret < 0) \
149  return ret; \
150  } while (0)
151  SET_EXPR(saturation, "s");
152  SET_EXPR(hue_deg, "h");
153  SET_EXPR(hue, "H");
154 #undef SET_EXPR
155 
156  av_log(ctx, AV_LOG_VERBOSE,
157  "H_expr:%s h_deg_expr:%s s_expr:%s\n",
158  hue->hue_expr, hue->hue_deg_expr, hue->saturation_expr);
159  compute_sin_and_cos(hue);
160 
161  return 0;
162 }
163 
164 static av_cold void uninit(AVFilterContext *ctx)
165 {
166  HueContext *hue = ctx->priv;
167 
169  av_expr_free(hue->hue_pexpr);
171 }
172 
174 {
175  static const enum AVPixelFormat pix_fmts[] = {
182  };
183 
185 
186  return 0;
187 }
188 
189 static int config_props(AVFilterLink *inlink)
190 {
191  HueContext *hue = inlink->dst->priv;
192  const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
193 
194  hue->hsub = desc->log2_chroma_w;
195  hue->vsub = desc->log2_chroma_h;
196 
197  hue->var_values[VAR_N] = 0;
198  hue->var_values[VAR_TB] = av_q2d(inlink->time_base);
199  hue->var_values[VAR_R] = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
200  NAN : av_q2d(inlink->frame_rate);
201 
202  return 0;
203 }
204 
205 static void process_chrominance(uint8_t *udst, uint8_t *vdst, const int dst_linesize,
206  uint8_t *usrc, uint8_t *vsrc, const int src_linesize,
207  int w, int h,
208  const int32_t c, const int32_t s)
209 {
210  int32_t u, v, new_u, new_v;
211  int i;
212 
213  /*
214  * If we consider U and V as the components of a 2D vector then its angle
215  * is the hue and the norm is the saturation
216  */
217  while (h--) {
218  for (i = 0; i < w; i++) {
219  /* Normalize the components from range [16;140] to [-112;112] */
220  u = usrc[i] - 128;
221  v = vsrc[i] - 128;
222  /*
223  * Apply the rotation of the vector : (c * u) - (s * v)
224  * (s * u) + (c * v)
225  * De-normalize the components (without forgetting to scale 128
226  * by << 16)
227  * Finally scale back the result by >> 16
228  */
229  new_u = ((c * u) - (s * v) + (1 << 15) + (128 << 16)) >> 16;
230  new_v = ((s * u) + (c * v) + (1 << 15) + (128 << 16)) >> 16;
231 
232  /* Prevent a potential overflow */
233  udst[i] = av_clip_uint8_c(new_u);
234  vdst[i] = av_clip_uint8_c(new_v);
235  }
236 
237  usrc += src_linesize;
238  vsrc += src_linesize;
239  udst += dst_linesize;
240  vdst += dst_linesize;
241  }
242 }
243 
244 #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
245 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
246 
247 static int filter_frame(AVFilterLink *inlink, AVFrame *inpic)
248 {
249  HueContext *hue = inlink->dst->priv;
250  AVFilterLink *outlink = inlink->dst->outputs[0];
251  AVFrame *outpic;
252  int direct = 0;
253 
254  if (av_frame_is_writable(inpic)) {
255  direct = 1;
256  outpic = inpic;
257  } else {
258  outpic = ff_get_video_buffer(outlink, outlink->w, outlink->h);
259  if (!outpic) {
260  av_frame_free(&inpic);
261  return AVERROR(ENOMEM);
262  }
263  av_frame_copy_props(outpic, inpic);
264  }
265 
266  hue->var_values[VAR_N] = inlink->frame_count;
267  hue->var_values[VAR_T] = TS2T(inpic->pts, inlink->time_base);
268  hue->var_values[VAR_PTS] = TS2D(inpic->pts);
269 
270  if (hue->saturation_expr) {
271  hue->saturation = av_expr_eval(hue->saturation_pexpr, hue->var_values, NULL);
272 
273  if (hue->saturation < SAT_MIN_VAL || hue->saturation > SAT_MAX_VAL) {
274  hue->saturation = av_clip(hue->saturation, SAT_MIN_VAL, SAT_MAX_VAL);
275  av_log(inlink->dst, AV_LOG_WARNING,
276  "Saturation value not in range [%d,%d]: clipping value to %0.1f\n",
278  }
279  }
280 
281  if (hue->hue_deg_expr) {
282  hue->hue_deg = av_expr_eval(hue->hue_deg_pexpr, hue->var_values, NULL);
283  hue->hue = hue->hue_deg * M_PI / 180;
284  } else if (hue->hue_expr) {
285  hue->hue = av_expr_eval(hue->hue_pexpr, hue->var_values, NULL);
286  hue->hue_deg = hue->hue * 180 / M_PI;
287  }
288 
289  av_log(inlink->dst, AV_LOG_DEBUG,
290  "H:%0.1f*PI h:%0.1f s:%0.f t:%0.1f n:%d\n",
291  hue->hue/M_PI, hue->hue_deg, hue->saturation,
292  hue->var_values[VAR_T], (int)hue->var_values[VAR_N]);
293 
294  compute_sin_and_cos(hue);
295 
296  if (!direct) {
297  av_image_copy_plane(outpic->data[0], outpic->linesize[0],
298  inpic->data[0], inpic->linesize[0],
299  inlink->w, inlink->h);
300  if (inpic->data[3])
301  av_image_copy_plane(outpic->data[3], outpic->linesize[3],
302  inpic->data[3], inpic->linesize[3],
303  inlink->w, inlink->h);
304  }
305 
306  process_chrominance(outpic->data[1], outpic->data[2], outpic->linesize[1],
307  inpic->data[1], inpic->data[2], inpic->linesize[1],
308  FF_CEIL_RSHIFT(inlink->w, hue->hsub),
309  FF_CEIL_RSHIFT(inlink->h, hue->vsub),
310  hue->hue_cos, hue->hue_sin);
311 
312  if (!direct)
313  av_frame_free(&inpic);
314  return ff_filter_frame(outlink, outpic);
315 }
316 
317 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
318  char *res, int res_len, int flags)
319 {
320  HueContext *hue = ctx->priv;
321  int ret;
322 
323 #define SET_EXPR(expr, option) \
324  do { \
325  ret = set_expr(&hue->expr##_pexpr, &hue->expr##_expr, \
326  args, option, ctx); \
327  if (ret < 0) \
328  return ret; \
329  } while (0)
330 
331  if (!strcmp(cmd, "h")) {
332  SET_EXPR(hue_deg, "h");
333  av_freep(&hue->hue_expr);
334  } else if (!strcmp(cmd, "H")) {
335  SET_EXPR(hue, "H");
336  av_freep(&hue->hue_deg_expr);
337  } else if (!strcmp(cmd, "s")) {
338  SET_EXPR(saturation, "s");
339  } else
340  return AVERROR(ENOSYS);
341 
342  return 0;
343 }
344 
345 static const AVFilterPad hue_inputs[] = {
346  {
347  .name = "default",
348  .type = AVMEDIA_TYPE_VIDEO,
349  .filter_frame = filter_frame,
350  .config_props = config_props,
351  },
352  { NULL }
353 };
354 
355 static const AVFilterPad hue_outputs[] = {
356  {
357  .name = "default",
358  .type = AVMEDIA_TYPE_VIDEO,
359  },
360  { NULL }
361 };
362 
364  .name = "hue",
365  .description = NULL_IF_CONFIG_SMALL("Adjust the hue and saturation of the input video."),
366 
367  .priv_size = sizeof(HueContext),
368 
369  .init = init,
370  .uninit = uninit,
373  .inputs = hue_inputs,
374  .outputs = hue_outputs,
375  .priv_class = &hue_class,
377 };