2 * Copyright (c) 2013 Clément Bœsch
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 #include "libavutil/opt.h"
22 #include "libavutil/eval.h"
23 #include "libavutil/avassert.h"
31 struct keypoint *next;
38 char *comp_points_str[NB_COMP];
39 uint8_t graph[NB_COMP][256];
42 #define OFFSET(x) offsetof(CurvesContext, x)
43 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
44 static const AVOption curves_options[] = {
45 { "red", "set red points coordinates", OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
46 { "r", "set red points coordinates", OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
47 { "green", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
48 { "g", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
49 { "blue", "set blue points coordinates", OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
50 { "b", "set blue points coordinates", OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
54 AVFILTER_DEFINE_CLASS(curves);
56 static struct keypoint *make_point(double x, double y, struct keypoint *next)
58 struct keypoint *point = av_mallocz(sizeof(*point));
68 static int parse_points_str(AVFilterContext *ctx, struct keypoint **points, const char *s)
70 char *p = (char *)s; // strtod won't alter the string
71 struct keypoint *last = NULL;
73 /* construct a linked list based on the key points string */
75 struct keypoint *point = make_point(0, 0, NULL);
77 return AVERROR(ENOMEM);
78 point->x = av_strtod(p, &p); if (p && *p) p++;
79 point->y = av_strtod(p, &p); if (p && *p) p++;
80 if (point->x < 0 || point->x > 1 || point->y < 0 || point->y > 1) {
81 av_log(ctx, AV_LOG_ERROR, "Invalid key point coordinates (%f;%f), "
82 "x and y must be in the [0;1] range.\n", point->x, point->y);
83 return AVERROR(EINVAL);
88 if ((int)(last->x * 255) >= (int)(point->x * 255)) {
89 av_log(ctx, AV_LOG_ERROR, "Key point coordinates (%f;%f) "
90 "and (%f;%f) are too close from each other or not "
91 "strictly increasing on the x-axis\n",
92 last->x, last->y, point->x, point->y);
93 return AVERROR(EINVAL);
100 /* auto insert first key point if missing at x=0 */
102 last = make_point(0, 0, NULL);
104 return AVERROR(ENOMEM);
105 last->x = last->y = 0;
107 } else if ((*points)->x != 0.) {
108 struct keypoint *newfirst = make_point(0, 0, *points);
110 return AVERROR(ENOMEM);
116 /* auto insert last key point if missing at x=1 */
118 struct keypoint *point = make_point(1, 1, NULL);
120 return AVERROR(ENOMEM);
127 static int get_nb_points(const struct keypoint *d)
138 * Natural cubic spline interpolation
139 * Finding curves using Cubic Splines notes by Steven Rauch and John Stockie.
140 * @see http://people.math.sfu.ca/~stockie/teaching/macm316/notes/splines.pdf
142 static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint *points)
145 const struct keypoint *point;
148 int n = get_nb_points(points); // number of splines
150 double (*matrix)[3] = av_calloc(n, sizeof(*matrix));
151 double *h = av_malloc((n - 1) * sizeof(*h));
152 double *r = av_calloc(n, sizeof(*r));
154 if (!matrix || !h || !r) {
155 ret = AVERROR(ENOMEM);
159 /* h(i) = x(i+1) - x(i) */
161 for (point = points; point; point = point->next) {
163 h[i] = point->x - xprev;
168 /* right-side of the polynomials, will be modified to contains the solution */
170 for (i = 1; i < n - 1; i++) {
171 double yp = point->y,
173 yn = point->next->next->y;
174 r[i] = 6 * ((yn-yc)/h[i] - (yc-yp)/h[i-1]);
178 #define B 0 /* sub diagonal (below main) */
179 #define M 1 /* main diagonal (center) */
180 #define A 2 /* sup diagonal (above main) */
182 /* left side of the polynomials into a tridiagonal matrix. */
183 matrix[0][M] = matrix[n - 1][M] = 1;
184 for (i = 1; i < n - 1; i++) {
185 matrix[i][B] = h[i-1];
186 matrix[i][M] = 2 * (h[i-1] + h[i]);
190 /* tridiagonal solving of the linear system */
191 for (i = 1; i < n; i++) {
192 double den = matrix[i][M] - matrix[i][B] * matrix[i-1][A];
193 double k = den ? 1./den : 1.;
195 r[i] = (r[i] - matrix[i][B] * r[i - 1]) * k;
197 for (i = n - 2; i >= 0; i--)
198 r[i] = r[i] - matrix[i][A] * r[i + 1];
200 /* compute the graph with x=[0..255] */
203 av_assert0(point->next); // always at least 2 key points
204 while (point->next) {
205 double yc = point->y;
206 double yn = point->next->y;
209 double b = (yn-yc)/h[i] - h[i]*r[i]/2. - h[i]*(r[i+1]-r[i])/6.;
210 double c = r[i] / 2.;
211 double d = (r[i+1] - r[i]) / (6.*h[i]);
214 int x_start = point->x * 255;
215 int x_end = point->next->x * 255;
217 av_assert0(x_start >= 0 && x_start <= 255 &&
218 x_end >= 0 && x_end <= 255);
220 for (x = x_start; x <= x_end; x++) {
221 double xx = (x - x_start) * 1/255.;
222 double yy = a + b*xx + c*xx*xx + d*xx*xx*xx;
223 y[x] = av_clipf(yy, 0, 1) * 255;
224 av_log(ctx, AV_LOG_DEBUG, "f(%f)=%f -> y[%d]=%d\n", xx, yy, x, y[x]);
238 static av_cold int init(AVFilterContext *ctx, const char *args)
241 CurvesContext *curves = ctx->priv;
242 struct keypoint *comp_points[NB_COMP] = {0};
244 curves->class = &curves_class;
245 av_opt_set_defaults(curves);
247 if ((ret = av_set_options_string(curves, args, "=", ":")) < 0)
250 for (i = 0; i < NB_COMP; i++) {
251 ret = parse_points_str(ctx, comp_points + i, curves->comp_points_str[i]);
254 ret = interpolate(ctx, curves->graph[i], comp_points[i]);
259 if (av_log_get_level() >= AV_LOG_VERBOSE) {
260 for (i = 0; i < NB_COMP; i++) {
261 struct keypoint *point = comp_points[i];
262 av_log(ctx, AV_LOG_VERBOSE, "#%d points:", i);
264 av_log(ctx, AV_LOG_VERBOSE, " (%f;%f)", point->x, point->y);
267 av_log(ctx, AV_LOG_VERBOSE, "\n");
268 av_log(ctx, AV_LOG_VERBOSE, "#%d values:", i);
269 for (j = 0; j < 256; j++)
270 av_log(ctx, AV_LOG_VERBOSE, " %02X", curves->graph[i][j]);
271 av_log(ctx, AV_LOG_VERBOSE, "\n");
275 for (i = 0; i < NB_COMP; i++) {
276 struct keypoint *point = comp_points[i];
278 struct keypoint *next = point->next;
288 static int query_formats(AVFilterContext *ctx)
290 static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_RGB24, AV_PIX_FMT_NONE};
291 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
295 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
297 int x, y, i, direct = 0;
298 AVFilterContext *ctx = inlink->dst;
299 CurvesContext *curves = ctx->priv;
300 AVFilterLink *outlink = inlink->dst->outputs[0];
305 if (av_frame_is_writable(in)) {
309 out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
312 return AVERROR(ENOMEM);
314 av_frame_copy_props(out, in);
320 for (y = 0; y < inlink->h; y++) {
322 const uint8_t *srcp = src;
324 for (x = 0; x < inlink->w; x++)
325 for (i = 0; i < NB_COMP; i++, dstp++, srcp++)
326 *dstp = curves->graph[i][*srcp];
327 dst += out->linesize[0];
328 src += in ->linesize[0];
334 return ff_filter_frame(outlink, out);
337 static const AVFilterPad curves_inputs[] = {
340 .type = AVMEDIA_TYPE_VIDEO,
341 .filter_frame = filter_frame,
346 static const AVFilterPad curves_outputs[] = {
349 .type = AVMEDIA_TYPE_VIDEO,
354 AVFilter avfilter_vf_curves = {
356 .description = NULL_IF_CONFIG_SMALL("Adjust components curves."),
357 .priv_size = sizeof(CurvesContext),
359 .query_formats = query_formats,
360 .inputs = curves_inputs,
361 .outputs = curves_outputs,
362 .priv_class = &curves_class,