2 * Copyright (c) 2003 Michael Niedermayer
3 * Copyright (c) 2012 Jeremy Tran
5 * This file is part of FFmpeg.
7 * FFmpeg is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
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 GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 * Apply a hue/saturation filter to the input video
25 * Ported from MPlayer libmpcodecs/vf_hue.c.
29 #include "libavutil/eval.h"
30 #include "libavutil/imgutils.h"
31 #include "libavutil/opt.h"
32 #include "libavutil/pixdesc.h"
39 #define SAT_MIN_VAL -10
40 #define SAT_MAX_VAL 10
42 static const char *const var_names
[] = {
44 "pts", // presentation timestamp expressed in AV_TIME_BASE units
46 "t", // timestamp expressed in seconds
62 float hue_deg
; /* hue expressed in degrees */
63 float hue
; /* hue expressed in radians */
66 AVExpr
*hue_deg_pexpr
;
69 char *saturation_expr
;
70 AVExpr
*saturation_pexpr
;
72 char *brightness_expr
;
73 AVExpr
*brightness_pexpr
;
79 double var_values
[VAR_NB
];
81 uint8_t lut_u
[256][256];
82 uint8_t lut_v
[256][256];
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 { "s", "set the saturation expression", OFFSET(saturation_expr
), AV_OPT_TYPE_STRING
,
91 { .str
= "1" }, .flags
= FLAGS
},
92 { "H", "set the hue angle radians expression", OFFSET(hue_expr
), AV_OPT_TYPE_STRING
,
93 { .str
= NULL
}, .flags
= FLAGS
},
94 { "b", "set the brightness expression", OFFSET(brightness_expr
), AV_OPT_TYPE_STRING
,
95 { .str
= "0" }, .flags
= FLAGS
},
99 AVFILTER_DEFINE_CLASS(hue
);
101 static inline void compute_sin_and_cos(HueContext
*hue
)
104 * Scale the value to the norm of the resulting (U,V) vector, that is
106 * This will be useful in the apply_lut function.
108 hue
->hue_sin
= rint(sin(hue
->hue
) * (1 << 16) * hue
->saturation
);
109 hue
->hue_cos
= rint(cos(hue
->hue
) * (1 << 16) * hue
->saturation
);
112 static inline void create_luma_lut(HueContext
*h
)
114 const float b
= h
->brightness
;
117 for (i
= 0; i
< 256; i
++) {
118 h
->lut_l
[i
] = av_clip_uint8(i
+ b
* 25.5);
122 static inline void create_chrominance_lut(HueContext
*h
, const int32_t c
,
125 int32_t i
, j
, u
, v
, new_u
, new_v
;
128 * If we consider U and V as the components of a 2D vector then its angle
129 * is the hue and the norm is the saturation
131 for (i
= 0; i
< 256; i
++) {
132 for (j
= 0; j
< 256; j
++) {
133 /* Normalize the components from range [16;140] to [-112;112] */
137 * Apply the rotation of the vector : (c * u) - (s * v)
139 * De-normalize the components (without forgetting to scale 128
141 * Finally scale back the result by >> 16
143 new_u
= ((c
* u
) - (s
* v
) + (1 << 15) + (128 << 16)) >> 16;
144 new_v
= ((s
* u
) + (c
* v
) + (1 << 15) + (128 << 16)) >> 16;
146 /* Prevent a potential overflow */
147 h
->lut_u
[i
][j
] = av_clip_uint8(new_u
);
148 h
->lut_v
[i
][j
] = av_clip_uint8(new_v
);
153 static int set_expr(AVExpr
**pexpr_ptr
, char **expr_ptr
,
154 const char *expr
, const char *option
, void *log_ctx
)
160 new_expr
= av_strdup(expr
);
162 return AVERROR(ENOMEM
);
163 ret
= av_expr_parse(&new_pexpr
, expr
, var_names
,
164 NULL
, NULL
, NULL
, NULL
, 0, log_ctx
);
166 av_log(log_ctx
, AV_LOG_ERROR
,
167 "Error when evaluating the expression '%s' for %s\n",
174 av_expr_free(*pexpr_ptr
);
175 *pexpr_ptr
= new_pexpr
;
177 *expr_ptr
= new_expr
;
182 static av_cold
int init(AVFilterContext
*ctx
)
184 HueContext
*hue
= ctx
->priv
;
187 if (hue
->hue_expr
&& hue
->hue_deg_expr
) {
188 av_log(ctx
, AV_LOG_ERROR
,
189 "H and h options are incompatible and cannot be specified "
190 "at the same time\n");
191 return AVERROR(EINVAL
);
194 #define SET_EXPR(expr, option) \
195 if (hue->expr##_expr) do { \
196 ret = set_expr(&hue->expr##_pexpr, &hue->expr##_expr, \
197 hue->expr##_expr, option, ctx); \
201 SET_EXPR(brightness
, "b");
202 SET_EXPR(saturation
, "s");
203 SET_EXPR(hue_deg
, "h");
207 av_log(ctx
, AV_LOG_VERBOSE
,
208 "H_expr:%s h_deg_expr:%s s_expr:%s b_expr:%s\n",
209 hue
->hue_expr
, hue
->hue_deg_expr
, hue
->saturation_expr
, hue
->brightness_expr
);
210 compute_sin_and_cos(hue
);
216 static av_cold
void uninit(AVFilterContext
*ctx
)
218 HueContext
*hue
= ctx
->priv
;
220 av_expr_free(hue
->brightness_pexpr
);
221 av_expr_free(hue
->hue_deg_pexpr
);
222 av_expr_free(hue
->hue_pexpr
);
223 av_expr_free(hue
->saturation_pexpr
);
226 static int query_formats(AVFilterContext
*ctx
)
228 static const enum AVPixelFormat pix_fmts
[] = {
229 AV_PIX_FMT_YUV444P
, AV_PIX_FMT_YUV422P
,
230 AV_PIX_FMT_YUV420P
, AV_PIX_FMT_YUV411P
,
231 AV_PIX_FMT_YUV410P
, AV_PIX_FMT_YUV440P
,
232 AV_PIX_FMT_YUVA444P
, AV_PIX_FMT_YUVA422P
,
237 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
242 static int config_props(AVFilterLink
*inlink
)
244 HueContext
*hue
= inlink
->dst
->priv
;
245 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
247 hue
->hsub
= desc
->log2_chroma_w
;
248 hue
->vsub
= desc
->log2_chroma_h
;
250 hue
->var_values
[VAR_N
] = 0;
251 hue
->var_values
[VAR_TB
] = av_q2d(inlink
->time_base
);
252 hue
->var_values
[VAR_R
] = inlink
->frame_rate
.num
== 0 || inlink
->frame_rate
.den
== 0 ?
253 NAN
: av_q2d(inlink
->frame_rate
);
258 static void apply_luma_lut(HueContext
*s
,
259 uint8_t *ldst
, const int dst_linesize
,
260 uint8_t *lsrc
, const int src_linesize
,
266 for (i
= 0; i
< w
; i
++)
267 ldst
[i
] = s
->lut_l
[lsrc
[i
]];
269 lsrc
+= src_linesize
;
270 ldst
+= dst_linesize
;
274 static void apply_lut(HueContext
*s
,
275 uint8_t *udst
, uint8_t *vdst
, const int dst_linesize
,
276 uint8_t *usrc
, uint8_t *vsrc
, const int src_linesize
,
282 for (i
= 0; i
< w
; i
++) {
283 const int u
= usrc
[i
];
284 const int v
= vsrc
[i
];
286 udst
[i
] = s
->lut_u
[u
][v
];
287 vdst
[i
] = s
->lut_v
[u
][v
];
290 usrc
+= src_linesize
;
291 vsrc
+= src_linesize
;
292 udst
+= dst_linesize
;
293 vdst
+= dst_linesize
;
297 #define TS2D(ts) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts))
298 #define TS2T(ts, tb) ((ts) == AV_NOPTS_VALUE ? NAN : (double)(ts) * av_q2d(tb))
300 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*inpic
)
302 HueContext
*hue
= inlink
->dst
->priv
;
303 AVFilterLink
*outlink
= inlink
->dst
->outputs
[0];
305 const int32_t old_hue_sin
= hue
->hue_sin
, old_hue_cos
= hue
->hue_cos
;
306 const float old_brightness
= hue
->brightness
;
309 if (av_frame_is_writable(inpic
)) {
313 outpic
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
315 av_frame_free(&inpic
);
316 return AVERROR(ENOMEM
);
318 av_frame_copy_props(outpic
, inpic
);
321 hue
->var_values
[VAR_N
] = inlink
->frame_count
;
322 hue
->var_values
[VAR_T
] = TS2T(inpic
->pts
, inlink
->time_base
);
323 hue
->var_values
[VAR_PTS
] = TS2D(inpic
->pts
);
325 if (hue
->saturation_expr
) {
326 hue
->saturation
= av_expr_eval(hue
->saturation_pexpr
, hue
->var_values
, NULL
);
328 if (hue
->saturation
< SAT_MIN_VAL
|| hue
->saturation
> SAT_MAX_VAL
) {
329 hue
->saturation
= av_clip(hue
->saturation
, SAT_MIN_VAL
, SAT_MAX_VAL
);
330 av_log(inlink
->dst
, AV_LOG_WARNING
,
331 "Saturation value not in range [%d,%d]: clipping value to %0.1f\n",
332 SAT_MIN_VAL
, SAT_MAX_VAL
, hue
->saturation
);
336 if (hue
->brightness_expr
) {
337 hue
->brightness
= av_expr_eval(hue
->brightness_pexpr
, hue
->var_values
, NULL
);
339 if (hue
->brightness
< -10 || hue
->brightness
> 10) {
340 hue
->brightness
= av_clipf(hue
->brightness
, -10, 10);
341 av_log(inlink
->dst
, AV_LOG_WARNING
,
342 "Brightness value not in range [%d,%d]: clipping value to %0.1f\n",
343 -10, 10, hue
->brightness
);
347 if (hue
->hue_deg_expr
) {
348 hue
->hue_deg
= av_expr_eval(hue
->hue_deg_pexpr
, hue
->var_values
, NULL
);
349 hue
->hue
= hue
->hue_deg
* M_PI
/ 180;
350 } else if (hue
->hue_expr
) {
351 hue
->hue
= av_expr_eval(hue
->hue_pexpr
, hue
->var_values
, NULL
);
352 hue
->hue_deg
= hue
->hue
* 180 / M_PI
;
355 av_log(inlink
->dst
, AV_LOG_DEBUG
,
356 "H:%0.1f*PI h:%0.1f s:%0.1f b:%0.f t:%0.1f n:%d\n",
357 hue
->hue
/M_PI
, hue
->hue_deg
, hue
->saturation
, hue
->brightness
,
358 hue
->var_values
[VAR_T
], (int)hue
->var_values
[VAR_N
]);
360 compute_sin_and_cos(hue
);
361 if (hue
->is_first
|| (old_hue_sin
!= hue
->hue_sin
|| old_hue_cos
!= hue
->hue_cos
))
362 create_chrominance_lut(hue
, hue
->hue_cos
, hue
->hue_sin
);
364 if (hue
->is_first
|| (old_brightness
!= hue
->brightness
&& hue
->brightness
))
365 create_luma_lut(hue
);
368 if (!hue
->brightness
)
369 av_image_copy_plane(outpic
->data
[0], outpic
->linesize
[0],
370 inpic
->data
[0], inpic
->linesize
[0],
371 inlink
->w
, inlink
->h
);
373 av_image_copy_plane(outpic
->data
[3], outpic
->linesize
[3],
374 inpic
->data
[3], inpic
->linesize
[3],
375 inlink
->w
, inlink
->h
);
378 apply_lut(hue
, outpic
->data
[1], outpic
->data
[2], outpic
->linesize
[1],
379 inpic
->data
[1], inpic
->data
[2], inpic
->linesize
[1],
380 FF_CEIL_RSHIFT(inlink
->w
, hue
->hsub
),
381 FF_CEIL_RSHIFT(inlink
->h
, hue
->vsub
));
383 apply_luma_lut(hue
, outpic
->data
[0], outpic
->linesize
[0],
384 inpic
->data
[0], inpic
->linesize
[0], inlink
->w
, inlink
->h
);
387 av_frame_free(&inpic
);
390 return ff_filter_frame(outlink
, outpic
);
393 static int process_command(AVFilterContext
*ctx
, const char *cmd
, const char *args
,
394 char *res
, int res_len
, int flags
)
396 HueContext
*hue
= ctx
->priv
;
399 #define SET_EXPR(expr, option) \
401 ret = set_expr(&hue->expr##_pexpr, &hue->expr##_expr, \
402 args, option, ctx); \
407 if (!strcmp(cmd
, "h")) {
408 SET_EXPR(hue_deg
, "h");
409 av_freep(&hue
->hue_expr
);
410 } else if (!strcmp(cmd
, "H")) {
412 av_freep(&hue
->hue_deg_expr
);
413 } else if (!strcmp(cmd
, "s")) {
414 SET_EXPR(saturation
, "s");
415 } else if (!strcmp(cmd
, "b")) {
416 SET_EXPR(brightness
, "b");
418 return AVERROR(ENOSYS
);
423 static const AVFilterPad hue_inputs
[] = {
426 .type
= AVMEDIA_TYPE_VIDEO
,
427 .filter_frame
= filter_frame
,
428 .config_props
= config_props
,
433 static const AVFilterPad hue_outputs
[] = {
436 .type
= AVMEDIA_TYPE_VIDEO
,
441 AVFilter ff_vf_hue
= {
443 .description
= NULL_IF_CONFIG_SMALL("Adjust the hue and saturation of the input video."),
444 .priv_size
= sizeof(HueContext
),
447 .query_formats
= query_formats
,
448 .process_command
= process_command
,
449 .inputs
= hue_inputs
,
450 .outputs
= hue_outputs
,
451 .priv_class
= &hue_class
,
452 .flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
,