Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | RSS feed

  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.  
  39. struct keypoint {
  40.     double x, y;
  41.     struct keypoint *next;
  42. };
  43.  
  44. #define NB_COMP 3
  45.  
  46. enum 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.  
  61. typedef struct {
  62.     const AVClass *class;
  63.     int 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.  
  72. typedef 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
  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 },
  101.     { NULL }
  102. };
  103.  
  104. AVFILTER_DEFINE_CLASS(curves);
  105.  
  106. static 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.  
  136. static 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.  
  148. static 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.  
  207. static 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.  */
  222. static 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.  
  311. end:
  312.     av_free(matrix);
  313.     av_free(h);
  314.     av_free(r);
  315.     return ret;
  316. }
  317.  
  318. static 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.     }
  368. end:
  369.     av_bprint_finalize(&ptstr, NULL);
  370.     av_file_unmap(buf, size);
  371.     return ret;
  372. }
  373.  
  374. static 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.  
  457. static 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.     AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
  468.     if (!fmts_list)
  469.         return AVERROR(ENOMEM);
  470.     return ff_set_common_formats(ctx, fmts_list);
  471. }
  472.  
  473. static int config_input(AVFilterLink *inlink)
  474. {
  475.     CurvesContext *curves = inlink->dst->priv;
  476.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
  477.  
  478.     ff_fill_rgba_map(curves->rgba_map, inlink->format);
  479.     curves->step = av_get_padded_bits_per_pixel(desc) >> 3;
  480.  
  481.     return 0;
  482. }
  483.  
  484. static int filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs)
  485. {
  486.     int x, y;
  487.     const CurvesContext *curves = ctx->priv;
  488.     const ThreadData *td = arg;
  489.     const AVFrame *in  = td->in;
  490.     const AVFrame *out = td->out;
  491.     const int direct = out == in;
  492.     const int step = curves->step;
  493.     const uint8_t r = curves->rgba_map[R];
  494.     const uint8_t g = curves->rgba_map[G];
  495.     const uint8_t b = curves->rgba_map[B];
  496.     const uint8_t a = curves->rgba_map[A];
  497.     const int slice_start = (in->height *  jobnr   ) / nb_jobs;
  498.     const int slice_end   = (in->height * (jobnr+1)) / nb_jobs;
  499.     uint8_t       *dst = out->data[0] + slice_start * out->linesize[0];
  500.     const uint8_t *src =  in->data[0] + slice_start *  in->linesize[0];
  501.  
  502.     for (y = slice_start; y < slice_end; y++) {
  503.         for (x = 0; x < in->width * step; x += step) {
  504.             dst[x + r] = curves->graph[R][src[x + r]];
  505.             dst[x + g] = curves->graph[G][src[x + g]];
  506.             dst[x + b] = curves->graph[B][src[x + b]];
  507.             if (!direct && step == 4)
  508.                 dst[x + a] = src[x + a];
  509.         }
  510.         dst += out->linesize[0];
  511.         src += in ->linesize[0];
  512.     }
  513.     return 0;
  514. }
  515.  
  516. static int filter_frame(AVFilterLink *inlink, AVFrame *in)
  517. {
  518.     AVFilterContext *ctx = inlink->dst;
  519.     AVFilterLink *outlink = ctx->outputs[0];
  520.     AVFrame *out;
  521.     ThreadData td;
  522.  
  523.     if (av_frame_is_writable(in)) {
  524.         out = in;
  525.     } else {
  526.         out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
  527.         if (!out) {
  528.             av_frame_free(&in);
  529.             return AVERROR(ENOMEM);
  530.         }
  531.         av_frame_copy_props(out, in);
  532.     }
  533.  
  534.     td.in  = in;
  535.     td.out = out;
  536.     ctx->internal->execute(ctx, filter_slice, &td, NULL, FFMIN(outlink->h, ctx->graph->nb_threads));
  537.  
  538.     if (out != in)
  539.         av_frame_free(&in);
  540.  
  541.     return ff_filter_frame(outlink, out);
  542. }
  543.  
  544. static const AVFilterPad curves_inputs[] = {
  545.     {
  546.         .name         = "default",
  547.         .type         = AVMEDIA_TYPE_VIDEO,
  548.         .filter_frame = filter_frame,
  549.         .config_props = config_input,
  550.     },
  551.     { NULL }
  552. };
  553.  
  554. static const AVFilterPad curves_outputs[] = {
  555.     {
  556.         .name = "default",
  557.         .type = AVMEDIA_TYPE_VIDEO,
  558.     },
  559.     { NULL }
  560. };
  561.  
  562. AVFilter ff_vf_curves = {
  563.     .name          = "curves",
  564.     .description   = NULL_IF_CONFIG_SMALL("Adjust components curves."),
  565.     .priv_size     = sizeof(CurvesContext),
  566.     .init          = init,
  567.     .query_formats = query_formats,
  568.     .inputs        = curves_inputs,
  569.     .outputs       = curves_outputs,
  570.     .priv_class    = &curves_class,
  571.     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS,
  572. };
  573.