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.     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.  
  72. #define OFFSET(x) offsetof(CurvesContext, x)
  73. #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
  74. static const AVOption curves_options[] = {
  75.     { "preset", "select a color curves preset", OFFSET(preset), AV_OPT_TYPE_INT, {.i64=PRESET_NONE}, PRESET_NONE, NB_PRESETS-1, FLAGS, "preset_name" },
  76.         { "none",               NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NONE},                 INT_MIN, INT_MAX, FLAGS, "preset_name" },
  77.         { "color_negative",     NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_COLOR_NEGATIVE},       INT_MIN, INT_MAX, FLAGS, "preset_name" },
  78.         { "cross_process",      NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_CROSS_PROCESS},        INT_MIN, INT_MAX, FLAGS, "preset_name" },
  79.         { "darker",             NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_DARKER},               INT_MIN, INT_MAX, FLAGS, "preset_name" },
  80.         { "increase_contrast",  NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_INCREASE_CONTRAST},    INT_MIN, INT_MAX, FLAGS, "preset_name" },
  81.         { "lighter",            NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_LIGHTER},              INT_MIN, INT_MAX, FLAGS, "preset_name" },
  82.         { "linear_contrast",    NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_LINEAR_CONTRAST},      INT_MIN, INT_MAX, FLAGS, "preset_name" },
  83.         { "medium_contrast",    NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_MEDIUM_CONTRAST},      INT_MIN, INT_MAX, FLAGS, "preset_name" },
  84.         { "negative",           NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NEGATIVE},             INT_MIN, INT_MAX, FLAGS, "preset_name" },
  85.         { "strong_contrast",    NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_STRONG_CONTRAST},      INT_MIN, INT_MAX, FLAGS, "preset_name" },
  86.         { "vintage",            NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_VINTAGE},              INT_MIN, INT_MAX, FLAGS, "preset_name" },
  87.     { "master","set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  88.     { "m",     "set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  89.     { "red",   "set red points coordinates",   OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  90.     { "r",     "set red points coordinates",   OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  91.     { "green", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  92.     { "g",     "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  93.     { "blue",  "set blue points coordinates",  OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  94.     { "b",     "set blue points coordinates",  OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  95.     { "all",   "set points coordinates for all components", OFFSET(comp_points_str_all), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  96.     { "psfile", "set Photoshop curves file name", OFFSET(psfile), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
  97.     { NULL }
  98. };
  99.  
  100. AVFILTER_DEFINE_CLASS(curves);
  101.  
  102. static const struct {
  103.     const char *r;
  104.     const char *g;
  105.     const char *b;
  106.     const char *master;
  107. } curves_presets[] = {
  108.     [PRESET_COLOR_NEGATIVE] = {
  109.         "0/1 0.129/1 0.466/0.498 0.725/0 1/0",
  110.         "0/1 0.109/1 0.301/0.498 0.517/0 1/0",
  111.         "0/1 0.098/1 0.235/0.498 0.423/0 1/0",
  112.     },
  113.     [PRESET_CROSS_PROCESS] = {
  114.         "0.25/0.156 0.501/0.501 0.686/0.745",
  115.         "0.25/0.188 0.38/0.501 0.745/0.815 1/0.815",
  116.         "0.231/0.094 0.709/0.874",
  117.     },
  118.     [PRESET_DARKER]             = { .master = "0.5/0.4" },
  119.     [PRESET_INCREASE_CONTRAST]  = { .master = "0.149/0.066 0.831/0.905 0.905/0.98" },
  120.     [PRESET_LIGHTER]            = { .master = "0.4/0.5" },
  121.     [PRESET_LINEAR_CONTRAST]    = { .master = "0.305/0.286 0.694/0.713" },
  122.     [PRESET_MEDIUM_CONTRAST]    = { .master = "0.286/0.219 0.639/0.643" },
  123.     [PRESET_NEGATIVE]           = { .master = "0/1 1/0" },
  124.     [PRESET_STRONG_CONTRAST]    = { .master = "0.301/0.196 0.592/0.6 0.686/0.737" },
  125.     [PRESET_VINTAGE] = {
  126.         "0/0.11 0.42/0.51 1/0.95",
  127.         "0.50/0.48",
  128.         "0/0.22 0.49/0.44 1/0.8",
  129.     }
  130. };
  131.  
  132. static struct keypoint *make_point(double x, double y, struct keypoint *next)
  133. {
  134.     struct keypoint *point = av_mallocz(sizeof(*point));
  135.  
  136.     if (!point)
  137.         return NULL;
  138.     point->x = x;
  139.     point->y = y;
  140.     point->next = next;
  141.     return point;
  142. }
  143.  
  144. static int parse_points_str(AVFilterContext *ctx, struct keypoint **points, const char *s)
  145. {
  146.     char *p = (char *)s; // strtod won't alter the string
  147.     struct keypoint *last = NULL;
  148.  
  149.     /* construct a linked list based on the key points string */
  150.     while (p && *p) {
  151.         struct keypoint *point = make_point(0, 0, NULL);
  152.         if (!point)
  153.             return AVERROR(ENOMEM);
  154.         point->x = av_strtod(p, &p); if (p && *p) p++;
  155.         point->y = av_strtod(p, &p); if (p && *p) p++;
  156.         if (point->x < 0 || point->x > 1 || point->y < 0 || point->y > 1) {
  157.             av_log(ctx, AV_LOG_ERROR, "Invalid key point coordinates (%f;%f), "
  158.                    "x and y must be in the [0;1] range.\n", point->x, point->y);
  159.             return AVERROR(EINVAL);
  160.         }
  161.         if (!*points)
  162.             *points = point;
  163.         if (last) {
  164.             if ((int)(last->x * 255) >= (int)(point->x * 255)) {
  165.                 av_log(ctx, AV_LOG_ERROR, "Key point coordinates (%f;%f) "
  166.                        "and (%f;%f) are too close from each other or not "
  167.                        "strictly increasing on the x-axis\n",
  168.                        last->x, last->y, point->x, point->y);
  169.                 return AVERROR(EINVAL);
  170.             }
  171.             last->next = point;
  172.         }
  173.         last = point;
  174.     }
  175.  
  176.     /* auto insert first key point if missing at x=0 */
  177.     if (!*points) {
  178.         last = make_point(0, 0, NULL);
  179.         if (!last)
  180.             return AVERROR(ENOMEM);
  181.         last->x = last->y = 0;
  182.         *points = last;
  183.     } else if ((*points)->x != 0.) {
  184.         struct keypoint *newfirst = make_point(0, 0, *points);
  185.         if (!newfirst)
  186.             return AVERROR(ENOMEM);
  187.         *points = newfirst;
  188.     }
  189.  
  190.     av_assert0(last);
  191.  
  192.     /* auto insert last key point if missing at x=1 */
  193.     if (last->x != 1.) {
  194.         struct keypoint *point = make_point(1, 1, NULL);
  195.         if (!point)
  196.             return AVERROR(ENOMEM);
  197.         last->next = point;
  198.     }
  199.  
  200.     return 0;
  201. }
  202.  
  203. static int get_nb_points(const struct keypoint *d)
  204. {
  205.     int n = 0;
  206.     while (d) {
  207.         n++;
  208.         d = d->next;
  209.     }
  210.     return n;
  211. }
  212.  
  213. /**
  214.  * Natural cubic spline interpolation
  215.  * Finding curves using Cubic Splines notes by Steven Rauch and John Stockie.
  216.  * @see http://people.math.sfu.ca/~stockie/teaching/macm316/notes/splines.pdf
  217.  */
  218. static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint *points)
  219. {
  220.     int i, ret = 0;
  221.     const struct keypoint *point;
  222.     double xprev = 0;
  223.  
  224.     int n = get_nb_points(points); // number of splines
  225.  
  226.     double (*matrix)[3] = av_calloc(n, sizeof(*matrix));
  227.     double *h = av_malloc((n - 1) * sizeof(*h));
  228.     double *r = av_calloc(n, sizeof(*r));
  229.  
  230.     if (!matrix || !h || !r) {
  231.         ret = AVERROR(ENOMEM);
  232.         goto end;
  233.     }
  234.  
  235.     /* h(i) = x(i+1) - x(i) */
  236.     i = -1;
  237.     for (point = points; point; point = point->next) {
  238.         if (i != -1)
  239.             h[i] = point->x - xprev;
  240.         xprev = point->x;
  241.         i++;
  242.     }
  243.  
  244.     /* right-side of the polynomials, will be modified to contains the solution */
  245.     point = points;
  246.     for (i = 1; i < n - 1; i++) {
  247.         double yp = point->y,
  248.                yc = point->next->y,
  249.                yn = point->next->next->y;
  250.         r[i] = 6 * ((yn-yc)/h[i] - (yc-yp)/h[i-1]);
  251.         point = point->next;
  252.     }
  253.  
  254. #define BD 0 /* sub  diagonal (below main) */
  255. #define MD 1 /* main diagonal (center) */
  256. #define AD 2 /* sup  diagonal (above main) */
  257.  
  258.     /* left side of the polynomials into a tridiagonal matrix. */
  259.     matrix[0][MD] = matrix[n - 1][MD] = 1;
  260.     for (i = 1; i < n - 1; i++) {
  261.         matrix[i][BD] = h[i-1];
  262.         matrix[i][MD] = 2 * (h[i-1] + h[i]);
  263.         matrix[i][AD] = h[i];
  264.     }
  265.  
  266.     /* tridiagonal solving of the linear system */
  267.     for (i = 1; i < n; i++) {
  268.         double den = matrix[i][MD] - matrix[i][BD] * matrix[i-1][AD];
  269.         double k = den ? 1./den : 1.;
  270.         matrix[i][AD] *= k;
  271.         r[i] = (r[i] - matrix[i][BD] * r[i - 1]) * k;
  272.     }
  273.     for (i = n - 2; i >= 0; i--)
  274.         r[i] = r[i] - matrix[i][AD] * r[i + 1];
  275.  
  276.     /* compute the graph with x=[0..255] */
  277.     i = 0;
  278.     point = points;
  279.     av_assert0(point->next); // always at least 2 key points
  280.     while (point->next) {
  281.         double yc = point->y;
  282.         double yn = point->next->y;
  283.  
  284.         double a = yc;
  285.         double b = (yn-yc)/h[i] - h[i]*r[i]/2. - h[i]*(r[i+1]-r[i])/6.;
  286.         double c = r[i] / 2.;
  287.         double d = (r[i+1] - r[i]) / (6.*h[i]);
  288.  
  289.         int x;
  290.         int x_start = point->x       * 255;
  291.         int x_end   = point->next->x * 255;
  292.  
  293.         av_assert0(x_start >= 0 && x_start <= 255 &&
  294.                    x_end   >= 0 && x_end   <= 255);
  295.  
  296.         for (x = x_start; x <= x_end; x++) {
  297.             double xx = (x - x_start) * 1/255.;
  298.             double yy = a + b*xx + c*xx*xx + d*xx*xx*xx;
  299.             y[x] = av_clipf(yy, 0, 1) * 255;
  300.             av_log(ctx, AV_LOG_DEBUG, "f(%f)=%f -> y[%d]=%d\n", xx, yy, x, y[x]);
  301.         }
  302.  
  303.         point = point->next;
  304.         i++;
  305.     }
  306.  
  307. end:
  308.     av_free(matrix);
  309.     av_free(h);
  310.     av_free(r);
  311.     return ret;
  312. }
  313.  
  314. static int parse_psfile(AVFilterContext *ctx, const char *fname)
  315. {
  316.     CurvesContext *curves = ctx->priv;
  317.     uint8_t *buf;
  318.     size_t size;
  319.     int i, ret, av_unused(version), nb_curves;
  320.     AVBPrint ptstr;
  321.     static const int comp_ids[] = {3, 0, 1, 2};
  322.  
  323.     av_bprint_init(&ptstr, 0, AV_BPRINT_SIZE_AUTOMATIC);
  324.  
  325.     ret = av_file_map(fname, &buf, &size, 0, NULL);
  326.     if (ret < 0)
  327.         return ret;
  328.  
  329. #define READ16(dst) do {                \
  330.     if (size < 2)                       \
  331.         return AVERROR_INVALIDDATA;     \
  332.     dst = AV_RB16(buf);                 \
  333.     buf  += 2;                          \
  334.     size -= 2;                          \
  335. } while (0)
  336.  
  337.     READ16(version);
  338.     READ16(nb_curves);
  339.     for (i = 0; i < FFMIN(nb_curves, FF_ARRAY_ELEMS(comp_ids)); i++) {
  340.         int nb_points, n;
  341.         av_bprint_clear(&ptstr);
  342.         READ16(nb_points);
  343.         for (n = 0; n < nb_points; n++) {
  344.             int y, x;
  345.             READ16(y);
  346.             READ16(x);
  347.             av_bprintf(&ptstr, "%f/%f ", x / 255., y / 255.);
  348.         }
  349.         if (*ptstr.str) {
  350.             char **pts = &curves->comp_points_str[comp_ids[i]];
  351.             if (!*pts) {
  352.                 *pts = av_strdup(ptstr.str);
  353.                 av_log(ctx, AV_LOG_DEBUG, "curves %d (intid=%d) [%d points]: [%s]\n",
  354.                        i, comp_ids[i], nb_points, *pts);
  355.                 if (!*pts) {
  356.                     ret = AVERROR(ENOMEM);
  357.                     goto end;
  358.                 }
  359.             }
  360.         }
  361.     }
  362. end:
  363.     av_bprint_finalize(&ptstr, NULL);
  364.     av_file_unmap(buf, size);
  365.     return ret;
  366. }
  367.  
  368. static av_cold int init(AVFilterContext *ctx)
  369. {
  370.     int i, j, ret;
  371.     CurvesContext *curves = ctx->priv;
  372.     struct keypoint *comp_points[NB_COMP + 1] = {0};
  373.     char **pts = curves->comp_points_str;
  374.     const char *allp = curves->comp_points_str_all;
  375.  
  376.     //if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all)
  377.     //    allp = curves_presets[curves->preset].all;
  378.  
  379.     if (allp) {
  380.         for (i = 0; i < NB_COMP; i++) {
  381.             if (!pts[i])
  382.                 pts[i] = av_strdup(allp);
  383.             if (!pts[i])
  384.                 return AVERROR(ENOMEM);
  385.         }
  386.     }
  387.  
  388.     if (curves->psfile) {
  389.         ret = parse_psfile(ctx, curves->psfile);
  390.         if (ret < 0)
  391.             return ret;
  392.     }
  393.  
  394.     if (curves->preset != PRESET_NONE) {
  395. #define SET_COMP_IF_NOT_SET(n, name) do {                           \
  396.     if (!pts[n] && curves_presets[curves->preset].name) {           \
  397.         pts[n] = av_strdup(curves_presets[curves->preset].name);    \
  398.         if (!pts[n])                                                \
  399.             return AVERROR(ENOMEM);                                 \
  400.     }                                                               \
  401. } while (0)
  402.         SET_COMP_IF_NOT_SET(0, r);
  403.         SET_COMP_IF_NOT_SET(1, g);
  404.         SET_COMP_IF_NOT_SET(2, b);
  405.         SET_COMP_IF_NOT_SET(3, master);
  406.     }
  407.  
  408.     for (i = 0; i < NB_COMP + 1; i++) {
  409.         ret = parse_points_str(ctx, comp_points + i, curves->comp_points_str[i]);
  410.         if (ret < 0)
  411.             return ret;
  412.         ret = interpolate(ctx, curves->graph[i], comp_points[i]);
  413.         if (ret < 0)
  414.             return ret;
  415.     }
  416.  
  417.     if (pts[NB_COMP]) {
  418.         for (i = 0; i < NB_COMP; i++)
  419.             for (j = 0; j < 256; j++)
  420.                 curves->graph[i][j] = curves->graph[NB_COMP][curves->graph[i][j]];
  421.     }
  422.  
  423.     if (av_log_get_level() >= AV_LOG_VERBOSE) {
  424.         for (i = 0; i < NB_COMP; i++) {
  425.             struct keypoint *point = comp_points[i];
  426.             av_log(ctx, AV_LOG_VERBOSE, "#%d points:", i);
  427.             while (point) {
  428.                 av_log(ctx, AV_LOG_VERBOSE, " (%f;%f)", point->x, point->y);
  429.                 point = point->next;
  430.             }
  431.             av_log(ctx, AV_LOG_VERBOSE, "\n");
  432.             av_log(ctx, AV_LOG_VERBOSE, "#%d values:", i);
  433.             for (j = 0; j < 256; j++)
  434.                 av_log(ctx, AV_LOG_VERBOSE, " %02X", curves->graph[i][j]);
  435.             av_log(ctx, AV_LOG_VERBOSE, "\n");
  436.         }
  437.     }
  438.  
  439.     for (i = 0; i < NB_COMP + 1; i++) {
  440.         struct keypoint *point = comp_points[i];
  441.         while (point) {
  442.             struct keypoint *next = point->next;
  443.             av_free(point);
  444.             point = next;
  445.         }
  446.     }
  447.  
  448.     return 0;
  449. }
  450.  
  451. static int query_formats(AVFilterContext *ctx)
  452. {
  453.     static const enum AVPixelFormat pix_fmts[] = {
  454.         AV_PIX_FMT_RGB24,  AV_PIX_FMT_BGR24,
  455.         AV_PIX_FMT_RGBA,   AV_PIX_FMT_BGRA,
  456.         AV_PIX_FMT_ARGB,   AV_PIX_FMT_ABGR,
  457.         AV_PIX_FMT_0RGB,   AV_PIX_FMT_0BGR,
  458.         AV_PIX_FMT_RGB0,   AV_PIX_FMT_BGR0,
  459.         AV_PIX_FMT_NONE
  460.     };
  461.     ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
  462.     return 0;
  463. }
  464.  
  465. static int config_input(AVFilterLink *inlink)
  466. {
  467.     CurvesContext *curves = inlink->dst->priv;
  468.     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
  469.  
  470.     ff_fill_rgba_map(curves->rgba_map, inlink->format);
  471.     curves->step = av_get_padded_bits_per_pixel(desc) >> 3;
  472.  
  473.     return 0;
  474. }
  475.  
  476. static int filter_frame(AVFilterLink *inlink, AVFrame *in)
  477. {
  478.     int x, y, direct = 0;
  479.     AVFilterContext *ctx = inlink->dst;
  480.     CurvesContext *curves = ctx->priv;
  481.     AVFilterLink *outlink = ctx->outputs[0];
  482.     AVFrame *out;
  483.     uint8_t *dst;
  484.     const uint8_t *src;
  485.     const int step = curves->step;
  486.     const uint8_t r = curves->rgba_map[R];
  487.     const uint8_t g = curves->rgba_map[G];
  488.     const uint8_t b = curves->rgba_map[B];
  489.     const uint8_t a = curves->rgba_map[A];
  490.  
  491.     if (av_frame_is_writable(in)) {
  492.         direct = 1;
  493.         out = in;
  494.     } else {
  495.         out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
  496.         if (!out) {
  497.             av_frame_free(&in);
  498.             return AVERROR(ENOMEM);
  499.         }
  500.         av_frame_copy_props(out, in);
  501.     }
  502.  
  503.     dst = out->data[0];
  504.     src = in ->data[0];
  505.  
  506.     for (y = 0; y < inlink->h; y++) {
  507.         for (x = 0; x < inlink->w * step; x += step) {
  508.             dst[x + r] = curves->graph[R][src[x + r]];
  509.             dst[x + g] = curves->graph[G][src[x + g]];
  510.             dst[x + b] = curves->graph[B][src[x + b]];
  511.             if (!direct && step == 4)
  512.                 dst[x + a] = src[x + a];
  513.         }
  514.         dst += out->linesize[0];
  515.         src += in ->linesize[0];
  516.     }
  517.  
  518.     if (!direct)
  519.         av_frame_free(&in);
  520.  
  521.     return ff_filter_frame(outlink, out);
  522. }
  523.  
  524. static const AVFilterPad curves_inputs[] = {
  525.     {
  526.         .name         = "default",
  527.         .type         = AVMEDIA_TYPE_VIDEO,
  528.         .filter_frame = filter_frame,
  529.         .config_props = config_input,
  530.     },
  531.     { NULL }
  532. };
  533.  
  534. static const AVFilterPad curves_outputs[] = {
  535.     {
  536.         .name = "default",
  537.         .type = AVMEDIA_TYPE_VIDEO,
  538.     },
  539.     { NULL }
  540. };
  541.  
  542. AVFilter avfilter_vf_curves = {
  543.     .name          = "curves",
  544.     .description   = NULL_IF_CONFIG_SMALL("Adjust components curves."),
  545.     .priv_size     = sizeof(CurvesContext),
  546.     .init          = init,
  547.     .query_formats = query_formats,
  548.     .inputs        = curves_inputs,
  549.     .outputs       = curves_outputs,
  550.     .priv_class    = &curves_class,
  551.     .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC,
  552. };
  553.