lavfi/hue: add named options support
[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/imgutils.h"
30 #include "libavutil/opt.h"
31 #include "libavutil/pixdesc.h"
32
33 #include "avfilter.h"
34 #include "formats.h"
35 #include "internal.h"
36 #include "video.h"
37
38 #define HUE_DEFAULT_VAL 0
39 #define SAT_DEFAULT_VAL 1
40
41 typedef struct {
42     const    AVClass *class;
43     float    hue_deg; /* hue expressed in degrees */
44     float    hue; /* hue expressed in radians */
45     float    saturation;
46     int      hsub;
47     int      vsub;
48     int32_t hue_sin;
49     int32_t hue_cos;
50 } HueContext;
51
52 #define OFFSET(x) offsetof(HueContext, x)
53 static const AVOption hue_options[] = {
54     { "h", "set the hue angle degrees", OFFSET(hue_deg), AV_OPT_TYPE_FLOAT,
55       { -FLT_MAX }, -FLT_MAX, FLT_MAX, AV_OPT_FLAG_VIDEO_PARAM },
56     { "H", "set the hue angle radians", OFFSET(hue), AV_OPT_TYPE_FLOAT,
57       { -FLT_MAX }, -FLT_MAX, FLT_MAX, AV_OPT_FLAG_VIDEO_PARAM },
58     { "s", "set the saturation value", OFFSET(saturation), AV_OPT_TYPE_FLOAT,
59       { SAT_DEFAULT_VAL }, -10, 10, AV_OPT_FLAG_VIDEO_PARAM },
60     { NULL }
61 };
62
63 AVFILTER_DEFINE_CLASS(hue);
64
65 static av_cold int init(AVFilterContext *ctx, const char *args)
66 {
67     HueContext *hue = ctx->priv;
68     float h = HUE_DEFAULT_VAL, s = SAT_DEFAULT_VAL;
69     int n, ret;
70     char c1 = 0, c2 = 0;
71     char *equal;
72
73     hue->class = &hue_class;
74
75     /* named options syntax */
76     if (equal = strchr(args, '=')) {
77         av_opt_set_defaults(hue);
78         if ((ret = av_set_options_string(hue, args, "=", ":")) < 0)
79             return ret;
80         if (hue->hue != -FLT_MAX && hue->hue_deg != -FLT_MAX) {
81             av_log(ctx, AV_LOG_ERROR,
82                    "H and h options are incompatible and cannot be specified "
83                    "at the same time\n");
84             return AVERROR(EINVAL);
85         }
86         if (hue->hue == -FLT_MAX)
87             hue->hue = HUE_DEFAULT_VAL;
88     /* compatibility syntax */
89     } else {
90     if (args) {
91         n = sscanf(args, "%f%c%f%c", &h, &c1, &s, &c2);
92         if (n != 0 && n != 1 && (n != 3 || c1 != ':')) {
93             av_log(ctx, AV_LOG_ERROR,
94                    "Invalid syntax for argument '%s': "
95                    "must be in the form 'hue[:saturation]'\n", args);
96             return AVERROR(EINVAL);
97         }
98
99     if (s < -10 || s > 10) {
100         av_log(ctx, AV_LOG_ERROR,
101                "Invalid value for saturation %0.1f: "
102                "must be included between range -10 and +10\n", s);
103         return AVERROR(EINVAL);
104     }
105     }
106     hue->hue_deg = h;
107     hue->saturation = s;
108     }
109
110     if (hue->hue_deg != -FLT_MAX)
111         /* Convert angle from degrees to radians */
112         hue->hue = hue->hue_deg * M_PI / 180;
113
114     return 0;
115 }
116
117 static av_cold void uninit(AVFilterContext *ctx)
118 {
119     HueContext *hue = ctx->priv;
120
121     av_opt_free(hue);
122 }
123
124 static int query_formats(AVFilterContext *ctx)
125 {
126     static const enum PixelFormat pix_fmts[] = {
127         PIX_FMT_YUV444P,      PIX_FMT_YUV422P,
128         PIX_FMT_YUV420P,      PIX_FMT_YUV411P,
129         PIX_FMT_YUV410P,      PIX_FMT_YUV440P,
130         PIX_FMT_YUVA420P,
131         PIX_FMT_NONE
132     };
133
134     ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
135
136     return 0;
137 }
138
139 static int config_props(AVFilterLink *inlink)
140 {
141     HueContext *hue = inlink->dst->priv;
142     const AVPixFmtDescriptor *desc = &av_pix_fmt_descriptors[inlink->format];
143
144     hue->hsub = desc->log2_chroma_w;
145     hue->vsub = desc->log2_chroma_h;
146     /*
147      * Scale the value to the norm of the resulting (U,V) vector, that is
148      * the saturation.
149      * This will be useful in the process_chrominance function.
150      */
151     hue->hue_sin = rint(sin(hue->hue) * (1 << 16) * hue->saturation);
152     hue->hue_cos = rint(cos(hue->hue) * (1 << 16) * hue->saturation);
153
154     return 0;
155 }
156
157 static void process_chrominance(uint8_t *udst, uint8_t *vdst, const int dst_linesize,
158                                 uint8_t *usrc, uint8_t *vsrc, const int src_linesize,
159                                 int w, int h,
160                                 const int32_t c, const int32_t s)
161 {
162     int32_t u, v, new_u, new_v;
163     int i;
164
165     /*
166      * If we consider U and V as the components of a 2D vector then its angle
167      * is the hue and the norm is the saturation
168      */
169     while (h--) {
170         for (i = 0; i < w; i++) {
171             /* Normalize the components from range [16;140] to [-112;112] */
172             u = usrc[i] - 128;
173             v = vsrc[i] - 128;
174             /*
175              * Apply the rotation of the vector : (c * u) - (s * v)
176              *                                    (s * u) + (c * v)
177              * De-normalize the components (without forgetting to scale 128
178              * by << 16)
179              * Finally scale back the result by >> 16
180              */
181             new_u = ((c * u) - (s * v) + (1 << 15) + (128 << 16)) >> 16;
182             new_v = ((s * u) + (c * v) + (1 << 15) + (128 << 16)) >> 16;
183
184             /* Prevent a potential overflow */
185             udst[i] = av_clip_uint8_c(new_u);
186             vdst[i] = av_clip_uint8_c(new_v);
187         }
188
189         usrc += src_linesize;
190         vsrc += src_linesize;
191         udst += dst_linesize;
192         vdst += dst_linesize;
193     }
194 }
195
196 static int draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
197 {
198     HueContext        *hue    = inlink->dst->priv;
199     AVFilterBufferRef *inpic  = inlink->cur_buf;
200     AVFilterBufferRef *outpic = inlink->dst->outputs[0]->out_buf;
201     uint8_t *inrow[3], *outrow[3]; // 0 : Y, 1 : U, 2 : V
202     int plane;
203
204     inrow[0]  = inpic->data[0]  + y * inpic->linesize[0];
205     outrow[0] = outpic->data[0] + y * outpic->linesize[0];
206
207     for (plane = 1; plane < 3; plane++) {
208         inrow[plane]  = inpic->data[plane]  + (y >> hue->vsub) * inpic->linesize[plane];
209         outrow[plane] = outpic->data[plane] + (y >> hue->vsub) * outpic->linesize[plane];
210     }
211
212     av_image_copy_plane(outrow[0], outpic->linesize[0],
213                         inrow[0],  inpic->linesize[0],
214                         inlink->w, inlink->h);
215
216     process_chrominance(outrow[1], outrow[2], outpic->linesize[1],
217                         inrow[1], inrow[2], inpic->linesize[1],
218                         inlink->w >> hue->hsub, inlink->h >> hue->vsub,
219                         hue->hue_cos, hue->hue_sin);
220
221     return ff_draw_slice(inlink->dst->outputs[0], y, h, slice_dir);
222 }
223
224 AVFilter avfilter_vf_hue = {
225     .name        = "hue",
226     .description = NULL_IF_CONFIG_SMALL("Adjust the hue and saturation of the input video."),
227
228     .priv_size = sizeof(HueContext),
229
230     .init          = init,
231     .uninit        = uninit,
232     .query_formats = query_formats,
233
234     .inputs = (const AVFilterPad[]) {
235         {
236             .name         = "default",
237             .type         = AVMEDIA_TYPE_VIDEO,
238             .draw_slice   = draw_slice,
239             .config_props = config_props,
240             .min_perms    = AV_PERM_READ,
241         },
242         { .name = NULL }
243     },
244     .outputs = (const AVFilterPad[]) {
245         {
246             .name         = "default",
247             .type         = AVMEDIA_TYPE_VIDEO,
248         },
249         { .name = NULL }
250     }
251 };