From patchwork Thu Aug 23 12:15:06 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: 10104 Delivered-To: ffmpegpatchwork@gmail.com Received: by 2002:a02:12c4:0:0:0:0:0 with SMTP id 65-v6csp2043621jap; Thu, 23 Aug 2018 05:15:25 -0700 (PDT) X-Google-Smtp-Source: ANB0Vdb7e0ABnw0NZiNxkGrjgKhQabnRU9Au6jt66oCb5muvHjhtAXwuFWLFm+CXGnnLz/tGKgFC X-Received: by 2002:a1c:64d5:: with SMTP id y204-v6mr4983436wmb.14.1535026525345; Thu, 23 Aug 2018 05:15:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1535026525; cv=none; d=google.com; s=arc-20160816; b=WWYiYQrlo0oNojG2X4Mbx1+UPuGAZJyeQ/WQbRAGOnw3P86OUHgc0ZWLCaiDwpaiiK f2q+ewvzE1F+cNBgMe6NlG8doea0ZT4wi6y2/HdNHV1fFuky8cxIE7wVWUoSvL1mqXxe p5Ypo0yjskMeL7zlGawhpmohhQ1xj/e005nwbXA4371/+J8Y6B/hB3WuCgYLNIv6dlDi TOgKAJ8qhmpIW3V2qH2UyX5fTX7At5O+I8Ebn3BCrhcYczP4mwaEWeC1EfbBHwOKK48J eTJvD9DgKETSCMFIs3JnToUSWcHSaOI0rlXgmf27l0QSSDhl6D/1LfgK9FYHb3MlOHlN EdNQ== 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=Jj50Nhr59Ul5kdGALBlI0XaP4NRrh2fzITLDoJAtm00=; b=REXLBoR7X/Cf73mJQksBPO+LqiQStzo7iP+7G9QQW3ZPHJRq0mj6QMjwPmxerdWojn /DVBIPAg566ZZaz9QBv1gefupDPNmnZKtLRQYeMafCqs2xOTrJJNnLzVi5tReHSWayg2 7hYtWLTZLJBEk15qQ8CS+2szJ9iZceN+L0GVA4bg0vIc8kNBeM55ovncSm12xDnI+JvE F+kU+JADDrQDEdEgZ8RDzzVY5ArUwVuWxc7LPKtO5qvGtz6FLG6s4yllcwXQNzBj9DSO bs4skiWm7hxOj9xCyvUWGjeUCsU6CimtbxU8cfCt+y2tsCY5GlZSitElSWeBzgqgVtU8 m0vQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=u2Qk1Z8b; 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 l13-v6si2472749wrs.164.2018.08.23.05.15.24; Thu, 23 Aug 2018 05:15:25 -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=u2Qk1Z8b; 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 1FB05689E20; Thu, 23 Aug 2018 15:15:22 +0300 (EEST) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wm0-f42.google.com (mail-wm0-f42.google.com [74.125.82.42]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 70A94689ABA for ; Thu, 23 Aug 2018 15:15:16 +0300 (EEST) Received: by mail-wm0-f42.google.com with SMTP id o18-v6so5636530wmc.0 for ; Thu, 23 Aug 2018 05:15:18 -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=Q+TSdxWbXvmvD8zWxJf6OKZJ41nfZgsTm5/oBf/waNY=; b=u2Qk1Z8bWle9Ul2SWnsuuWmN2Csza75okhBe+PTsnqmhxUonbHAh7xgLBzx8PqTfNm EKXZOJGUT0ALDSYRpsTo5YVjDUkybvhPoJfq228eo26v0z4FFtQN2K5GA5FdAKm5zSrf jgNn1380J3u40sGBJjxAEOpImG25yKixB/vVtZRdHY/7vZIT4vA4pfOpWMhA0UOIkz0I lT02nXw8gx/w3JxSsiGljrhlSSgeKny/e4pQoSIuDGAF9zrjzNElLKhlmAOoWM2Rz9L3 c84uUkE08e/Kahn5WgHp8GviBV4gob8yU2HIOnfa20tkZ6PF7Zp6cP1KbLXEpoKkH154 bS0w== 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=Q+TSdxWbXvmvD8zWxJf6OKZJ41nfZgsTm5/oBf/waNY=; b=DoBxwcwY9dF61zu/HiAWJOpGt5U++kdkkKWp9px1rYrJ8T0YJfXu8u/fPaL51fVqG6 KJOkzTiCnGpeRaYgudlbUI19vSGDwn5GNgp7NdWHpxiKavXfSNqSmccDn5LXv8r1mIIc jaGXYdSkQP23vLWUEKfTp7ixSiV1rqohYvev3wCVWjBG+KKxmqk6tPoSnN0P/N18644P +V94/D0ioZpxUvw7CaFyaIHqYOeQCx5k1nejCCa1fXdTNrB9yr/jBBQUgdFHk9Cj/FRM PGdDJdUJl5rhhsPWk5VhBX/Uy3GqhAACVLW2kZaspZxew+1TfDjJc6800fDFONVH2x9r 5DfA== X-Gm-Message-State: APzg51B0P6B+QJVSYF7BE2BJITE8oChK/eZuV09VZFhkZJhAkRWYC7MM mtmA5XS7qbNGhdVF/zcRHYK5OjOz X-Received: by 2002:a1c:e189:: with SMTP id y131-v6mr5061989wmg.44.1535026516965; Thu, 23 Aug 2018 05:15:16 -0700 (PDT) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id o14-v6sm7535442wmd.35.2018.08.23.05.15.15 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 23 Aug 2018 05:15:16 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Thu, 23 Aug 2018 14:15:06 +0200 Message-Id: <20180823121506.7599-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 | 31 +++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_lut3d.c | 438 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 471 insertions(+) diff --git a/doc/filters.texi b/doc/filters.texi index 32c95b591c..37e79d34e1 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -10962,6 +10962,37 @@ 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. +@item cubic +Interpolate values using the cubic 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..6ec0cad650 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,440 @@ 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; + } + } 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 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(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 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(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