FFmpeg
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
vf_eq.c
Go to the documentation of this file.
1 /*
2  * Original MPlayer filters by Richard Felker, Hampa Hug, Daniel Moreno,
3  * and Michael Niedermeyer.
4  *
5  * Copyright (c) 2014 James Darnley <james.darnley@gmail.com>
6  * Copyright (c) 2015 Arwa Arif <arwaarif1994@gmail.com>
7  *
8  * This file is part of FFmpeg.
9  *
10  * FFmpeg is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * FFmpeg is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License along
21  * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
22  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23  */
24 
25 /**
26  * @file
27  * very simple video equalizer
28  */
29 
30 #include "libavfilter/internal.h"
31 #include "libavutil/common.h"
32 #include "libavutil/imgutils.h"
33 #include "libavutil/opt.h"
34 #include "libavutil/pixdesc.h"
35 #include "vf_eq.h"
36 
37 static void create_lut(EQParameters *param)
38 {
39  int i;
40  double g = 1.0 / param->gamma;
41  double lw = 1.0 - param->gamma_weight;
42 
43  for (i = 0; i < 256; i++) {
44  double v = i / 255.0;
45  v = param->contrast * (v - 0.5) + 0.5 + param->brightness;
46 
47  if (v <= 0.0) {
48  param->lut[i] = 0;
49  } else {
50  v = v * lw + pow(v, g) * param->gamma_weight;
51 
52  if (v >= 1.0)
53  param->lut[i] = 255;
54  else
55  param->lut[i] = 256.0 * v;
56  }
57  }
58 
59  param->lut_clean = 1;
60 }
61 
62 static void apply_lut(EQParameters *param, uint8_t *dst, int dst_stride,
63  const uint8_t *src, int src_stride, int w, int h)
64 {
65  int x, y;
66 
67  if (!param->lut_clean)
68  create_lut(param);
69 
70  for (y = 0; y < h; y++) {
71  for (x = 0; x < w; x++) {
72  dst[y * dst_stride + x] = param->lut[src[y * src_stride + x]];
73  }
74  }
75 }
76 
77 static void process_c(EQParameters *param, uint8_t *dst, int dst_stride,
78  const uint8_t *src, int src_stride, int w, int h)
79 {
80  int x, y, pel;
81 
82  int contrast = (int) (param->contrast * 256 * 16);
83  int brightness = ((int) (100.0 * param->brightness + 100.0) * 511) / 200 - 128 - contrast / 32;
84 
85  for (y = 0; y < h; y++) {
86  for (x = 0; x < w; x++) {
87  pel = ((src[y * src_stride + x] * contrast) >> 12) + brightness;
88 
89  if (pel & ~255)
90  pel = (-pel) >> 31;
91 
92  dst[y * dst_stride + x] = pel;
93  }
94  }
95 }
96 
97 static void check_values(EQParameters *param, EQContext *eq)
98 {
99  if (param->contrast == 1.0 && param->brightness == 0.0 && param->gamma == 1.0)
100  param->adjust = NULL;
101  else if (param->gamma == 1.0)
102  param->adjust = eq->process;
103  else
104  param->adjust = apply_lut;
105 }
106 
107 static void set_contrast(EQContext *eq)
108 {
109  eq->contrast = av_clipf(av_expr_eval(eq->contrast_pexpr, eq->var_values, eq), -2.0, 2.0);
110  eq->param[0].contrast = eq->contrast;
111  eq->param[0].lut_clean = 0;
112  check_values(&eq->param[0], eq);
113 }
114 
116 {
117  eq->brightness = av_clipf(av_expr_eval(eq->brightness_pexpr, eq->var_values, eq), -1.0, 1.0);
118  eq->param[0].brightness = eq->brightness;
119  eq->param[0].lut_clean = 0;
120  check_values(&eq->param[0], eq);
121 }
122 
123 static void set_gamma(EQContext *eq)
124 {
125  int i;
126 
127  eq->gamma = av_clipf(av_expr_eval(eq->gamma_pexpr, eq->var_values, eq), 0.1, 10.0);
128  eq->gamma_r = av_clipf(av_expr_eval(eq->gamma_r_pexpr, eq->var_values, eq), 0.1, 10.0);
129  eq->gamma_g = av_clipf(av_expr_eval(eq->gamma_g_pexpr, eq->var_values, eq), 0.1, 10.0);
130  eq->gamma_b = av_clipf(av_expr_eval(eq->gamma_b_pexpr, eq->var_values, eq), 0.1, 10.0);
131  eq->gamma_weight = av_clipf(av_expr_eval(eq->gamma_weight_pexpr, eq->var_values, eq), 0.0, 1.0);
132 
133  eq->param[0].gamma = eq->gamma * eq->gamma_g;
134  eq->param[1].gamma = sqrt(eq->gamma_b / eq->gamma_g);
135  eq->param[2].gamma = sqrt(eq->gamma_r / eq->gamma_g);
136 
137  for (i = 0; i < 3; i++) {
138  eq->param[i].gamma_weight = eq->gamma_weight;
139  eq->param[i].lut_clean = 0;
140  check_values(&eq->param[i], eq);
141  }
142 }
143 
145 {
146  int i;
147 
148  eq->saturation = av_clipf(av_expr_eval(eq->saturation_pexpr, eq->var_values, eq), 0.0, 3.0);
149 
150  for (i = 1; i < 3; i++) {
151  eq->param[i].contrast = eq->saturation;
152  eq->param[i].lut_clean = 0;
153  check_values(&eq->param[i], eq);
154  }
155 }
156 
157 static int set_expr(AVExpr **pexpr, const char *expr, const char *option, void *log_ctx)
158 {
159  int ret;
160  AVExpr *old = NULL;
161 
162  if (*pexpr)
163  old = *pexpr;
164  ret = av_expr_parse(pexpr, expr, var_names, NULL, NULL, NULL, NULL, 0, log_ctx);
165  if (ret < 0) {
166  av_log(log_ctx, AV_LOG_ERROR,
167  "Error when parsing the expression '%s' for %s\n",
168  expr, option);
169  *pexpr = old;
170  return ret;
171  }
172 
173  av_expr_free(old);
174  return 0;
175 }
176 
177 static int initialize(AVFilterContext *ctx)
178 {
179  EQContext *eq = ctx->priv;
180  int ret;
181 
182  eq->process = process_c;
183 
184  if ((ret = set_expr(&eq->contrast_pexpr, eq->contrast_expr, "contrast", ctx)) < 0 ||
185  (ret = set_expr(&eq->brightness_pexpr, eq->brightness_expr, "brightness", ctx)) < 0 ||
186  (ret = set_expr(&eq->saturation_pexpr, eq->saturation_expr, "saturation", ctx)) < 0 ||
187  (ret = set_expr(&eq->gamma_pexpr, eq->gamma_expr, "gamma", ctx)) < 0 ||
188  (ret = set_expr(&eq->gamma_r_pexpr, eq->gamma_r_expr, "gamma_r", ctx)) < 0 ||
189  (ret = set_expr(&eq->gamma_g_pexpr, eq->gamma_g_expr, "gamma_g", ctx)) < 0 ||
190  (ret = set_expr(&eq->gamma_b_pexpr, eq->gamma_b_expr, "gamma_b", ctx)) < 0 ||
191  (ret = set_expr(&eq->gamma_weight_pexpr, eq->gamma_weight_expr, "gamma_weight", ctx)) < 0 )
192  return ret;
193 
194  if (ARCH_X86)
195  ff_eq_init_x86(eq);
196 
197  if (eq->eval_mode == EVAL_MODE_INIT) {
198  set_gamma(eq);
199  set_contrast(eq);
200  set_brightness(eq);
201  set_saturation(eq);
202  }
203 
204  return 0;
205 }
206 
207 static void uninit(AVFilterContext *ctx)
208 {
209  EQContext *eq = ctx->priv;
210 
219 }
220 
221 static int config_props(AVFilterLink *inlink)
222 {
223  EQContext *eq = inlink->dst->priv;
224 
225  eq->var_values[VAR_N] = 0;
226  eq->var_values[VAR_R] = inlink->frame_rate.num == 0 || inlink->frame_rate.den == 0 ?
227  NAN : av_q2d(inlink->frame_rate);
228 
229  return 0;
230 }
231 
233 {
234  static const enum AVPixelFormat pixel_fmts_eq[] = {
242  };
243  AVFilterFormats *fmts_list = ff_make_format_list(pixel_fmts_eq);
244  if (!fmts_list)
245  return AVERROR(ENOMEM);
246  return ff_set_common_formats(ctx, fmts_list);
247 }
248 
249 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
250 
251 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
252 {
253  AVFilterContext *ctx = inlink->dst;
254  AVFilterLink *outlink = inlink->dst->outputs[0];
255  EQContext *eq = ctx->priv;
256  AVFrame *out;
257  int64_t pos = av_frame_get_pkt_pos(in);
258  const AVPixFmtDescriptor *desc;
259  int i;
260 
261  out = ff_get_video_buffer(outlink, inlink->w, inlink->h);
262  if (!out)
263  return AVERROR(ENOMEM);
264 
265  av_frame_copy_props(out, in);
266  desc = av_pix_fmt_desc_get(inlink->format);
267 
268  eq->var_values[VAR_N] = inlink->frame_count;
269  eq->var_values[VAR_POS] = pos == -1 ? NAN : pos;
270  eq->var_values[VAR_T] = TS2T(in->pts, inlink->time_base);
271 
272  if (eq->eval_mode == EVAL_MODE_FRAME) {
273  set_gamma(eq);
274  set_contrast(eq);
275  set_brightness(eq);
276  set_saturation(eq);
277  }
278 
279  for (i = 0; i < desc->nb_components; i++) {
280  int w = inlink->w;
281  int h = inlink->h;
282 
283  if (i == 1 || i == 2) {
284  w = FF_CEIL_RSHIFT(w, desc->log2_chroma_w);
285  h = FF_CEIL_RSHIFT(h, desc->log2_chroma_h);
286  }
287 
288  if (eq->param[i].adjust)
289  eq->param[i].adjust(&eq->param[i], out->data[i], out->linesize[i],
290  in->data[i], in->linesize[i], w, h);
291  else
292  av_image_copy_plane(out->data[i], out->linesize[i],
293  in->data[i], in->linesize[i], w, h);
294  }
295 
296  av_frame_free(&in);
297  return ff_filter_frame(outlink, out);
298 }
299 
300 static inline int set_param(AVExpr **pexpr, const char *args, const char *cmd,
301  void (*set_fn)(EQContext *eq), AVFilterContext *ctx)
302 {
303  EQContext *eq = ctx->priv;
304  int ret;
305  if ((ret = set_expr(pexpr, args, cmd, ctx)) < 0)
306  return ret;
307  if (eq->eval_mode == EVAL_MODE_INIT)
308  set_fn(eq);
309  return 0;
310 }
311 
312 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
313  char *res, int res_len, int flags)
314 {
315  EQContext *eq = ctx->priv;
316 
317 #define SET_PARAM(param_name, set_fn_name) \
318  if (!strcmp(cmd, #param_name)) return set_param(&eq->param_name##_pexpr, args, cmd, set_##set_fn_name, ctx);
319 
320  SET_PARAM(contrast, contrast)
321  else SET_PARAM(brightness, brightness)
322  else SET_PARAM(saturation, saturation)
323  else SET_PARAM(gamma, gamma)
324  else SET_PARAM(gamma_r, gamma)
325  else SET_PARAM(gamma_g, gamma)
326  else SET_PARAM(gamma_b, gamma)
327  else SET_PARAM(gamma_weight, gamma)
328  else return AVERROR(ENOSYS);
329 }
330 
331 static const AVFilterPad eq_inputs[] = {
332  {
333  .name = "default",
334  .type = AVMEDIA_TYPE_VIDEO,
335  .filter_frame = filter_frame,
336  .config_props = config_props,
337  },
338  { NULL }
339 };
340 
341 static const AVFilterPad eq_outputs[] = {
342  {
343  .name = "default",
344  .type = AVMEDIA_TYPE_VIDEO,
345  },
346  { NULL }
347 };
348 
349 #define OFFSET(x) offsetof(EQContext, x)
350 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
351 
352 static const AVOption eq_options[] = {
353  { "contrast", "set the contrast adjustment, negative values give a negative image",
354  OFFSET(contrast_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
355  { "brightness", "set the brightness adjustment",
356  OFFSET(brightness_expr), AV_OPT_TYPE_STRING, {.str = "0.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
357  { "saturation", "set the saturation adjustment",
358  OFFSET(saturation_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
359  { "gamma", "set the initial gamma value",
360  OFFSET(gamma_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
361  { "gamma_r", "gamma value for red",
362  OFFSET(gamma_r_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
363  { "gamma_g", "gamma value for green",
364  OFFSET(gamma_g_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
365  { "gamma_b", "gamma value for blue",
366  OFFSET(gamma_b_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
367  { "gamma_weight", "set the gamma weight which reduces the effect of gamma on bright areas",
368  OFFSET(gamma_weight_expr), AV_OPT_TYPE_STRING, {.str = "1.0"}, CHAR_MIN, CHAR_MAX, FLAGS },
369  { "eval", "specify when to evaluate expressions", OFFSET(eval_mode), AV_OPT_TYPE_INT, {.i64 = EVAL_MODE_INIT}, 0, EVAL_MODE_NB-1, FLAGS, "eval" },
370  { "init", "eval expressions once during initialization", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_INIT}, .flags = FLAGS, .unit = "eval" },
371  { "frame", "eval expressions per-frame", 0, AV_OPT_TYPE_CONST, {.i64=EVAL_MODE_FRAME}, .flags = FLAGS, .unit = "eval" },
372  { NULL }
373 };
374 
376 
378  .name = "eq",
379  .description = NULL_IF_CONFIG_SMALL("Adjust brightness, contrast, gamma, and saturation."),
380  .priv_size = sizeof(EQContext),
381  .priv_class = &eq_class,
382  .inputs = eq_inputs,
383  .outputs = eq_outputs,
386  .init = initialize,
387  .uninit = uninit,
388 };