]> git.sesse.net Git - ffmpeg/blobdiff - libavfilter/vf_curves.c
Merge commit 'cf679b9476727a237c8006c685ace18acba149ab'
[ffmpeg] / libavfilter / vf_curves.c
index bed532760d5c3297b031ee68deb0114f3f9189d0..482a046b48a362be22caa810eb8de91603a92635 100644 (file)
  */
 
 #include "libavutil/opt.h"
+#include "libavutil/bprint.h"
 #include "libavutil/eval.h"
+#include "libavutil/file.h"
+#include "libavutil/intreadwrite.h"
 #include "libavutil/avassert.h"
+#include "libavutil/pixdesc.h"
 #include "avfilter.h"
+#include "drawutils.h"
 #include "formats.h"
 #include "internal.h"
 #include "video.h"
 
+#define R 0
+#define G 1
+#define B 2
+#define A 3
+
 struct keypoint {
     double x, y;
     struct keypoint *next;
@@ -51,9 +61,12 @@ enum preset {
 typedef struct {
     const AVClass *class;
     enum preset preset;
-    char *comp_points_str[NB_COMP];
+    char *comp_points_str[NB_COMP + 1];
     char *comp_points_str_all;
-    uint8_t graph[NB_COMP][256];
+    uint8_t graph[NB_COMP + 1][256];
+    char *psfile;
+    uint8_t rgba_map[4];
+    int step;
 } CurvesContext;
 
 #define OFFSET(x) offsetof(CurvesContext, x)
@@ -71,6 +84,8 @@ static const AVOption curves_options[] = {
         { "negative",           NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_NEGATIVE},             INT_MIN, INT_MAX, FLAGS, "preset_name" },
         { "strong_contrast",    NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_STRONG_CONTRAST},      INT_MIN, INT_MAX, FLAGS, "preset_name" },
         { "vintage",            NULL, 0, AV_OPT_TYPE_CONST, {.i64=PRESET_VINTAGE},              INT_MIN, INT_MAX, FLAGS, "preset_name" },
+    { "master","set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
+    { "m",     "set master points coordinates",OFFSET(comp_points_str[NB_COMP]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
     { "red",   "set red points coordinates",   OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
     { "r",     "set red points coordinates",   OFFSET(comp_points_str[0]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
     { "green", "set green points coordinates", OFFSET(comp_points_str[1]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
@@ -78,6 +93,7 @@ static const AVOption curves_options[] = {
     { "blue",  "set blue points coordinates",  OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
     { "b",     "set blue points coordinates",  OFFSET(comp_points_str[2]), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
     { "all",   "set points coordinates for all components", OFFSET(comp_points_str_all), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
+    { "psfile", "set Photoshop curves file name", OFFSET(psfile), AV_OPT_TYPE_STRING, {.str=NULL}, .flags = FLAGS },
     { NULL }
 };
 
@@ -87,7 +103,7 @@ static const struct {
     const char *r;
     const char *g;
     const char *b;
-    const char *all;
+    const char *master;
 } curves_presets[] = {
     [PRESET_COLOR_NEGATIVE] = {
         "0/1 0.129/1 0.466/0.498 0.725/0 1/0",
@@ -99,13 +115,13 @@ static const struct {
         "0.25/0.188 0.38/0.501 0.745/0.815 1/0.815",
         "0.231/0.094 0.709/0.874",
     },
-    [PRESET_DARKER]             = { .all = "0.5/0.4" },
-    [PRESET_INCREASE_CONTRAST]  = { .all = "0.149/0.066 0.831/0.905 0.905/0.98" },
-    [PRESET_LIGHTER]            = { .all = "0.4/0.5" },
-    [PRESET_LINEAR_CONTRAST]    = { .all = "0.305/0.286 0.694/0.713" },
-    [PRESET_MEDIUM_CONTRAST]    = { .all = "0.286/0.219 0.639/0.643" },
-    [PRESET_NEGATIVE]           = { .all = "0/1 1/0" },
-    [PRESET_STRONG_CONTRAST]    = { .all = "0.301/0.196 0.592/0.6 0.686/0.737" },
+    [PRESET_DARKER]             = { .master = "0.5/0.4" },
+    [PRESET_INCREASE_CONTRAST]  = { .master = "0.149/0.066 0.831/0.905 0.905/0.98" },
+    [PRESET_LIGHTER]            = { .master = "0.4/0.5" },
+    [PRESET_LINEAR_CONTRAST]    = { .master = "0.305/0.286 0.694/0.713" },
+    [PRESET_MEDIUM_CONTRAST]    = { .master = "0.286/0.219 0.639/0.643" },
+    [PRESET_NEGATIVE]           = { .master = "0/1 1/0" },
+    [PRESET_STRONG_CONTRAST]    = { .master = "0.301/0.196 0.592/0.6 0.686/0.737" },
     [PRESET_VINTAGE] = {
         "0/0.11 0.42/0.51 1/0.95",
         "0.50/0.48",
@@ -235,27 +251,27 @@ static int interpolate(AVFilterContext *ctx, uint8_t *y, const struct keypoint *
         point = point->next;
     }
 
-#define B 0 /* sub  diagonal (below main) */
-#define M 1 /* main diagonal (center) */
-#define A 2 /* sup  diagonal (above main) */
+#define BD 0 /* sub  diagonal (below main) */
+#define MD 1 /* main diagonal (center) */
+#define AD 2 /* sup  diagonal (above main) */
 
     /* left side of the polynomials into a tridiagonal matrix. */
-    matrix[0][M] = matrix[n - 1][M] = 1;
+    matrix[0][MD] = matrix[n - 1][MD] = 1;
     for (i = 1; i < n - 1; i++) {
-        matrix[i][B] = h[i-1];
-        matrix[i][M] = 2 * (h[i-1] + h[i]);
-        matrix[i][A] = h[i];
+        matrix[i][BD] = h[i-1];
+        matrix[i][MD] = 2 * (h[i-1] + h[i]);
+        matrix[i][AD] = h[i];
     }
 
     /* tridiagonal solving of the linear system */
     for (i = 1; i < n; i++) {
-        double den = matrix[i][M] - matrix[i][B] * matrix[i-1][A];
+        double den = matrix[i][MD] - matrix[i][BD] * matrix[i-1][AD];
         double k = den ? 1./den : 1.;
-        matrix[i][A] *= k;
-        r[i] = (r[i] - matrix[i][B] * r[i - 1]) * k;
+        matrix[i][AD] *= k;
+        r[i] = (r[i] - matrix[i][BD] * r[i - 1]) * k;
     }
     for (i = n - 2; i >= 0; i--)
-        r[i] = r[i] - matrix[i][A] * r[i + 1];
+        r[i] = r[i] - matrix[i][AD] * r[i + 1];
 
     /* compute the graph with x=[0..255] */
     i = 0;
@@ -295,16 +311,70 @@ end:
     return ret;
 }
 
-static av_cold int init(AVFilterContext *ctx, const char *args)
+static int parse_psfile(AVFilterContext *ctx, const char *fname)
+{
+    CurvesContext *curves = ctx->priv;
+    uint8_t *buf;
+    size_t size;
+    int i, ret, av_unused(version), nb_curves;
+    AVBPrint ptstr;
+    static const int comp_ids[] = {3, 0, 1, 2};
+
+    av_bprint_init(&ptstr, 0, AV_BPRINT_SIZE_AUTOMATIC);
+
+    ret = av_file_map(fname, &buf, &size, 0, NULL);
+    if (ret < 0)
+        return ret;
+
+#define READ16(dst) do {                \
+    if (size < 2)                       \
+        return AVERROR_INVALIDDATA;     \
+    dst = AV_RB16(buf);                 \
+    buf  += 2;                          \
+    size -= 2;                          \
+} while (0)
+
+    READ16(version);
+    READ16(nb_curves);
+    for (i = 0; i < FFMIN(nb_curves, FF_ARRAY_ELEMS(comp_ids)); i++) {
+        int nb_points, n;
+        av_bprint_clear(&ptstr);
+        READ16(nb_points);
+        for (n = 0; n < nb_points; n++) {
+            int y, x;
+            READ16(y);
+            READ16(x);
+            av_bprintf(&ptstr, "%f/%f ", x / 255., y / 255.);
+        }
+        if (*ptstr.str) {
+            char **pts = &curves->comp_points_str[comp_ids[i]];
+            if (!*pts) {
+                *pts = av_strdup(ptstr.str);
+                av_log(ctx, AV_LOG_DEBUG, "curves %d (intid=%d) [%d points]: [%s]\n",
+                       i, comp_ids[i], nb_points, *pts);
+                if (!*pts) {
+                    ret = AVERROR(ENOMEM);
+                    goto end;
+                }
+            }
+        }
+    }
+end:
+    av_bprint_finalize(&ptstr, NULL);
+    av_file_unmap(buf, size);
+    return ret;
+}
+
+static av_cold int init(AVFilterContext *ctx)
 {
     int i, j, ret;
     CurvesContext *curves = ctx->priv;
-    struct keypoint *comp_points[NB_COMP] = {0};
+    struct keypoint *comp_points[NB_COMP + 1] = {0};
     char **pts = curves->comp_points_str;
     const char *allp = curves->comp_points_str_all;
 
-    if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all)
-        allp = curves_presets[curves->preset].all;
+    //if (!allp && curves->preset != PRESET_NONE && curves_presets[curves->preset].all)
+    //    allp = curves_presets[curves->preset].all;
 
     if (allp) {
         for (i = 0; i < NB_COMP; i++) {
@@ -315,15 +385,27 @@ static av_cold int init(AVFilterContext *ctx, const char *args)
         }
     }
 
+    if (curves->psfile) {
+        ret = parse_psfile(ctx, curves->psfile);
+        if (ret < 0)
+            return ret;
+    }
+
     if (curves->preset != PRESET_NONE) {
-        if (!pts[0]) pts[0] = av_strdup(curves_presets[curves->preset].r);
-        if (!pts[1]) pts[1] = av_strdup(curves_presets[curves->preset].g);
-        if (!pts[2]) pts[2] = av_strdup(curves_presets[curves->preset].b);
-        if (!pts[0] || !pts[1] || !pts[2])
-            return AVERROR(ENOMEM);
+#define SET_COMP_IF_NOT_SET(n, name) do {                           \
+    if (!pts[n] && curves_presets[curves->preset].name) {           \
+        pts[n] = av_strdup(curves_presets[curves->preset].name);    \
+        if (!pts[n])                                                \
+            return AVERROR(ENOMEM);                                 \
+    }                                                               \
+} while (0)
+        SET_COMP_IF_NOT_SET(0, r);
+        SET_COMP_IF_NOT_SET(1, g);
+        SET_COMP_IF_NOT_SET(2, b);
+        SET_COMP_IF_NOT_SET(3, master);
     }
 
-    for (i = 0; i < NB_COMP; i++) {
+    for (i = 0; i < NB_COMP + 1; i++) {
         ret = parse_points_str(ctx, comp_points + i, curves->comp_points_str[i]);
         if (ret < 0)
             return ret;
@@ -332,6 +414,12 @@ static av_cold int init(AVFilterContext *ctx, const char *args)
             return ret;
     }
 
+    if (pts[NB_COMP]) {
+        for (i = 0; i < NB_COMP; i++)
+            for (j = 0; j < 256; j++)
+                curves->graph[i][j] = curves->graph[NB_COMP][curves->graph[i][j]];
+    }
+
     if (av_log_get_level() >= AV_LOG_VERBOSE) {
         for (i = 0; i < NB_COMP; i++) {
             struct keypoint *point = comp_points[i];
@@ -348,7 +436,7 @@ static av_cold int init(AVFilterContext *ctx, const char *args)
         }
     }
 
-    for (i = 0; i < NB_COMP; i++) {
+    for (i = 0; i < NB_COMP + 1; i++) {
         struct keypoint *point = comp_points[i];
         while (point) {
             struct keypoint *next = point->next;
@@ -362,20 +450,43 @@ static av_cold int init(AVFilterContext *ctx, const char *args)
 
 static int query_formats(AVFilterContext *ctx)
 {
-    static const enum AVPixelFormat pix_fmts[] = {AV_PIX_FMT_RGB24, AV_PIX_FMT_NONE};
+    static const enum AVPixelFormat pix_fmts[] = {
+        AV_PIX_FMT_RGB24,  AV_PIX_FMT_BGR24,
+        AV_PIX_FMT_RGBA,   AV_PIX_FMT_BGRA,
+        AV_PIX_FMT_ARGB,   AV_PIX_FMT_ABGR,
+        AV_PIX_FMT_0RGB,   AV_PIX_FMT_0BGR,
+        AV_PIX_FMT_RGB0,   AV_PIX_FMT_BGR0,
+        AV_PIX_FMT_NONE
+    };
     ff_set_common_formats(ctx, ff_make_format_list(pix_fmts));
     return 0;
 }
 
+static int config_input(AVFilterLink *inlink)
+{
+    CurvesContext *curves = inlink->dst->priv;
+    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+
+    ff_fill_rgba_map(curves->rgba_map, inlink->format);
+    curves->step = av_get_padded_bits_per_pixel(desc) >> 3;
+
+    return 0;
+}
+
 static int filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
-    int x, y, i, direct = 0;
+    int x, y, direct = 0;
     AVFilterContext *ctx = inlink->dst;
     CurvesContext *curves = ctx->priv;
     AVFilterLink *outlink = inlink->dst->outputs[0];
     AVFrame *out;
     uint8_t *dst;
     const uint8_t *src;
+    const int step = curves->step;
+    const uint8_t r = curves->rgba_map[R];
+    const uint8_t g = curves->rgba_map[G];
+    const uint8_t b = curves->rgba_map[B];
+    const uint8_t a = curves->rgba_map[A];
 
     if (av_frame_is_writable(in)) {
         direct = 1;
@@ -393,12 +504,13 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     src = in ->data[0];
 
     for (y = 0; y < inlink->h; y++) {
-        uint8_t *dstp = dst;
-        const uint8_t *srcp = src;
-
-        for (x = 0; x < inlink->w; x++)
-            for (i = 0; i < NB_COMP; i++, dstp++, srcp++)
-                *dstp = curves->graph[i][*srcp];
+        for (x = 0; x < inlink->w * step; x += step) {
+            dst[x + r] = curves->graph[R][src[x + r]];
+            dst[x + g] = curves->graph[G][src[x + g]];
+            dst[x + b] = curves->graph[B][src[x + b]];
+            if (!direct && step == 4)
+                dst[x + a] = src[x + a];
+        }
         dst += out->linesize[0];
         src += in ->linesize[0];
     }
@@ -414,6 +526,7 @@ static const AVFilterPad curves_inputs[] = {
         .name         = "default",
         .type         = AVMEDIA_TYPE_VIDEO,
         .filter_frame = filter_frame,
+        .config_props = config_input,
     },
     { NULL }
 };
@@ -435,4 +548,5 @@ AVFilter avfilter_vf_curves = {
     .inputs        = curves_inputs,
     .outputs       = curves_outputs,
     .priv_class    = &curves_class,
+    .flags         = AVFILTER_FLAG_SUPPORT_TIMELINE,
 };