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/bprint.h"
23 #include "libavutil/eval.h"
24 #include "libavutil/file.h"
25 #include "libavutil/intreadwrite.h"
26 #include "libavutil/avassert.h"
27 #include "libavutil/pixdesc.h"
29 #include "drawutils.h"
41 struct keypoint
*next
;
48 PRESET_COLOR_NEGATIVE
,
51 PRESET_INCREASE_CONTRAST
,
53 PRESET_LINEAR_CONTRAST
,
54 PRESET_MEDIUM_CONTRAST
,
56 PRESET_STRONG_CONTRAST
,
64 char *comp_points_str
[NB_COMP
+ 1];
65 char *comp_points_str_all
;
66 uint8_t graph
[NB_COMP
+ 1][256];
72 typedef struct ThreadData
{
76 #define OFFSET(x) offsetof(CurvesContext, x)
77 #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
78 static const AVOption curves_options
[] = {
79 { "preset", "select a color curves preset", OFFSET(preset
), AV_OPT_TYPE_INT
, {.i64
=PRESET_NONE
}, PRESET_NONE
, NB_PRESETS
-1, FLAGS
, "preset_name" },
80 { "none", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_NONE
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
81 { "color_negative", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_COLOR_NEGATIVE
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
82 { "cross_process", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_CROSS_PROCESS
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
83 { "darker", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_DARKER
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
84 { "increase_contrast", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_INCREASE_CONTRAST
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
85 { "lighter", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_LIGHTER
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
86 { "linear_contrast", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_LINEAR_CONTRAST
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
87 { "medium_contrast", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_MEDIUM_CONTRAST
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
88 { "negative", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_NEGATIVE
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
89 { "strong_contrast", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_STRONG_CONTRAST
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
90 { "vintage", NULL
, 0, AV_OPT_TYPE_CONST
, {.i64
=PRESET_VINTAGE
}, INT_MIN
, INT_MAX
, FLAGS
, "preset_name" },
91 { "master","set master points coordinates",OFFSET(comp_points_str
[NB_COMP
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
92 { "m", "set master points coordinates",OFFSET(comp_points_str
[NB_COMP
]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
93 { "red", "set red points coordinates", OFFSET(comp_points_str
[0]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
94 { "r", "set red points coordinates", OFFSET(comp_points_str
[0]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
95 { "green", "set green points coordinates", OFFSET(comp_points_str
[1]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
96 { "g", "set green points coordinates", OFFSET(comp_points_str
[1]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
97 { "blue", "set blue points coordinates", OFFSET(comp_points_str
[2]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
98 { "b", "set blue points coordinates", OFFSET(comp_points_str
[2]), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
99 { "all", "set points coordinates for all components", OFFSET(comp_points_str_all
), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
100 { "psfile", "set Photoshop curves file name", OFFSET(psfile
), AV_OPT_TYPE_STRING
, {.str
=NULL
}, .flags
= FLAGS
},
104 AVFILTER_DEFINE_CLASS(curves
);
106 static const struct {
111 } curves_presets
[] = {
112 [PRESET_COLOR_NEGATIVE
] = {
113 "0/1 0.129/1 0.466/0.498 0.725/0 1/0",
114 "0/1 0.109/1 0.301/0.498 0.517/0 1/0",
115 "0/1 0.098/1 0.235/0.498 0.423/0 1/0",
117 [PRESET_CROSS_PROCESS
] = {
118 "0.25/0.156 0.501/0.501 0.686/0.745",
119 "0.25/0.188 0.38/0.501 0.745/0.815 1/0.815",
120 "0.231/0.094 0.709/0.874",
122 [PRESET_DARKER
] = { .master
= "0.5/0.4" },
123 [PRESET_INCREASE_CONTRAST
] = { .master
= "0.149/0.066 0.831/0.905 0.905/0.98" },
124 [PRESET_LIGHTER
] = { .master
= "0.4/0.5" },
125 [PRESET_LINEAR_CONTRAST
] = { .master
= "0.305/0.286 0.694/0.713" },
126 [PRESET_MEDIUM_CONTRAST
] = { .master
= "0.286/0.219 0.639/0.643" },
127 [PRESET_NEGATIVE
] = { .master
= "0/1 1/0" },
128 [PRESET_STRONG_CONTRAST
] = { .master
= "0.301/0.196 0.592/0.6 0.686/0.737" },
130 "0/0.11 0.42/0.51 1/0.95",
132 "0/0.22 0.49/0.44 1/0.8",
136 static struct keypoint
*make_point(double x
, double y
, struct keypoint
*next
)
138 struct keypoint
*point
= av_mallocz(sizeof(*point
));
148 static int parse_points_str(AVFilterContext
*ctx
, struct keypoint
**points
, const char *s
)
150 char *p
= (char *)s
; // strtod won't alter the string
151 struct keypoint
*last
= NULL
;
153 /* construct a linked list based on the key points string */
155 struct keypoint
*point
= make_point(0, 0, NULL
);
157 return AVERROR(ENOMEM
);
158 point
->x
= av_strtod(p
, &p
); if (p
&& *p
) p
++;
159 point
->y
= av_strtod(p
, &p
); if (p
&& *p
) p
++;
160 if (point
->x
< 0 || point
->x
> 1 || point
->y
< 0 || point
->y
> 1) {
161 av_log(ctx
, AV_LOG_ERROR
, "Invalid key point coordinates (%f;%f), "
162 "x and y must be in the [0;1] range.\n", point
->x
, point
->y
);
163 return AVERROR(EINVAL
);
168 if ((int)(last
->x
* 255) >= (int)(point
->x
* 255)) {
169 av_log(ctx
, AV_LOG_ERROR
, "Key point coordinates (%f;%f) "
170 "and (%f;%f) are too close from each other or not "
171 "strictly increasing on the x-axis\n",
172 last
->x
, last
->y
, point
->x
, point
->y
);
173 return AVERROR(EINVAL
);
180 /* auto insert first key point if missing at x=0 */
182 last
= make_point(0, 0, NULL
);
184 return AVERROR(ENOMEM
);
185 last
->x
= last
->y
= 0;
187 } else if ((*points
)->x
!= 0.) {
188 struct keypoint
*newfirst
= make_point(0, 0, *points
);
190 return AVERROR(ENOMEM
);
196 /* auto insert last key point if missing at x=1 */
198 struct keypoint
*point
= make_point(1, 1, NULL
);
200 return AVERROR(ENOMEM
);
207 static int get_nb_points(const struct keypoint
*d
)
218 * Natural cubic spline interpolation
219 * Finding curves using Cubic Splines notes by Steven Rauch and John Stockie.
220 * @see http://people.math.sfu.ca/~stockie/teaching/macm316/notes/splines.pdf
222 static int interpolate(AVFilterContext
*ctx
, uint8_t *y
, const struct keypoint
*points
)
225 const struct keypoint
*point
;
228 int n
= get_nb_points(points
); // number of splines
230 double (*matrix
)[3] = av_calloc(n
, sizeof(*matrix
));
231 double *h
= av_malloc((n
- 1) * sizeof(*h
));
232 double *r
= av_calloc(n
, sizeof(*r
));
234 if (!matrix
|| !h
|| !r
) {
235 ret
= AVERROR(ENOMEM
);
239 /* h(i) = x(i+1) - x(i) */
241 for (point
= points
; point
; point
= point
->next
) {
243 h
[i
] = point
->x
- xprev
;
248 /* right-side of the polynomials, will be modified to contains the solution */
250 for (i
= 1; i
< n
- 1; i
++) {
251 double yp
= point
->y
,
253 yn
= point
->next
->next
->y
;
254 r
[i
] = 6 * ((yn
-yc
)/h
[i
] - (yc
-yp
)/h
[i
-1]);
258 #define BD 0 /* sub diagonal (below main) */
259 #define MD 1 /* main diagonal (center) */
260 #define AD 2 /* sup diagonal (above main) */
262 /* left side of the polynomials into a tridiagonal matrix. */
263 matrix
[0][MD
] = matrix
[n
- 1][MD
] = 1;
264 for (i
= 1; i
< n
- 1; i
++) {
265 matrix
[i
][BD
] = h
[i
-1];
266 matrix
[i
][MD
] = 2 * (h
[i
-1] + h
[i
]);
267 matrix
[i
][AD
] = h
[i
];
270 /* tridiagonal solving of the linear system */
271 for (i
= 1; i
< n
; i
++) {
272 double den
= matrix
[i
][MD
] - matrix
[i
][BD
] * matrix
[i
-1][AD
];
273 double k
= den
? 1./den
: 1.;
275 r
[i
] = (r
[i
] - matrix
[i
][BD
] * r
[i
- 1]) * k
;
277 for (i
= n
- 2; i
>= 0; i
--)
278 r
[i
] = r
[i
] - matrix
[i
][AD
] * r
[i
+ 1];
280 /* compute the graph with x=[0..255] */
283 av_assert0(point
->next
); // always at least 2 key points
284 while (point
->next
) {
285 double yc
= point
->y
;
286 double yn
= point
->next
->y
;
289 double b
= (yn
-yc
)/h
[i
] - h
[i
]*r
[i
]/2. - h
[i
]*(r
[i
+1]-r
[i
])/6.;
290 double c
= r
[i
] / 2.;
291 double d
= (r
[i
+1] - r
[i
]) / (6.*h
[i
]);
294 int x_start
= point
->x
* 255;
295 int x_end
= point
->next
->x
* 255;
297 av_assert0(x_start
>= 0 && x_start
<= 255 &&
298 x_end
>= 0 && x_end
<= 255);
300 for (x
= x_start
; x
<= x_end
; x
++) {
301 double xx
= (x
- x_start
) * 1/255.;
302 double yy
= a
+ b
*xx
+ c
*xx
*xx
+ d
*xx
*xx
*xx
;
303 y
[x
] = av_clipf(yy
, 0, 1) * 255;
304 av_log(ctx
, AV_LOG_DEBUG
, "f(%f)=%f -> y[%d]=%d\n", xx
, yy
, x
, y
[x
]);
318 static int parse_psfile(AVFilterContext
*ctx
, const char *fname
)
320 CurvesContext
*curves
= ctx
->priv
;
323 int i
, ret
, av_unused(version
), nb_curves
;
325 static const int comp_ids
[] = {3, 0, 1, 2};
327 av_bprint_init(&ptstr
, 0, AV_BPRINT_SIZE_AUTOMATIC
);
329 ret
= av_file_map(fname
, &buf
, &size
, 0, NULL
);
333 #define READ16(dst) do { \
335 ret = AVERROR_INVALIDDATA; \
338 dst = AV_RB16(buf); \
345 for (i
= 0; i
< FFMIN(nb_curves
, FF_ARRAY_ELEMS(comp_ids
)); i
++) {
347 av_bprint_clear(&ptstr
);
349 for (n
= 0; n
< nb_points
; n
++) {
353 av_bprintf(&ptstr
, "%f/%f ", x
/ 255., y
/ 255.);
356 char **pts
= &curves
->comp_points_str
[comp_ids
[i
]];
358 *pts
= av_strdup(ptstr
.str
);
359 av_log(ctx
, AV_LOG_DEBUG
, "curves %d (intid=%d) [%d points]: [%s]\n",
360 i
, comp_ids
[i
], nb_points
, *pts
);
362 ret
= AVERROR(ENOMEM
);
369 av_bprint_finalize(&ptstr
, NULL
);
370 av_file_unmap(buf
, size
);
374 static av_cold
int init(AVFilterContext
*ctx
)
377 CurvesContext
*curves
= ctx
->priv
;
378 struct keypoint
*comp_points
[NB_COMP
+ 1] = {0};
379 char **pts
= curves
->comp_points_str
;
380 const char *allp
= curves
->comp_points_str_all
;
382 //if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all)
383 // allp = curves_presets[curves->preset].all;
386 for (i
= 0; i
< NB_COMP
; i
++) {
388 pts
[i
] = av_strdup(allp
);
390 return AVERROR(ENOMEM
);
394 if (curves
->psfile
) {
395 ret
= parse_psfile(ctx
, curves
->psfile
);
400 if (curves
->preset
!= PRESET_NONE
) {
401 #define SET_COMP_IF_NOT_SET(n, name) do { \
402 if (!pts[n] && curves_presets[curves->preset].name) { \
403 pts[n] = av_strdup(curves_presets[curves->preset].name); \
405 return AVERROR(ENOMEM); \
408 SET_COMP_IF_NOT_SET(0, r
);
409 SET_COMP_IF_NOT_SET(1, g
);
410 SET_COMP_IF_NOT_SET(2, b
);
411 SET_COMP_IF_NOT_SET(3, master
);
414 for (i
= 0; i
< NB_COMP
+ 1; i
++) {
415 ret
= parse_points_str(ctx
, comp_points
+ i
, curves
->comp_points_str
[i
]);
418 ret
= interpolate(ctx
, curves
->graph
[i
], comp_points
[i
]);
424 for (i
= 0; i
< NB_COMP
; i
++)
425 for (j
= 0; j
< 256; j
++)
426 curves
->graph
[i
][j
] = curves
->graph
[NB_COMP
][curves
->graph
[i
][j
]];
429 if (av_log_get_level() >= AV_LOG_VERBOSE
) {
430 for (i
= 0; i
< NB_COMP
; i
++) {
431 struct keypoint
*point
= comp_points
[i
];
432 av_log(ctx
, AV_LOG_VERBOSE
, "#%d points:", i
);
434 av_log(ctx
, AV_LOG_VERBOSE
, " (%f;%f)", point
->x
, point
->y
);
437 av_log(ctx
, AV_LOG_VERBOSE
, "\n");
438 av_log(ctx
, AV_LOG_VERBOSE
, "#%d values:", i
);
439 for (j
= 0; j
< 256; j
++)
440 av_log(ctx
, AV_LOG_VERBOSE
, " %02X", curves
->graph
[i
][j
]);
441 av_log(ctx
, AV_LOG_VERBOSE
, "\n");
445 for (i
= 0; i
< NB_COMP
+ 1; i
++) {
446 struct keypoint
*point
= comp_points
[i
];
448 struct keypoint
*next
= point
->next
;
457 static int query_formats(AVFilterContext
*ctx
)
459 static const enum AVPixelFormat pix_fmts
[] = {
460 AV_PIX_FMT_RGB24
, AV_PIX_FMT_BGR24
,
461 AV_PIX_FMT_RGBA
, AV_PIX_FMT_BGRA
,
462 AV_PIX_FMT_ARGB
, AV_PIX_FMT_ABGR
,
463 AV_PIX_FMT_0RGB
, AV_PIX_FMT_0BGR
,
464 AV_PIX_FMT_RGB0
, AV_PIX_FMT_BGR0
,
467 ff_set_common_formats(ctx
, ff_make_format_list(pix_fmts
));
471 static int config_input(AVFilterLink
*inlink
)
473 CurvesContext
*curves
= inlink
->dst
->priv
;
474 const AVPixFmtDescriptor
*desc
= av_pix_fmt_desc_get(inlink
->format
);
476 ff_fill_rgba_map(curves
->rgba_map
, inlink
->format
);
477 curves
->step
= av_get_padded_bits_per_pixel(desc
) >> 3;
482 static int filter_slice(AVFilterContext
*ctx
, void *arg
, int jobnr
, int nb_jobs
)
485 const CurvesContext
*curves
= ctx
->priv
;
486 const ThreadData
*td
= arg
;
487 const AVFrame
*in
= td
->in
;
488 const AVFrame
*out
= td
->out
;
489 const int direct
= out
== in
;
490 const int step
= curves
->step
;
491 const uint8_t r
= curves
->rgba_map
[R
];
492 const uint8_t g
= curves
->rgba_map
[G
];
493 const uint8_t b
= curves
->rgba_map
[B
];
494 const uint8_t a
= curves
->rgba_map
[A
];
495 const int slice_start
= (in
->height
* jobnr
) / nb_jobs
;
496 const int slice_end
= (in
->height
* (jobnr
+1)) / nb_jobs
;
497 uint8_t *dst
= out
->data
[0] + slice_start
* out
->linesize
[0];
498 const uint8_t *src
= in
->data
[0] + slice_start
* in
->linesize
[0];
500 for (y
= slice_start
; y
< slice_end
; y
++) {
501 for (x
= 0; x
< in
->width
* step
; x
+= step
) {
502 dst
[x
+ r
] = curves
->graph
[R
][src
[x
+ r
]];
503 dst
[x
+ g
] = curves
->graph
[G
][src
[x
+ g
]];
504 dst
[x
+ b
] = curves
->graph
[B
][src
[x
+ b
]];
505 if (!direct
&& step
== 4)
506 dst
[x
+ a
] = src
[x
+ a
];
508 dst
+= out
->linesize
[0];
509 src
+= in
->linesize
[0];
514 static int filter_frame(AVFilterLink
*inlink
, AVFrame
*in
)
516 AVFilterContext
*ctx
= inlink
->dst
;
517 AVFilterLink
*outlink
= ctx
->outputs
[0];
521 if (av_frame_is_writable(in
)) {
524 out
= ff_get_video_buffer(outlink
, outlink
->w
, outlink
->h
);
527 return AVERROR(ENOMEM
);
529 av_frame_copy_props(out
, in
);
534 ctx
->internal
->execute(ctx
, filter_slice
, &td
, NULL
, FFMIN(outlink
->h
, ctx
->graph
->nb_threads
));
539 return ff_filter_frame(outlink
, out
);
542 static const AVFilterPad curves_inputs
[] = {
545 .type
= AVMEDIA_TYPE_VIDEO
,
546 .filter_frame
= filter_frame
,
547 .config_props
= config_input
,
552 static const AVFilterPad curves_outputs
[] = {
555 .type
= AVMEDIA_TYPE_VIDEO
,
560 AVFilter ff_vf_curves
= {
562 .description
= NULL_IF_CONFIG_SMALL("Adjust components curves."),
563 .priv_size
= sizeof(CurvesContext
),
565 .query_formats
= query_formats
,
566 .inputs
= curves_inputs
,
567 .outputs
= curves_outputs
,
568 .priv_class
= &curves_class
,
569 .flags
= AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC
| AVFILTER_FLAG_SLICE_THREADS
,