From c4cda4eb878affb89c031fa7e0cc3349c9a502cd Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Thu, 23 Aug 2018 09:34:44 +0200 Subject: avfilter: add lut1d filter --- libavfilter/vf_lut3d.c | 448 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 448 insertions(+) (limited to 'libavfilter/vf_lut3d.c') diff --git a/libavfilter/vf_lut3d.c b/libavfilter/vf_lut3d.c index 27b79b860b..4d985c599f 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,450 @@ 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, + INTERPOLATE_1D_CUBIC, + NB_INTERP_1D_MODE +}; + +#define MAX_1D_LEVEL 65536 + +typedef struct LUT1DContext { + const AVClass *class; + char *file; + int interpolation; ///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; + } else if (!strncmp(line, "LUT_1D_INPUT_RANGE ", 19)) { + sscanf(line + 19, "%f %f", min, max); + min[1] = min[2] = min[0]; + max[1] = max[2] = max[0]; + 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_LINEAR}, 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" }, + { "cubic", "use values from the cubic interpolation", 0, AV_OPT_TYPE_CONST, {.i64=INTERPOLATE_1D_CUBIC}, 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); +} + +static inline float interp_1d_cubic(const LUT1DContext *lut1d, + int idx, const float s) +{ + const int prev = PREV(s); + const int next = NEXT1D(s); + const float mu = s - prev; + float a0, a1, a2, a3, mu2; + + float y0 = lut1d->lut[idx][FFMAX(prev - 1, 0)]; + float y1 = lut1d->lut[idx][prev]; + float y2 = lut1d->lut[idx][next]; + float y3 = lut1d->lut[idx][FFMIN(next + 1, lut1d->lutsize - 1)]; + + + mu2 = mu * mu; + a0 = y3 - y2 - y0 + y1; + a1 = y0 - y1 - a0; + a2 = y2 - y0; + a3 = y1; + + return a0 * mu * mu2 + a1 * mu2 + a2 * mu + a3; +} + +#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 factor = (1 << depth) - 1; \ + const float scale = (1. / factor) * (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 * factor, depth); \ + dstg[x] = av_clip_uintp2(g * factor, depth); \ + dstb[x] = av_clip_uintp2(b * factor, 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(cubic, 8, 8) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 9) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 9) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 9) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 10) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 10) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 10) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 12) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 12) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 12) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 14) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 14) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 16, 14) + +DEFINE_INTERP_FUNC_PLANAR_1D(nearest, 16, 16) +DEFINE_INTERP_FUNC_PLANAR_1D(linear, 16, 16) +DEFINE_INTERP_FUNC_PLANAR_1D(cubic, 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 factor = (1 << nbits) - 1; \ + const float scale = (1. / factor) * (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 * factor); \ + dst[x + g] = av_clip_uint##nbits(gg * factor); \ + dst[x + b] = av_clip_uint##nbits(bb * factor); \ + 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(cubic, 8) + +DEFINE_INTERP_FUNC_1D(nearest, 16) +DEFINE_INTERP_FUNC_1D(linear, 16) +DEFINE_INTERP_FUNC_1D(cubic, 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; + case INTERPOLATE_1D_CUBIC: SET_FUNC_1D(cubic); 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 -- cgit v1.2.3