From patchwork Sun Oct 31 09:29:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 31262 Delivered-To: ffmpegpatchwork2@gmail.com Received: by 2002:a5e:a610:0:0:0:0:0 with SMTP id q16csp1464096ioi; Sun, 31 Oct 2021 02:28:52 -0700 (PDT) X-Google-Smtp-Source: ABdhPJyyABrJC9FHhMpmRfL6o8OUxgUsnE93EL505KWAEpBnep+xeyTHqP2tzZyYR77awOR+Qiez X-Received: by 2002:a17:907:e8b:: with SMTP id ho11mr8832655ejc.430.1635672532135; Sun, 31 Oct 2021 02:28:52 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1635672532; cv=none; d=google.com; s=arc-20160816; b=LHdDi6ehuN83OA2Soingn5EltLwEm5XlqtNb7LczHyd2BZsR/2BeH5yaYEterQBHQe JnUdawQvXd4bd/ylcGYccNculAfIGZ+mvMAkR7aCiD1C/PhvCOANRHuIh29BcrwvgdQn 4EwIU+XRkfOvPYDiIbBu98VmsmqjOwyxdm6cecX5jae0VeXQpdqslh1PtT6d1NFHtxCF RTa4jDWIIXP7hs5Ml44CHnMRaLRwekF++VCQX3gyUSu6Mv2cZ02BVZXQl89D/zewORk3 QdNZPeu0Yde8EbLXrgPo2t28wekaQNhHxpLnQmpJAL1AaCljAyGPUhMKXpZllOoONr/w vuTg== 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; bh=zv2Sz1XT6uQ4xJWBKKfFrvq/CGDRGLqV2rPk1jewpyw=; b=afnnnyLy6sWngr0HmBGN4+ebw8pqA8QdtBjGprBMnlHsXDzbk3ORi/23d6YgQGHiNm Bpv9N2fFRynNcfg/pwmOj1YEZaPvf2fO1qEVD6kCk3mmLvGrHjkcmekVgWdEghKdYTZ5 e3XkzEGfWQcCqutk+JiUrkQEvN/s3RCWuq+dEuE+utlMzM861LCQEbt7FLusamPmdN2I qmhhiEz4QWd/AOULb8CAZFqEKTjFKXcOQ52RPp+K9Pwlx9ysXKWWflX+JEOhTax726xV NlMJc5Tt40JeVbkA7/gsqNbWezAiXKgbvW6ZY8EhGor5lSKNs/vJUsERdeK/uaS/CkJz U3Tw== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20210112 header.b=mLvBd4k0; 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 ds5si21338836ejc.522.2021.10.31.02.28.51; Sun, 31 Oct 2021 02:28:52 -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=20210112 header.b=mLvBd4k0; 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 76E3968A360; Sun, 31 Oct 2021 11:28:47 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-ed1-f52.google.com (mail-ed1-f52.google.com [209.85.208.52]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id D7B43689F4D for ; Sun, 31 Oct 2021 11:28:40 +0200 (EET) Received: by mail-ed1-f52.google.com with SMTP id z20so54424748edc.13 for ; Sun, 31 Oct 2021 02:28:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112; h=from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=dxzFF7RcrtGmZveCnbS3Np8+DVkO6r7MP9XkBoZq0Oc=; b=mLvBd4k0KA3+J6+ive4HkdA/1bHFWwwrEVAE2PsYLEvFAtp51kZHsaOWljg3ESn3ts b1SXv4kSRhP5ha4j/9/kf0WyYMWJFQUHFPzlxub0EL245gAzqFSfUoE+EXySba0tn8RG 3vKymnBilAXxrFDm5un+YV0VnWPE+fIl1wyrhvmgcBm8P6P10sw/+iDBmz/qHptmHX/f ufjzfPVSusRuBtMO4Em7FZLz5CRVkiXfyf0VHmM95hwmaOQAkqkESJ8wrr6gbZaYSfya cgnLjQvzGHcZA0BaNSL2ILIkDyxI4lvbkJqAHKtAPZUeUaWlrHFnnnPY/mCWWuvEp/Rg Qo6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=dxzFF7RcrtGmZveCnbS3Np8+DVkO6r7MP9XkBoZq0Oc=; b=EQ/FD7qAnL0NSlsPUNpQ3+5e+6QHKSiMwE2qaZ1q1tIYH7BFvF4Ctg9lUbgnT+h0rb n7IPC4Z3mb9UFlxftniHE0AQE26r43knQ9sOB2j8SkYpwgVqUyHXihNdZ0299a1hgF91 ByhEFMc82Mj15m8HGoh2fZoHaSPNcYwHhTiByOJQvRtAY6KNmLYAaSvWVXNucYtKCPFq rMaCFloDMZ8pDreqihUjnbOHn0k7Nt00DdL+wc2tX0gkPORohi5h5rejCSoORh1aC4L5 /Z6zBTM2Sm3GE/uO8kiKfa79YDrb+iq3ZeThIwM266Em8c37aijjb1kJfjpATJwuzXTz yeIA== X-Gm-Message-State: AOAM531sHEAefqYwRa1D1ST1tzTXMyx3GXYz2Wu6vAa+MfNlYxuSW6Zq M6hcCrxa47zkOqmg/eYhCwd6G53Tyfk= X-Received: by 2002:aa7:cac2:: with SMTP id l2mr31271918edt.168.1635672520245; Sun, 31 Oct 2021 02:28:40 -0700 (PDT) Received: from localhost.localdomain ([212.15.177.28]) by smtp.gmail.com with ESMTPSA id m10sm6874264edi.72.2021.10.31.02.28.39 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 31 Oct 2021 02:28:39 -0700 (PDT) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sun, 31 Oct 2021 10:29:02 +0100 Message-Id: <20211031092902.40320-1-onemda@gmail.com> X-Mailer: git-send-email 2.33.0 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH] avfilter: add positive video filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 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" X-TUID: JvuAEwvubaPz Signed-off-by: Paul B Mahol --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_positive.c | 337 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 339 insertions(+) create mode 100644 libavfilter/vf_positive.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 6ac6e3b764..54fbce7909 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -380,6 +380,7 @@ OBJS-$(CONFIG_PHASE_FILTER) += vf_phase.o OBJS-$(CONFIG_PHOTOSENSITIVITY_FILTER) += vf_photosensitivity.o OBJS-$(CONFIG_PIXDESCTEST_FILTER) += vf_pixdesctest.o OBJS-$(CONFIG_PIXSCOPE_FILTER) += vf_datascope.o +OBJS-$(CONFIG_POSITIVE_FILTER) += vf_positive.o OBJS-$(CONFIG_PP_FILTER) += vf_pp.o qp_table.o OBJS-$(CONFIG_PP7_FILTER) += vf_pp7.o qp_table.o OBJS-$(CONFIG_PREMULTIPLY_FILTER) += vf_premultiply.o framesync.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index f5c539199c..c415005ab8 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -364,6 +364,7 @@ extern const AVFilter ff_vf_phase; extern const AVFilter ff_vf_photosensitivity; extern const AVFilter ff_vf_pixdesctest; extern const AVFilter ff_vf_pixscope; +extern const AVFilter ff_vf_positive; extern const AVFilter ff_vf_pp; extern const AVFilter ff_vf_pp7; extern const AVFilter ff_vf_premultiply; diff --git a/libavfilter/vf_positive.c b/libavfilter/vf_positive.c new file mode 100644 index 0000000000..4557aae349 --- /dev/null +++ b/libavfilter/vf_positive.c @@ -0,0 +1,337 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#include "libavutil/attributes.h" +#include "libavutil/common.h" +#include "libavutil/ffmath.h" +#include "libavutil/imgutils.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "drawutils.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +#define AA_DMIN (1 << 0) +#define AA_DMAX (1 << 1) +#define AA_OFFSET (1 << 2) +#define AA_EXPOSURE (1 << 3) +#define AA_BLACK (1 << 4) +#define AA_WBH (1 << 5) +#define AA_WBL (1 << 6) + +typedef struct ThreadData { + AVFrame *in; + AVFrame *out; +} ThreadData; + +typedef struct PositiveContext { + const AVClass *class; + + float sampler[4]; + float picker_avg[3]; + float picker_min[3]; + float picker_max[3]; + int autoadjust; + + float dmin[3]; + float dmax; + float owbh[3]; + float owbl[3]; + float ooffset; + float oblack; + float wbh[3]; + float offset[3]; + float black; + float gamma; + float softclip; + float exposure; + + int max; + int nb_planes; +} PositiveContext; + +#define OFFSET(x) offsetof(PositiveContext, x) +#define VF AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +static const AVOption positive_options[] = { + { "dminr", "set the red component of film color substrate", OFFSET(dmin[2]), AV_OPT_TYPE_FLOAT, {.dbl=1.13}, 0, 1.5, VF }, + { "dming", "set the green component of film color substrate", OFFSET(dmin[0]), AV_OPT_TYPE_FLOAT, {.dbl=0.49}, 0, 1.5, VF }, + { "dminb", "set the blue component of film color substrate", OFFSET(dmin[1]), AV_OPT_TYPE_FLOAT, {.dbl=0.27}, 0, 1.5, VF }, + { "wbhr", "set the red component highlights offset for whites", OFFSET(owbh[2]), AV_OPT_TYPE_FLOAT, {.dbl=1.00},.25, 2.0, VF }, + { "wbhg", "set the green component highlights offset for whites", OFFSET(owbh[0]), AV_OPT_TYPE_FLOAT, {.dbl=1.00},.25, 2.0, VF }, + { "wbhb", "set the blue component higlights offset for whites", OFFSET(owbh[1]), AV_OPT_TYPE_FLOAT, {.dbl=1.00},.25, 2.0, VF }, + { "wblr", "set the red component shadows offset for blacks", OFFSET(owbl[2]), AV_OPT_TYPE_FLOAT, {.dbl=1.00},.25, 2.0, VF }, + { "wblg", "set the green component shadows offset for blacks", OFFSET(owbl[0]), AV_OPT_TYPE_FLOAT, {.dbl=1.00},.25, 2.0, VF }, + { "wblb", "set the blue component shadows offset for blacks", OFFSET(owbl[1]), AV_OPT_TYPE_FLOAT, {.dbl=1.00},.25, 2.0, VF }, + { "dmax", "set the max film density", OFFSET(dmax), AV_OPT_TYPE_FLOAT, {.dbl=1.06}, .1, 6.0, VF }, + { "offset", "set the inversion offset", OFFSET(ooffset), AV_OPT_TYPE_FLOAT, {.dbl=-.05},-1., 1.0, VF }, + { "black", "set the display black", OFFSET(oblack), AV_OPT_TYPE_FLOAT, {.dbl=-.07},-.5, .5, VF }, + { "gamma", "set the display gamma", OFFSET(gamma), AV_OPT_TYPE_FLOAT, {.dbl=4.00}, 1., 8.0, VF }, + { "softclip", "set the highlights roll-off", OFFSET(softclip), AV_OPT_TYPE_FLOAT, {.dbl= .75},.0001,1., VF }, + { "exposure", "set the extra exposure", OFFSET(exposure), AV_OPT_TYPE_FLOAT, {.dbl=0.92}, .5, 2.0, VF }, + { "samplex", "set the X-axis block start to pick color", OFFSET(sampler[0]),AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, VF }, + { "sampley", "set the Y-axis block start to pick color", OFFSET(sampler[1]),AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, VF }, + { "sampleX", "set the X-axis block end to pick color", OFFSET(sampler[2]),AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, VF }, + { "sampleY", "set the Y-axis block end to pick color", OFFSET(sampler[3]),AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, VF }, + { "autoadjust","set the auto adjust of options from sampler", OFFSET(autoadjust),AV_OPT_TYPE_FLAGS, {.i64=0}, 0, 0xFF,VF, "auto" }, + { "dmin", "set auto-adjust for dmin", 0, AV_OPT_TYPE_CONST, {.i64=AA_DMIN}, 0, 0,VF, "auto" }, + { "dmax", "set auto-adjust for dmax", 0, AV_OPT_TYPE_CONST, {.i64=AA_DMAX}, 0, 0,VF, "auto" }, + { "offset", "set auto-adjust for offset", 0, AV_OPT_TYPE_CONST, {.i64=AA_OFFSET}, 0, 0,VF, "auto" }, + { "exposure","set auto-adjust for exposure", 0, AV_OPT_TYPE_CONST, {.i64=AA_EXPOSURE},0,0,VF, "auto" }, + { "black", "set auto-adjust for black", 0, AV_OPT_TYPE_CONST, {.i64=AA_BLACK}, 0, 0,VF, "auto" }, + { "wbh", "set auto-adjust for white-balance highlights", 0, AV_OPT_TYPE_CONST, {.i64=AA_WBH}, 0, 0,VF, "auto" }, + { "wbl", "set auto-adjust for white-balance shadows", 0, AV_OPT_TYPE_CONST, {.i64=AA_WBL}, 0, 0,VF, "auto" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(positive); + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + PositiveContext *s = ctx->priv; + + for (int c = 0; c < 3; c++) + s->wbh[c] = s->owbh[c] / s->dmax; + for (int c = 0; c < 3; c++) + s->offset[c] = s->owbh[c] * s->owbl[c] * s->ooffset; + + s->black = -s->exposure * (1.f + s->oblack); + + return 0; +} + +#define THRESHOLD 2.3283064365386963e-10f // -32 EV + +static int filter_slice(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + PositiveContext *s = ctx->priv; + const float exposure = s->exposure; + const float black = s->black; + const float gamma = s->gamma; + const float softclip = s->softclip; + const float softclipcomp = 1.f - softclip; + const float isoftclipcomp = 1.f / softclipcomp; + ThreadData *td = arg; + AVFrame *in = td->in; + AVFrame *out = td->out; + + for (int p = 0; p < 3; p++) { + const int w = in->width; + const int h = in->height; + const int slice_start = (h * jobnr) / nb_jobs; + const int slice_end = (h * (jobnr+1)) / nb_jobs; + const float Dmin = s->dmin[p]; + const float wb_high = s->wbh[p]; + const float offset = s->offset[p]; + + for (int y = slice_start; y < slice_end; y++) { + float *const restrict dst = (float *)(out->data[p] + y * out->linesize[p]); + const float *const restrict src = (const float *const )(in->data[p] + y * in->linesize[p]); + + for (int x = 0; x < w; x++) { + const float density = -log10f(Dmin / fmaxf(src[x], THRESHOLD)); + const float corrected_de = wb_high * density + offset; + const float print_linear = -(exposure * ff_exp10f(corrected_de) + black); + const float print_gamma = powf(fmaxf(print_linear, 0.f), gamma); + dst[x] = (print_gamma > softclip) ? softclip + (1.f - expf(-(print_gamma - softclip) * isoftclipcomp)) * softclipcomp : print_gamma; + } + } + } + + return 0; +} + +static void adjust_dmin(PositiveContext *s) +{ + memcpy(s->dmin, s->picker_max, sizeof(s->dmin)); +} + +static void adjust_dmax(PositiveContext *s) +{ + float rgb[3]; + + for (int c = 0; c < 3; c++) + rgb[c] = log10f(s->dmin[c] / fmaxf(s->picker_min[c], THRESHOLD)); + s->dmax = FFMAX3(rgb[0], rgb[1], rgb[2]); + s->dmax = av_clipf(s->dmax, 0.1f, 6.f); +} + +static void adjust_offset(PositiveContext *s) +{ + float rgb[3]; + + for (int c = 0; c < 3; c++) + rgb[c] = log10f(s->dmin[c] / fmaxf(s->picker_max[c], THRESHOLD)) / s->dmax; + s->ooffset = FFMIN3(rgb[0], rgb[1], rgb[2]); + s->ooffset = av_clipf(s->ooffset, -1.f, 1.f); +} + +static void adjust_exposure(PositiveContext *s) +{ + float rgb[3]; + + for (int c = 0; c < 3; c++) { + rgb[c] = -log10f(s->dmin[c] / fmaxf(s->picker_min[c], THRESHOLD)); + rgb[c] *= s->owbh[c] / s->dmax; + rgb[c] += s->owbl[c] * s->ooffset; + rgb[c] = 0.96f / (1.0f - ff_exp10f(rgb[c]) + s->oblack); + } + + s->exposure = FFMIN3(rgb[0], rgb[1], rgb[2]); + s->exposure = av_clipf(s->exposure, 0.5f, 2.f); +} + +static void adjust_black(PositiveContext *s) +{ + float rgb[3]; + + for (int c = 0; c < 3; c++) { + rgb[c] = -log10f(s->dmin[c] / fmaxf(s->picker_max[c], THRESHOLD)); + rgb[c] *= s->owbh[c] / s->dmax; + rgb[c] += s->owbl[c] * s->ooffset * s->owbh[c]; + rgb[c] = 0.1f - (1.0f - ff_exp10f(rgb[c])); + } + + s->oblack = FFMAX3(rgb[0], rgb[1], rgb[2]); + s->oblack = av_clipf(s->oblack, -.5f, .5f); +} + +static void adjust_wbh(PositiveContext *s) +{ + float rgb_min[3], min; + + for (int c = 0; c < 3; c++) + rgb_min[c] = fabsf(-1.f / (s->ooffset * s->owbl[c] - log10f(s->dmin[c] / fmaxf(s->picker_avg[c], THRESHOLD)) / s->dmax)); + + min = FFMIN3(rgb_min[0], rgb_min[1], rgb_min[2]); + for (int c = 0; c < 3; c++) { + s->owbh[c] = rgb_min[c] / min; + s->owbh[c] = av_clipf(s->owbh[c], 0.25f, 2.f); + } +} + +static void adjust_wbl(PositiveContext *s) +{ + float rgb_min[3], min; + + for (int c = 0; c < 3; c++) + rgb_min[c] = log10f(s->dmin[c] / fmaxf(s->picker_avg[c], THRESHOLD)) / s->dmax; + + min = FFMIN3(rgb_min[0], rgb_min[1], rgb_min[2]); + for (int c = 0; c < 3; c++) { + s->owbl[c] = min / fmaxf(rgb_min[c], THRESHOLD); + s->owbl[c] = av_clipf(s->owbl[c], 0.25f, 2.f); + } +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + PositiveContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + ThreadData td; + AVFrame *out; + + 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 AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + } + + if (s->sampler[0] < s->sampler[2] && + s->sampler[1] < s->sampler[3]) { + const int x = lrintf(s->sampler[0] * (in->width - 1)); + const int y = lrintf(s->sampler[1] * (in->height - 1)); + const int X = lrintf(s->sampler[2] * (in->width - 1)); + const int Y = lrintf(s->sampler[3] * (in->height - 1)); + + for (int p = 0; p < 3; p++) { + s->picker_min[p] = FLT_MAX; + s->picker_max[p] = -FLT_MAX; + s->picker_avg[p] = 0.f; + + for (int j = y; j < Y; j++) { + const float *src = (const float *)(in->data[p] + j * in->linesize[p]); + for (int i = x; i < X; i++) { + s->picker_avg[p] += src[i]; + s->picker_min[p] = fminf(src[i], s->picker_min[p]); + s->picker_max[p] = fmaxf(src[i], s->picker_max[p]); + } + } + + s->picker_avg[p] /= (X - x) * (Y - y); + } + } + + if (s->autoadjust & AA_DMIN) adjust_dmin(s); + if (s->autoadjust & AA_DMAX) adjust_dmax(s); + if (s->autoadjust & AA_OFFSET) adjust_offset(s); + if (s->autoadjust & AA_EXPOSURE) adjust_exposure(s); + if (s->autoadjust & AA_BLACK) adjust_black(s); + if (s->autoadjust & AA_WBH) adjust_wbh(s); + if (s->autoadjust & AA_WBL) adjust_wbl(s); + + config_input(inlink); + + td.out = out; + td.in = in; + ff_filter_execute(ctx, filter_slice, &td, NULL, + FFMIN(in->height, ff_filter_get_nb_threads(ctx))); + if (out != in) + av_frame_free(&in); + + return ff_filter_frame(outlink, out); +} + +static const AVFilterPad inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + .config_props = config_input, + }, +}; + +static const AVFilterPad outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, +}; + +const AVFilter ff_vf_positive = { + .name = "positive", + .description = NULL_IF_CONFIG_SMALL("Invert scanned film negatives."), + .priv_size = sizeof(PositiveContext), + .priv_class = &positive_class, + FILTER_INPUTS(inputs), + FILTER_OUTPUTS(outputs), + FILTER_PIXFMTS(AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32), + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, + .process_command = ff_filter_process_command, +};