From patchwork Thu Aug 23 09:44:53 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 10102 Delivered-To: ffmpegpatchwork@gmail.com Received: by 2002:a02:12c4:0:0:0:0:0 with SMTP id 65-v6csp1897512jap; Thu, 23 Aug 2018 02:45:13 -0700 (PDT) X-Google-Smtp-Source: ANB0Vda8Ol9PdDHkBJJDV2n1ztVsb+swuewq1bCJQtdz5Lp4Ko9hVF2bZKjQKqFkARv6BObC9AqZ X-Received: by 2002:adf:9281:: with SMTP id 1-v6mr9404474wrn.69.1535017513142; Thu, 23 Aug 2018 02:45:13 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1535017513; cv=none; d=google.com; s=arc-20160816; b=ZPyrfFVW7E5crqlj/r/tnrqJOwCmWN5lEqh6iEJou30ut0j0xUVJqzdCKaf5ZyTFSM athYfLQvFz6MgLY1D4nzbQ/09TEwUg9Gx1gLlTdZ2e4cN6mzGVHkzMtYBYDtDCRc05C8 3aGCyLi16AYbI5wmc9D/rrKxmbtmQArB2jvUzub8XNSro+PRyMi34/jWnBRZ/oFelDzf GYxCzOTb9uEHYeOxC7YlbV1VZ7/gjXAxW5E1Y7CMmJv6r19dWq4vSsfbIi7gKcaTPR7h 9x5a5xGNsr1UwgVHfcPdhfy2ir8mJiW1oPYkAjiBdSD2cSqUPhI41dFecMLt70gycdiI HOhw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:reply-to:list-subscribe :list-help:list-post:list-archive:list-unsubscribe:list-id :precedence:subject:mime-version:message-id:date:to:from :dkim-signature:delivered-to:arc-authentication-results; bh=Gn06NgCKPd569qunSTIWBZQ0N7Sqj+4Utg8iF8Ra7Nw=; b=kSA5/I4Qkpy9XxRr8XcLJBZPd4VhSjDCOKHGQfQBZhdtlYANO4RGNyFj7htMP5Ajy7 Ol03GUa7ZEUcmx1rh81n/nCL23kvAWe3EafahGhMXKixMNVeOKDT+fZL6J5NJ0wxppXQ aSqpqinsLTK2nKssxY7AsaPchIziXnRPDQeR2gWNETZnlB54I2fmtjiOTULEnOq70pIX bA6VmfW2IIxzugJnmndWOsUAMSVwTppqxrDA1aF6bfoBTvuuG1v/uPiIriHYYbsRVLf+ i4uAa6CjIoWCSIWEeLjmGi0lptEt/SBOSB+hfyh79FEG4znN8h9PoNubI8hUcSX3vrEG K8/Q== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=ML3iEj9z; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id 194-v6si3220379wmk.174.2018.08.23.02.45.12; Thu, 23 Aug 2018 02:45:13 -0700 (PDT) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=ML3iEj9z; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=QUARANTINE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 34D8F6883B4; Thu, 23 Aug 2018 12:45:09 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr1-f49.google.com (mail-wr1-f49.google.com [209.85.221.49]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 450DE68046B for ; Thu, 23 Aug 2018 12:45:03 +0300 (EEST) Received: by mail-wr1-f49.google.com with SMTP id a108-v6so4048402wrc.13 for ; Thu, 23 Aug 2018 02:45:04 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=1HM/0XKgm9ZuZ232zVHyG4+ojG0IueOrk7brysWIcbo=; b=ML3iEj9zyem9i0c+pkyZEgzYQpMEjH2IYSOgAAhFd+H9emaFk4mOMkJuCgdbw/9UPx CdAdajOR1NO53yyYtyU7HZi9zo55APKiTstHb5xUm0wJ5UU6tjZe7CT7Nqgq1QPOlx0o hjovKYFO4sHK8/LCbwbwQ+q+zKvd0nFjHrLLr+3GH5EkiTR0dBb5/aZyjWfLGZ0MXjt6 WH4lZrtdboWOR1tHur6eOy8UV5rpEd/ZU3OUaVlktIwTSO3z8habfh3+C0qOTDZLai8v HyUE+lE+YKOmkNASNyUAevelmPwzAqwZMCFuyDHlyFfzXa8OFv1c1FGbiCWH54gseEAm cPRw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=1HM/0XKgm9ZuZ232zVHyG4+ojG0IueOrk7brysWIcbo=; b=dbjOJ0LEEvvHXh0TjdfR93Y+v9e8PHJ7gpRmLrX7s9DWpH7LrhizYXBekkcfQodCdU ESsjnOtW4B1Qwg3L7L0jVgKnaKG2IDBARBpNefDzJPZCBjwWknb1DLjD4YgDT3ksKQtP mfiBe+/uj7EzEKP8Ew6YWtdpxeK2lz2Sh1PnG2tolxcCAGhnTB+RoRAoY+XuZY1HKc2S gXOSqPn3I0nIMhy0IT7NAbWi2nDhrj5c75ZBdFXngSHzzWC92RlIApJPtgg/e5VZetKF 91J5qFy+Mh0eoe/q2bZ9+QzQyPaUV6Y3YPdz9Dr0zIwP2duO+LSs5g5YXRrrB1tt563b nkeg== X-Gm-Message-State: APzg51DYMVgTZngnR+fIYe3+926e4iTxO2TqGeWVfew55bjSTrUpt9NM z35G/2GhAaVxUHiz6TjQW/Zpmplk X-Received: by 2002:a5d:6150:: with SMTP id y16-v6mr10129476wrt.141.1535017503856; Thu, 23 Aug 2018 02:45:03 -0700 (PDT) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id 66-v6sm4650371wmw.34.2018.08.23.02.45.02 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 23 Aug 2018 02:45:03 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Thu, 23 Aug 2018 11:44:53 +0200 Message-Id: <20180823094453.28449-1-onemda@gmail.com> X-Mailer: git-send-email 2.17.1 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] avfilter: add lut1d filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- 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; ///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<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<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<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<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