[FFmpeg-devel] [PATCH] avfilter: add lut1d filter
Paul B Mahol
onemda at gmail.com
Thu Aug 23 12:44:53 EEST 2018
Signed-off-by: Paul B Mahol <onemda at gmail.com>
---
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
+ at section lut1d
+
+Apply a 1D LUT to an input video.
+
+The filter accepts the following options:
+
+ at table @option
+ at item file
+Set the 1D LUT file name.
+
+Currently supported formats:
+ at table @samp
+ at item cube
+Iridas
+ at end table
+
+ at item interp
+Select interpolation mode.
+
+Available values are:
+
+ at table @samp
+ at item nearest
+Use values from the nearest defined point.
+ at item linear
+Interpolate values using the linear interpolation.
+ at end table
+ at 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
More information about the ffmpeg-devel
mailing list