Signed-off-by: Paul B Mahol <[email protected]>
---
doc/filters.texi | 29 +++
libavfilter/Makefile | 1 +
libavfilter/allfilters.c | 1 +
libavfilter/vf_lut3d.c | 404 +++++++++++++++++++++++++++++++++++++++
4 files changed, 435 insertions(+)
diff --git a/doc/filters.texi b/doc/filters.texi
index 32c95b591c..72ce95f94a 100644
--- a/doc/filters.texi
+++ b/doc/filters.texi
@@ -10962,6 +10962,35 @@ Set maximal size in number of frames. Default is 0.
Set first frame of loop. Default is 0.
@end table
+@section lut1d
+
+Apply a 1D LUT to an input video.
+
+The filter accepts the following options:
+
+@table @option
+@item file
+Set the 1D LUT file name.
+
+Currently supported formats:
+@table @samp
+@item cube
+Iridas
+@end table
+
+@item interp
+Select interpolation mode.
+
+Available values are:
+
+@table @samp
+@item nearest
+Use values from the nearest defined point.
+@item linear
+Interpolate values using the linear interpolation.
+@end table
+@end table
+
@anchor{lut3d}
@section lut3d
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index e5d3a57af7..e412000c8f 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -258,6 +258,7 @@ OBJS-$(CONFIG_LIBVMAF_FILTER) +=
vf_libvmaf.o framesync.o
OBJS-$(CONFIG_LIMITER_FILTER) += vf_limiter.o
OBJS-$(CONFIG_LOOP_FILTER) += f_loop.o
OBJS-$(CONFIG_LUMAKEY_FILTER) += vf_lumakey.o
+OBJS-$(CONFIG_LUT1D_FILTER) += vf_lut3d.o
OBJS-$(CONFIG_LUT_FILTER) += vf_lut.o
OBJS-$(CONFIG_LUT2_FILTER) += vf_lut2.o framesync.o
OBJS-$(CONFIG_LUT3D_FILTER) += vf_lut3d.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 9732ae5345..2fa9460335 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -246,6 +246,7 @@ extern AVFilter ff_vf_limiter;
extern AVFilter ff_vf_loop;
extern AVFilter ff_vf_lumakey;
extern AVFilter ff_vf_lut;
+extern AVFilter ff_vf_lut1d;
extern AVFilter ff_vf_lut2;
extern AVFilter ff_vf_lut3d;
extern AVFilter ff_vf_lutrgb;
diff --git a/libavfilter/vf_lut3d.c b/libavfilter/vf_lut3d.c
index 27b79b860b..b5673b82bf 100644
--- a/libavfilter/vf_lut3d.c
+++ b/libavfilter/vf_lut3d.c
@@ -1,5 +1,6 @@
/*
* Copyright (c) 2013 Clément Bœsch
+ * Copyright (c) 2018 Paul B Mahol
*
* This file is part of FFmpeg.
*
@@ -975,3 +976,406 @@ AVFilter ff_vf_haldclut = {
.flags = AVFILTER_FLAG_SUPPORT_TIMELINE_INTERNAL |
AVFILTER_FLAG_SLICE_THREADS,
};
#endif
+
+#if CONFIG_LUT1D_FILTER
+
+enum interp_1d_mode {
+ INTERPOLATE_1D_NEAREST,
+ INTERPOLATE_1D_LINEAR,
+ NB_INTERP_1D_MODE
+};
+
+#define MAX_1D_LEVEL 65536
+
+typedef struct LUT1DContext {
+ const AVClass *class;
+ char *file;
+ int interpolation; ///<interp_1d_mode
+ uint8_t rgba_map[4];
+ int step;
+ float lut[3][MAX_1D_LEVEL];
+ int lutsize;
+ avfilter_action_func *interp;
+} LUT1DContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(LUT1DContext, x)
+
+static void set_identity_matrix_1d(LUT1DContext *lut1d, int size)
+{
+ const float c = 1. / (size - 1);
+ int i;
+
+ lut1d->lutsize = size;
+ for (i = 0; i < size; i++) {
+ lut1d->lut[0][i] = i * c;
+ lut1d->lut[1][i] = i * c;
+ lut1d->lut[2][i] = i * c;
+ }
+}
+
+static int parse_cube_1d(AVFilterContext *ctx, FILE *f)
+{
+ LUT1DContext *lut1d = ctx->priv;
+ char line[MAX_LINE_SIZE];
+ float min[3] = {0.0, 0.0, 0.0};
+ float max[3] = {1.0, 1.0, 1.0};
+
+ while (fgets(line, sizeof(line), f)) {
+ if (!strncmp(line, "LUT_1D_SIZE ", 12)) {
+ const int size = strtol(line + 12, NULL, 0);
+ int i;
+
+ if (size < 2 || size > MAX_1D_LEVEL) {
+ av_log(ctx, AV_LOG_ERROR, "Too large or invalid 1D LUT
size\n");
+ return AVERROR(EINVAL);
+ }
+ lut1d->lutsize = size;
+ for (i = 0; i < size; i++) {
+ do {
+try_again:
+ NEXT_LINE(0);
+ if (!strncmp(line, "DOMAIN_", 7)) {
+ float *vals = NULL;
+ if (!strncmp(line + 7, "MIN ", 4)) vals = min;
+ else if (!strncmp(line + 7, "MAX ", 4)) vals = max;
+ if (!vals)
+ return AVERROR_INVALIDDATA;
+ sscanf(line + 11, "%f %f %f", vals, vals + 1, vals +
2);
+ av_log(ctx, AV_LOG_DEBUG, "min: %f %f %f | max: %f %f
%f\n",
+ min[0], min[1], min[2], max[0], max[1], max[2]);
+ goto try_again;
+ }
+ } while (skip_line(line));
+ if (sscanf(line, "%f %f %f", &lut1d->lut[0][i],
&lut1d->lut[1][i], &lut1d->lut[2][i]) != 3)
+ return AVERROR_INVALIDDATA;
+ lut1d->lut[0][i] *= max[0] - min[0];
+ lut1d->lut[1][i] *= max[1] - min[1];
+ lut1d->lut[2][i] *= max[2] - min[2];
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+static const AVOption lut1d_options[] = {
+ { "file", "set 1D LUT file name", OFFSET(file), AV_OPT_TYPE_STRING,
{.str=NULL}, .flags = FLAGS },
+ { "interp", "select interpolation mode", OFFSET(interpolation),
AV_OPT_TYPE_INT, {.i64=INTERPOLATE_1D_NEAREST}, 0, NB_INTERP_1D_MODE-1, FLAGS,
"interp_mode" },
+ { "nearest", "use values from the nearest defined points",
0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_NEAREST}, INT_MIN,
INT_MAX, FLAGS, "interp_mode" },
+ { "linear", "use values from the linear interpolation",
0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_LINEAR}, INT_MIN, INT_MAX,
FLAGS, "interp_mode" },
+ { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(lut1d);
+
+static inline float interp_1d_nearest(const LUT1DContext *lut1d,
+ int idx, const float s)
+{
+ return lut1d->lut[idx][NEAR(s)];
+}
+
+#define NEXT1D(x) (FFMIN((int)(x) + 1, lut1d->lutsize - 1))
+
+static inline float interp_1d_linear(const LUT1DContext *lut1d,
+ int idx, const float s)
+{
+ const int prev = PREV(s);
+ const int next = NEXT1D(s);
+ const float d = s - prev;
+ const float p = lut1d->lut[idx][prev];
+ const float n = lut1d->lut[idx][next];
+
+ return lerpf(p, n, d);
+}
+
+#define DEFINE_INTERP_FUNC_PLANAR_1D(name, nbits, depth)
\
+static int interp_1d_##nbits##_##name##_p##depth(AVFilterContext *ctx, void
*arg, int jobnr, int nb_jobs) \
+{
\
+ int x, y;
\
+ const LUT1DContext *lut1d = ctx->priv;
\
+ const ThreadData *td = arg;
\
+ const AVFrame *in = td->in;
\
+ const AVFrame *out = td->out;
\
+ const int direct = out == in;
\
+ const int slice_start = (in->height * jobnr ) / nb_jobs;
\
+ const int slice_end = (in->height * (jobnr+1)) / nb_jobs;
\
+ uint8_t *grow = out->data[0] + slice_start * out->linesize[0];
\
+ uint8_t *brow = out->data[1] + slice_start * out->linesize[1];
\
+ uint8_t *rrow = out->data[2] + slice_start * out->linesize[2];
\
+ uint8_t *arow = out->data[3] + slice_start * out->linesize[3];
\
+ const uint8_t *srcgrow = in->data[0] + slice_start * in->linesize[0];
\
+ const uint8_t *srcbrow = in->data[1] + slice_start * in->linesize[1];
\
+ const uint8_t *srcrrow = in->data[2] + slice_start * in->linesize[2];
\
+ const uint8_t *srcarow = in->data[3] + slice_start * in->linesize[3];
\
+ const float scale = (1. / ((1<<depth) - 1)) * (lut1d->lutsize - 1);
\
+
\
+ for (y = slice_start; y < slice_end; y++) {
\
+ uint##nbits##_t *dstg = (uint##nbits##_t *)grow;
\
+ uint##nbits##_t *dstb = (uint##nbits##_t *)brow;
\
+ uint##nbits##_t *dstr = (uint##nbits##_t *)rrow;
\
+ uint##nbits##_t *dsta = (uint##nbits##_t *)arow;
\
+ const uint##nbits##_t *srcg = (const uint##nbits##_t *)srcgrow;
\
+ const uint##nbits##_t *srcb = (const uint##nbits##_t *)srcbrow;
\
+ const uint##nbits##_t *srcr = (const uint##nbits##_t *)srcrrow;
\
+ const uint##nbits##_t *srca = (const uint##nbits##_t *)srcarow;
\
+ for (x = 0; x < in->width; x++) {
\
+ float r = srcr[x] * scale;
\
+ float g = srcg[x] * scale;
\
+ float b = srcb[x] * scale;
\
+ r = interp_1d_##name(lut1d, 0, r);
\
+ g = interp_1d_##name(lut1d, 1, g);
\
+ b = interp_1d_##name(lut1d, 2, b);
\
+ dstr[x] = av_clip_uintp2(r * (float)((1<<depth) - 1), depth);
\
+ dstg[x] = av_clip_uintp2(g * (float)((1<<depth) - 1), depth);
\
+ dstb[x] = av_clip_uintp2(b * (float)((1<<depth) - 1), depth);
\
+ if (!direct && in->linesize[3])
\
+ dsta[x] = srca[x];
\
+ }
\
+ grow += out->linesize[0];
\
+ brow += out->linesize[1];
\
+ rrow += out->linesize[2];
\
+ arow += out->linesize[3];
\
+ srcgrow += in->linesize[0];
\
+ srcbrow += in->linesize[1];
\
+ srcrrow += in->linesize[2];
\
+ srcarow += in->linesize[3];
\
+ }
\
+ return 0;
\
+}
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 8, 8)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear, 8, 8)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 9)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 9)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 10)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 10)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 12)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 12)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 14)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 14)
+
+DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 16)
+DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 16)
+
+#define DEFINE_INTERP_FUNC_1D(name, nbits)
\
+static int interp_1d_##nbits##_##name(AVFilterContext *ctx, void *arg, int
jobnr, int nb_jobs) \
+{
\
+ int x, y;
\
+ const LUT1DContext *lut1d = ctx->priv;
\
+ const ThreadData *td = arg;
\
+ const AVFrame *in = td->in;
\
+ const AVFrame *out = td->out;
\
+ const int direct = out == in;
\
+ const int step = lut1d->step;
\
+ const uint8_t r = lut1d->rgba_map[R];
\
+ const uint8_t g = lut1d->rgba_map[G];
\
+ const uint8_t b = lut1d->rgba_map[B];
\
+ const uint8_t a = lut1d->rgba_map[A];
\
+ const int slice_start = (in->height * jobnr ) / nb_jobs;
\
+ const int slice_end = (in->height * (jobnr+1)) / nb_jobs;
\
+ uint8_t *dstrow = out->data[0] + slice_start * out->linesize[0];
\
+ const uint8_t *srcrow = in ->data[0] + slice_start * in ->linesize[0];
\
+ const float scale = (1. / ((1<<nbits) - 1)) * (lut1d->lutsize - 1);
\
+
\
+ for (y = slice_start; y < slice_end; y++) {
\
+ uint##nbits##_t *dst = (uint##nbits##_t *)dstrow;
\
+ const uint##nbits##_t *src = (const uint##nbits##_t *)srcrow;
\
+ for (x = 0; x < in->width * step; x += step) {
\
+ float rr = src[x + r] * scale;
\
+ float gg = src[x + g] * scale;
\
+ float bb = src[x + b] * scale;
\
+ rr = interp_1d_##name(lut1d, 0, rr);
\
+ gg = interp_1d_##name(lut1d, 1, gg);
\
+ bb = interp_1d_##name(lut1d, 2, bb);
\
+ dst[x + r] = av_clip_uint##nbits(rr * (float)((1<<nbits) - 1));
\
+ dst[x + g] = av_clip_uint##nbits(gg * (float)((1<<nbits) - 1));
\
+ dst[x + b] = av_clip_uint##nbits(bb * (float)((1<<nbits) - 1));
\
+ if (!direct && step == 4)
\
+ dst[x + a] = src[x + a];
\
+ }
\
+ dstrow += out->linesize[0];
\
+ srcrow += in ->linesize[0];
\
+ }
\
+ return 0;
\
+}
+
+DEFINE_INTERP_FUNC_1D(nearest, 8)
+DEFINE_INTERP_FUNC_1D(linear, 8)
+
+DEFINE_INTERP_FUNC_1D(nearest, 16)
+DEFINE_INTERP_FUNC_1D(linear, 16)
+
+static int config_input_1d(AVFilterLink *inlink)
+{
+ int depth, is16bit = 0, planar = 0;
+ LUT1DContext *lut1d = inlink->dst->priv;
+ const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format);
+
+ depth = desc->comp[0].depth;
+
+ switch (inlink->format) {
+ case AV_PIX_FMT_RGB48:
+ case AV_PIX_FMT_BGR48:
+ case AV_PIX_FMT_RGBA64:
+ case AV_PIX_FMT_BGRA64:
+ is16bit = 1;
+ break;
+ case AV_PIX_FMT_GBRP9:
+ case AV_PIX_FMT_GBRP10:
+ case AV_PIX_FMT_GBRP12:
+ case AV_PIX_FMT_GBRP14:
+ case AV_PIX_FMT_GBRP16:
+ case AV_PIX_FMT_GBRAP10:
+ case AV_PIX_FMT_GBRAP12:
+ case AV_PIX_FMT_GBRAP16:
+ is16bit = 1;
+ case AV_PIX_FMT_GBRP:
+ case AV_PIX_FMT_GBRAP:
+ planar = 1;
+ break;
+ }
+
+ ff_fill_rgba_map(lut1d->rgba_map, inlink->format);
+ lut1d->step = av_get_padded_bits_per_pixel(desc) >> (3 + is16bit);
+
+#define SET_FUNC_1D(name) do { \
+ if (planar) { \
+ switch (depth) { \
+ case 8: lut1d->interp = interp_1d_8_##name##_p8; break; \
+ case 9: lut1d->interp = interp_1d_16_##name##_p9; break; \
+ case 10: lut1d->interp = interp_1d_16_##name##_p10; break; \
+ case 12: lut1d->interp = interp_1d_16_##name##_p12; break; \
+ case 14: lut1d->interp = interp_1d_16_##name##_p14; break; \
+ case 16: lut1d->interp = interp_1d_16_##name##_p16; break; \
+ } \
+ } else if (is16bit) { lut1d->interp = interp_1d_16_##name; \
+ } else { lut1d->interp = interp_1d_8_##name; } \
+} while (0)
+
+ switch (lut1d->interpolation) {
+ case INTERPOLATE_1D_NEAREST: SET_FUNC_1D(nearest); break;
+ case INTERPOLATE_1D_LINEAR: SET_FUNC_1D(linear); break;
+ default:
+ av_assert0(0);
+ }
+
+ return 0;
+}
+
+static av_cold int lut1d_init(AVFilterContext *ctx)
+{
+ int ret;
+ FILE *f;
+ const char *ext;
+ LUT1DContext *lut1d = ctx->priv;
+
+ if (!lut1d->file) {
+ set_identity_matrix_1d(lut1d, 32);
+ return 0;
+ }
+
+ f = fopen(lut1d->file, "r");
+ if (!f) {
+ ret = AVERROR(errno);
+ av_log(ctx, AV_LOG_ERROR, "%s: %s\n", lut1d->file, av_err2str(ret));
+ return ret;
+ }
+
+ ext = strrchr(lut1d->file, '.');
+ if (!ext) {
+ av_log(ctx, AV_LOG_ERROR, "Unable to guess the format from the
extension\n");
+ ret = AVERROR_INVALIDDATA;
+ goto end;
+ }
+ ext++;
+
+ if (!av_strcasecmp(ext, "cube") || !av_strcasecmp(ext, "1dlut")) {
+ ret = parse_cube_1d(ctx, f);
+ } else {
+ av_log(ctx, AV_LOG_ERROR, "Unrecognized '.%s' file type\n", ext);
+ ret = AVERROR(EINVAL);
+ }
+
+ if (!ret && !lut1d->lutsize) {
+ av_log(ctx, AV_LOG_ERROR, "1D LUT is empty\n");
+ ret = AVERROR_INVALIDDATA;
+ }
+
+end:
+ fclose(f);
+ return ret;
+}
+
+static AVFrame *apply_1d_lut(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterContext *ctx = inlink->dst;
+ LUT1DContext *lut1d = ctx->priv;
+ AVFilterLink *outlink = inlink->dst->outputs[0];
+ AVFrame *out;
+ ThreadData td;
+
+ if (av_frame_is_writable(in)) {
+ out = in;
+ } else {
+ out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
+ if (!out) {
+ av_frame_free(&in);
+ return NULL;
+ }
+ av_frame_copy_props(out, in);
+ }
+
+ td.in = in;
+ td.out = out;
+ ctx->internal->execute(ctx, lut1d->interp, &td, NULL, FFMIN(outlink->h,
ff_filter_get_nb_threads(ctx)));
+
+ if (out != in)
+ av_frame_free(&in);
+
+ return out;
+}
+
+static int filter_frame_1d(AVFilterLink *inlink, AVFrame *in)
+{
+ AVFilterLink *outlink = inlink->dst->outputs[0];
+ AVFrame *out = apply_1d_lut(inlink, in);
+ if (!out)
+ return AVERROR(ENOMEM);
+ return ff_filter_frame(outlink, out);
+}
+
+static const AVFilterPad lut1d_inputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ .filter_frame = filter_frame_1d,
+ .config_props = config_input_1d,
+ },
+ { NULL }
+};
+
+static const AVFilterPad lut1d_outputs[] = {
+ {
+ .name = "default",
+ .type = AVMEDIA_TYPE_VIDEO,
+ },
+ { NULL }
+};
+
+AVFilter ff_vf_lut1d = {
+ .name = "lut1d",
+ .description = NULL_IF_CONFIG_SMALL("Adjust colors using a 1D LUT."),
+ .priv_size = sizeof(LUT1DContext),
+ .init = lut1d_init,
+ .query_formats = query_formats,
+ .inputs = lut1d_inputs,
+ .outputs = lut1d_outputs,
+ .priv_class = &lut1d_class,
+ .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC |
AVFILTER_FLAG_SLICE_THREADS,
+};
+#endif
--
2.17.1
_______________________________________________
ffmpeg-devel mailing list
[email protected]
http://ffmpeg.org/mailman/listinfo/ffmpeg-devel