1/* 2 * Copyright (c) 2013 Cl��ment B��sch 3 * 4 * This file is part of FFmpeg. 5 * 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. 10 * 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. 15 * 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 19 */ 20 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" 28#include "avfilter.h" 29#include "drawutils.h" 30#include "formats.h" 31#include "internal.h" 32#include "video.h" 33 34#define R 0 35#define G 1 36#define B 2 37#define A 3 38 39struct keypoint { 40 double x, y; 41 struct keypoint *next; 42}; 43 44#define NB_COMP 3 45 46enum preset { 47 PRESET_NONE, 48 PRESET_COLOR_NEGATIVE, 49 PRESET_CROSS_PROCESS, 50 PRESET_DARKER, 51 PRESET_INCREASE_CONTRAST, 52 PRESET_LIGHTER, 53 PRESET_LINEAR_CONTRAST, 54 PRESET_MEDIUM_CONTRAST, 55 PRESET_NEGATIVE, 56 PRESET_STRONG_CONTRAST, 57 PRESET_VINTAGE, 58 NB_PRESETS, 59}; 60 61typedef struct { 62 const AVClass *class; 63 enum preset preset; 64 char *comp_points_str[NB_COMP + 1]; 65 char *comp_points_str_all; 66 uint8_t graph[NB_COMP + 1][256]; 67 char *psfile; 68 uint8_t rgba_map[4]; 69 int step; 70} CurvesContext; 71 72typedef struct ThreadData { 73 AVFrame *in, *out; 74} ThreadData; 75 76#define OFFSET(x) offsetof(CurvesContext, x) 77#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM 78static 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 }, 101 { NULL } 102}; 103 104AVFILTER_DEFINE_CLASS(curves); 105 106static const struct { 107 const char *r; 108 const char *g; 109 const char *b; 110 const char *master; 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", 116 }, 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", 121 }, 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" }, 129 [PRESET_VINTAGE] = { 130 "0/0.11 0.42/0.51 1/0.95", 131 "0.50/0.48", 132 "0/0.22 0.49/0.44 1/0.8", 133 } 134}; 135 136static struct keypoint *make_point(double x, double y, struct keypoint *next) 137{ 138 struct keypoint *point = av_mallocz(sizeof(*point)); 139 140 if (!point) 141 return NULL; 142 point->x = x; 143 point->y = y; 144 point->next = next; 145 return point; 146} 147 148static int parse_points_str(AVFilterContext *ctx, struct keypoint **points, const char *s) 149{ 150 char *p = (char *)s; // strtod won't alter the string 151 struct keypoint *last = NULL; 152 153 /* construct a linked list based on the key points string */ 154 while (p && *p) { 155 struct keypoint *point = make_point(0, 0, NULL); 156 if (!point) 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); 164 } 165 if (!*points) 166 *points = point; 167 if (last) { 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); 174 } 175 last->next = point; 176 } 177 last = point; 178 } 179 180 /* auto insert first key point if missing at x=0 */ 181 if (!*points) { 182 last = make_point(0, 0, NULL); 183 if (!last) 184 return AVERROR(ENOMEM); 185 last->x = last->y = 0; 186 *points = last; 187 } else if ((*points)->x != 0.) { 188 struct keypoint *newfirst = make_point(0, 0, *points); 189 if (!newfirst) 190 return AVERROR(ENOMEM); 191 *points = newfirst; 192 } 193 194 av_assert0(last); 195 196 /* auto insert last key point if missing at x=1 */ 197 if (last->x != 1.) { 198 struct keypoint *point = make_point(1, 1, NULL); 199 if (!point) 200 return AVERROR(ENOMEM); 201 last->next = point; 202 } 203 204 return 0; 205} 206 207static int get_nb_points(const struct keypoint *d) 208{ 209 int n = 0; 210 while (d) { 211 n++; 212 d = d->next; 213 } 214 return n; 215} 216 217/** 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 221 */ 222static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint *points) 223{ 224 int i, ret = 0; 225 const struct keypoint *point; 226 double xprev = 0; 227 228 int n = get_nb_points(points); // number of splines 229 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)); 233 234 if (!matrix || !h || !r) { 235 ret = AVERROR(ENOMEM); 236 goto end; 237 } 238 239 /* h(i) = x(i+1) - x(i) */ 240 i = -1; 241 for (point = points; point; point = point->next) { 242 if (i != -1) 243 h[i] = point->x - xprev; 244 xprev = point->x; 245 i++; 246 } 247 248 /* right-side of the polynomials, will be modified to contains the solution */ 249 point = points; 250 for (i = 1; i < n - 1; i++) { 251 double yp = point->y, 252 yc = point->next->y, 253 yn = point->next->next->y; 254 r[i] = 6 * ((yn-yc)/h[i] - (yc-yp)/h[i-1]); 255 point = point->next; 256 } 257 258#define BD 0 /* sub diagonal (below main) */ 259#define MD 1 /* main diagonal (center) */ 260#define AD 2 /* sup diagonal (above main) */ 261 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]; 268 } 269 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.; 274 matrix[i][AD] *= k; 275 r[i] = (r[i] - matrix[i][BD] * r[i - 1]) * k; 276 } 277 for (i = n - 2; i >= 0; i--) 278 r[i] = r[i] - matrix[i][AD] * r[i + 1]; 279 280 /* compute the graph with x=[0..255] */ 281 i = 0; 282 point = points; 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; 287 288 double a = yc; 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]); 292 293 int x; 294 int x_start = point->x * 255; 295 int x_end = point->next->x * 255; 296 297 av_assert0(x_start >= 0 && x_start <= 255 && 298 x_end >= 0 && x_end <= 255); 299 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]); 305 } 306 307 point = point->next; 308 i++; 309 } 310 311end: 312 av_free(matrix); 313 av_free(h); 314 av_free(r); 315 return ret; 316} 317 318static int parse_psfile(AVFilterContext *ctx, const char *fname) 319{ 320 CurvesContext *curves = ctx->priv; 321 uint8_t *buf; 322 size_t size; 323 int i, ret, av_unused(version), nb_curves; 324 AVBPrint ptstr; 325 static const int comp_ids[] = {3, 0, 1, 2}; 326 327 av_bprint_init(&ptstr, 0, AV_BPRINT_SIZE_AUTOMATIC); 328 329 ret = av_file_map(fname, &buf, &size, 0, NULL); 330 if (ret < 0) 331 return ret; 332 333#define READ16(dst) do { \ 334 if (size < 2) { \ 335 ret = AVERROR_INVALIDDATA; \ 336 goto end; \ 337 } \ 338 dst = AV_RB16(buf); \ 339 buf += 2; \ 340 size -= 2; \ 341} while (0) 342 343 READ16(version); 344 READ16(nb_curves); 345 for (i = 0; i < FFMIN(nb_curves, FF_ARRAY_ELEMS(comp_ids)); i++) { 346 int nb_points, n; 347 av_bprint_clear(&ptstr); 348 READ16(nb_points); 349 for (n = 0; n < nb_points; n++) { 350 int y, x; 351 READ16(y); 352 READ16(x); 353 av_bprintf(&ptstr, "%f/%f ", x / 255., y / 255.); 354 } 355 if (*ptstr.str) { 356 char **pts = &curves->comp_points_str[comp_ids[i]]; 357 if (!*pts) { 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); 361 if (!*pts) { 362 ret = AVERROR(ENOMEM); 363 goto end; 364 } 365 } 366 } 367 } 368end: 369 av_bprint_finalize(&ptstr, NULL); 370 av_file_unmap(buf, size); 371 return ret; 372} 373 374static av_cold int init(AVFilterContext *ctx) 375{ 376 int i, j, ret; 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; 381 382 //if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all) 383 // allp = curves_presets[curves->preset].all; 384 385 if (allp) { 386 for (i = 0; i < NB_COMP; i++) { 387 if (!pts[i]) 388 pts[i] = av_strdup(allp); 389 if (!pts[i]) 390 return AVERROR(ENOMEM); 391 } 392 } 393 394 if (curves->psfile) { 395 ret = parse_psfile(ctx, curves->psfile); 396 if (ret < 0) 397 return ret; 398 } 399 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); \ 404 if (!pts[n]) \ 405 return AVERROR(ENOMEM); \ 406 } \ 407} while (0) 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); 412 } 413 414 for (i = 0; i < NB_COMP + 1; i++) { 415 ret = parse_points_str(ctx, comp_points + i, curves->comp_points_str[i]); 416 if (ret < 0) 417 return ret; 418 ret = interpolate(ctx, curves->graph[i], comp_points[i]); 419 if (ret < 0) 420 return ret; 421 } 422 423 if (pts[NB_COMP]) { 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]]; 427 } 428 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); 433 while (point) { 434 av_log(ctx, AV_LOG_VERBOSE, " (%f;%f)", point->x, point->y); 435 point = point->next; 436 } 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"); 442 } 443 } 444 445 for (i = 0; i < NB_COMP + 1; i++) { 446 struct keypoint *point = comp_points[i]; 447 while (point) { 448 struct keypoint *next = point->next; 449 av_free(point); 450 point = next; 451 } 452 } 453 454 return 0; 455} 456 457static int query_formats(AVFilterContext *ctx) 458{ 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, 465 AV_PIX_FMT_NONE 466 }; 467 ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); 468 return 0; 469} 470 471static int config_input(AVFilterLink *inlink) 472{ 473 CurvesContext *curves = inlink->dst->priv; 474 const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); 475 476 ff_fill_rgba_map(curves->rgba_map, inlink->format); 477 curves->step = av_get_padded_bits_per_pixel(desc) >> 3; 478 479 return 0; 480} 481 482static int filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) 483{ 484 int x, y; 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]; 499 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]; 507 } 508 dst += out->linesize[0]; 509 src += in ->linesize[0]; 510 } 511 return 0; 512} 513 514static int filter_frame(AVFilterLink *inlink, AVFrame *in) 515{ 516 AVFilterContext *ctx = inlink->dst; 517 AVFilterLink *outlink = ctx->outputs[0]; 518 AVFrame *out; 519 ThreadData td; 520 521 if (av_frame_is_writable(in)) { 522 out = in; 523 } else { 524 out = ff_get_video_buffer(outlink, outlink->w, outlink->h); 525 if (!out) { 526 av_frame_free(&in); 527 return AVERROR(ENOMEM); 528 } 529 av_frame_copy_props(out, in); 530 } 531 532 td.in = in; 533 td.out = out; 534 ctx->internal->execute(ctx, filter_slice, &td, NULL, FFMIN(outlink->h, ctx->graph->nb_threads)); 535 536 if (out != in) 537 av_frame_free(&in); 538 539 return ff_filter_frame(outlink, out); 540} 541 542static const AVFilterPad curves_inputs[] = { 543 { 544 .name = "default", 545 .type = AVMEDIA_TYPE_VIDEO, 546 .filter_frame = filter_frame, 547 .config_props = config_input, 548 }, 549 { NULL } 550}; 551 552static const AVFilterPad curves_outputs[] = { 553 { 554 .name = "default", 555 .type = AVMEDIA_TYPE_VIDEO, 556 }, 557 { NULL } 558}; 559 560AVFilter ff_vf_curves = { 561 .name = "curves", 562 .description = NULL_IF_CONFIG_SMALL("Adjust components curves."), 563 .priv_size = sizeof(CurvesContext), 564 .init = init, 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, 570}; 571