From 49a9cada08724df87186a598c0e96df61d47a76e Mon Sep 17 00:00:00 2001 From: har0ke Date: Wed, 3 Jul 2024 19:41:52 -0500 Subject: [PATCH] Add divecorrector filter (an extended version of the channelmixer) --- .gitignore | 1 + compile.sh | 76 ++++ libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/divecorrector_template.c | 252 +++++++++++++ libavfilter/vf_divecorrector.c | 520 +++++++++++++++++++++++++++ 6 files changed, 851 insertions(+) create mode 100644 compile.sh create mode 100644 libavfilter/divecorrector_template.c create mode 100644 libavfilter/vf_divecorrector.c diff --git a/.gitignore b/.gitignore index e810d11107..0bdc6de8b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +bin *.a *.o *.o.* diff --git a/compile.sh b/compile.sh new file mode 100644 index 0000000000..0b123e3c1c --- /dev/null +++ b/compile.sh @@ -0,0 +1,76 @@ + +prefix="$(realpath "$(dirname "$0")")"/bin + +echo "$prefix" + +./configure \ + --prefix=/usr \ + --disable-debug \ + --disable-static \ + --disable-stripping \ + --enable-amf \ + --enable-lto \ + --enable-fontconfig \ + --enable-frei0r \ + --enable-gmp \ + --enable-gpl \ + --enable-ladspa \ + --enable-libaom \ + --enable-libass \ + --enable-libbluray \ + --enable-libbs2b \ + --enable-libdav1d \ + --enable-libdrm \ + --enable-libdvdnav \ + --enable-libdvdread \ + --enable-libfreetype \ + --enable-libfribidi \ + --enable-libgsm \ + --enable-libharfbuzz \ + --enable-libiec61883 \ + --enable-libjack \ + --enable-libjxl \ + --enable-libmodplug \ + --enable-libmp3lame \ + --enable-libopencore_amrnb \ + --enable-libopencore_amrwb \ + --enable-libopenjpeg \ + --enable-libopenmpt \ + --enable-libopus \ + --enable-libplacebo \ + --enable-libpulse \ + --enable-librav1e \ + --enable-librsvg \ + --enable-librubberband \ + --enable-libsnappy \ + --enable-libsoxr \ + --enable-libspeex \ + --enable-libsrt \ + --enable-libssh \ + --enable-libsvtav1 \ + --enable-libtheora \ + --enable-libv4l2 \ + --enable-libvidstab \ + --enable-libvmaf \ + --enable-libvorbis \ + --enable-libvpl \ + --enable-libvpx \ + --enable-libwebp \ + --enable-libx264 \ + --enable-libx265 \ + --enable-libxcb \ + --enable-libxml2 \ + --enable-libxvid \ + --enable-libzimg \ + --enable-nvdec \ + --enable-nvenc \ + --enable-opencl \ + --enable-opengl \ + --enable-shared \ + --enable-vapoursynth \ + --enable-version3 \ + --enable-vulkan \ + --prefix="$prefix" + +make -j10 + diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 994d9773ba..7fbfdefe69 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -231,6 +231,7 @@ OBJS-$(CONFIG_CIESCOPE_FILTER) += vf_ciescope.o OBJS-$(CONFIG_CODECVIEW_FILTER) += vf_codecview.o qp_table.o OBJS-$(CONFIG_COLORBALANCE_FILTER) += vf_colorbalance.o OBJS-$(CONFIG_COLORCHANNELMIXER_FILTER) += vf_colorchannelmixer.o +OBJS-$(CONFIG_DIVECORRECTOR_FILTER) += vf_divecorrector.o OBJS-$(CONFIG_COLORCONTRAST_FILTER) += vf_colorcontrast.o OBJS-$(CONFIG_COLORCORRECT_FILTER) += vf_colorcorrect.o OBJS-$(CONFIG_COLORIZE_FILTER) += vf_colorize.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 149bf50997..ca44de7878 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -214,6 +214,7 @@ extern const AVFilter ff_vf_ciescope; extern const AVFilter ff_vf_codecview; extern const AVFilter ff_vf_colorbalance; extern const AVFilter ff_vf_colorchannelmixer; +extern const AVFilter ff_vf_divecorrector; extern const AVFilter ff_vf_colorcontrast; extern const AVFilter ff_vf_colorcorrect; extern const AVFilter ff_vf_colorize; diff --git a/libavfilter/divecorrector_template.c b/libavfilter/divecorrector_template.c new file mode 100644 index 0000000000..c6f6e06bbc --- /dev/null +++ b/libavfilter/divecorrector_template.c @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2013 Paul B Mahol + * + * 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 + +#undef pixel +#undef cpixel +#undef ROUND +#if DEPTH == 8 +#define pixel uint8_t +#define cpixel int +#define ROUND lrintf +#elif DEPTH == 16 +#define pixel uint16_t +#define cpixel int +#define ROUND lrintf +#else +#define NOP(x) (x) +#define pixel float +#define cpixel float +#define ROUND NOP +#endif + +#undef fn +#undef fn2 +#undef fn3 +#define fn3(a,b) a##_##b +#define fn2(a,b) fn3(a,b) +#define fn(a) fn2(a, DEPTH) + +static av_always_inline int fn(filter_slice_rgba_planar)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs, + int have_alpha, int depth, int pc) +{ + DiveCorrectorContext *s = ctx->priv; + ThreadData *td = arg; + AVFrame *in = td->in; + AVFrame *out = td->out; + const float pa = s->preserve_amount; + const float max = (1 << depth) - 1; + const int slice_start = (out->height * jobnr) / nb_jobs; + const int slice_end = (out->height * (jobnr+1)) / nb_jobs; + const pixel *srcg = (const pixel *)(in->data[0] + slice_start * in->linesize[0]); + const pixel *srcb = (const pixel *)(in->data[1] + slice_start * in->linesize[1]); + const pixel *srcr = (const pixel *)(in->data[2] + slice_start * in->linesize[2]); + const pixel *srca = (const pixel *)(in->data[3] + slice_start * in->linesize[3]); + pixel *dstg = (pixel *)(out->data[0] + slice_start * out->linesize[0]); + pixel *dstb = (pixel *)(out->data[1] + slice_start * out->linesize[1]); + pixel *dstr = (pixel *)(out->data[2] + slice_start * out->linesize[2]); + pixel *dsta = (pixel *)(out->data[3] + slice_start * out->linesize[3]); + + for (int i = slice_start; i < slice_end; i++) { + for (int j = 0; j < out->width; j++) { + const pixel rin = srcr[j]; + const pixel gin = srcg[j]; + const pixel bin = srcb[j]; + const pixel ain = have_alpha ? srca[j] : 0; + cpixel rout, gout, bout; + +#if DEPTH == 32 + rout = s->rr * rin + + s->rg * gin + + s->rb * bin + + s->ro + + (have_alpha == 1 ? s->ra * ain : 0); + gout = s->gr * rin + + s->gg * gin + + s->gb * bin + + s->go + + (have_alpha == 1 ? s->ga * ain : 0); + bout = s->br * rin + + s->bg * gin + + s->bb * bin + + s->bo + + (have_alpha == 1 ? s->ba * ain : 0); +#else + rout = s->lut[R][R][rin] + + s->lut[R][G][gin] + + s->lut[R][B][bin] + + s->offsets[R] + + (have_alpha == 1 ? s->lut[R][A][ain] : 0); + gout = s->lut[G][R][rin] + + s->lut[G][G][gin] + + s->lut[G][B][bin] + + s->offsets[G] + + (have_alpha == 1 ? s->lut[G][A][ain] : 0); + bout = s->lut[B][R][rin] + + s->lut[B][G][gin] + + s->lut[B][B][bin] + + s->offsets[B] + + (have_alpha == 1 ? s->lut[B][A][ain] : 0); +#endif + + if (pc) { + float frout, fgout, fbout, lin, lout; + +#if DEPTH < 32 + frout = av_clipf(rout, 0.f, max); + fgout = av_clipf(gout, 0.f, max); + fbout = av_clipf(bout, 0.f, max); +#else + frout = rout; + fgout = gout; + fbout = bout; +#endif + + preserve_color(s->preserve_color, rin, gin, bin, + rout, gout, bout, max, &lin, &lout); + preservel(&frout, &fgout, &fbout, lin, lout, max); + + rout = ROUND(lerpf(rout, frout, pa)); + gout = ROUND(lerpf(gout, fgout, pa)); + bout = ROUND(lerpf(bout, fbout, pa)); + } + +#if DEPTH < 32 + dstr[j] = av_clip_uintp2(rout, depth); + dstg[j] = av_clip_uintp2(gout, depth); + dstb[j] = av_clip_uintp2(bout, depth); +#else + dstr[j] = rout; + dstg[j] = gout; + dstb[j] = bout; +#endif + + if (have_alpha == 1) { +#if DEPTH < 32 + dsta[j] = av_clip_uintp2(s->lut[A][R][rin] + + s->lut[A][G][gin] + + s->lut[A][B][bin] + + s->lut[A][A][ain], depth); +#else + dsta[j] = s->ar * rin + + s->ag * gin + + s->ab * bin + + s->aa * ain; +#endif + } + } + + srcg += in->linesize[0] / sizeof(pixel); + srcb += in->linesize[1] / sizeof(pixel); + srcr += in->linesize[2] / sizeof(pixel); + srca += in->linesize[3] / sizeof(pixel); + dstg += out->linesize[0] / sizeof(pixel); + dstb += out->linesize[1] / sizeof(pixel); + dstr += out->linesize[2] / sizeof(pixel); + dsta += out->linesize[3] / sizeof(pixel); + } + + return 0; +} + +#if DEPTH < 32 + +static av_always_inline int fn(filter_slice_rgba_packed)(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs, + int have_alpha, int step, int pc, int depth) +{ + DiveCorrectorContext *s = ctx->priv; + ThreadData *td = arg; + AVFrame *in = td->in; + AVFrame *out = td->out; + const float pa = s->preserve_amount; + const float max = (1 << depth) - 1; + const int slice_start = (out->height * jobnr) / nb_jobs; + const int slice_end = (out->height * (jobnr+1)) / nb_jobs; + const uint8_t roffset = s->rgba_map[R]; + const uint8_t goffset = s->rgba_map[G]; + const uint8_t boffset = s->rgba_map[B]; + const uint8_t aoffset = s->rgba_map[A]; + const uint8_t *srcrow = in->data[0] + slice_start * in->linesize[0]; + uint8_t *dstrow = out->data[0] + slice_start * out->linesize[0]; + int i, j; + + for (i = slice_start; i < slice_end; i++) { + const pixel *src = (const pixel *)srcrow; + pixel *dst = (pixel *)dstrow; + + for (j = 0; j < out->width * step; j += step) { + const pixel rin = src[j + roffset]; + const pixel gin = src[j + goffset]; + const pixel bin = src[j + boffset]; + const pixel ain = src[j + aoffset]; + int rout, gout, bout; + + rout = s->lut[R][R][rin] + + s->lut[R][G][gin] + + s->lut[R][B][bin] + + s->offsets[R] + + (have_alpha == 1 ? s->lut[R][A][ain] : 0); + gout = s->lut[G][R][rin] + + s->lut[G][G][gin] + + s->lut[G][B][bin] + + s->offsets[G] + + (have_alpha == 1 ? s->lut[G][A][ain] : 0); + bout = s->lut[B][R][rin] + + s->lut[B][G][gin] + + s->lut[B][B][bin] + + s->offsets[B] + + (have_alpha == 1 ? s->lut[B][A][ain] : 0); + + if (pc) { + float frout = av_clipf(rout, 0.f, max); + float fgout = av_clipf(gout, 0.f, max); + float fbout = av_clipf(bout, 0.f, max); + float lin, lout; + + preserve_color(s->preserve_color, rin, gin, bin, + rout, gout, bout, max, &lin, &lout); + preservel(&frout, &fgout, &fbout, lin, lout, max); + + rout = lrintf(lerpf(rout, frout, pa)); + gout = lrintf(lerpf(gout, fgout, pa)); + bout = lrintf(lerpf(bout, fbout, pa)); + } + + dst[j + roffset] = av_clip_uintp2(rout, depth); + dst[j + goffset] = av_clip_uintp2(gout, depth); + dst[j + boffset] = av_clip_uintp2(bout, depth); + + if (have_alpha == 1) { + dst[j + aoffset] = av_clip_uintp2(s->lut[A][R][rin] + + s->lut[A][G][gin] + + s->lut[A][B][bin] + + s->lut[A][A][ain], depth); + } + } + + srcrow += in->linesize[0]; + dstrow += out->linesize[0]; + } + + return 0; +} + +#endif diff --git a/libavfilter/vf_divecorrector.c b/libavfilter/vf_divecorrector.c new file mode 100644 index 0000000000..9b3680ccbc --- /dev/null +++ b/libavfilter/vf_divecorrector.c @@ -0,0 +1,520 @@ +/* + * Copyright (c) 2013 Paul B Mahol + * + * 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/mem.h" +#include "libavutil/opt.h" +#include "libavutil/pixdesc.h" +#include "avfilter.h" +#include "drawutils.h" +#include "internal.h" +#include "video.h" +#include "preserve_color.h" + +#define R 0 +#define G 1 +#define B 2 +#define A 3 + +typedef struct ThreadData { + AVFrame *in, *out; +} ThreadData; + +typedef struct DiveCorrectorContext { + const AVClass *class; + double rr, rg, rb, ra, ro; + double gr, gg, gb, ga, go; + double br, bg, bb, ba, bo; + double ar, ag, ab, aa, ao; + double mix; + double preserve_amount; + int preserve_color; + + int *lut[4][4]; + int offsets[4]; + + int *buffer; + + uint8_t rgba_map[4]; + + int (*filter_slice[2])(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs); +} DiveCorrectorContext; + +static float lerpf(float v0, float v1, float f) +{ + return v0 + (v1 - v0) * f; +} + +static void preservel(float *r, float *g, float *b, float lin, float lout, float max) +{ + if (lout <= 0.f) + lout = 1.f / (max * 2.f); + *r *= lin / lout; + *g *= lin / lout; + *b *= lin / lout; +} + +#define DEPTH 8 +#include "divecorrector_template.c" + +#undef DEPTH +#define DEPTH 16 +#include "divecorrector_template.c" + +#undef DEPTH +#define DEPTH 32 +#include "divecorrector_template.c" + +#define OFFSET(x) offsetof(DiveCorrectorContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +static const AVOption divecorrector_options[] = { + { "rr", "set the red gain for the red channel", OFFSET(rr), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -10, 10, FLAGS }, + { "rg", "set the green gain for the red channel", OFFSET(rg), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "rb", "set the blue gain for the red channel", OFFSET(rb), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "ra", "set the alpha gain for the red channel", OFFSET(ra), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "ro", "set offset for the red channel", OFFSET(ro), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "gr", "set the red gain for the green channel", OFFSET(gr), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "gg", "set the green gain for the green channel", OFFSET(gg), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -10, 10, FLAGS }, + { "gb", "set the blue gain for the green channel", OFFSET(gb), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "ga", "set the alpha gain for the green channel", OFFSET(ga), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "go", "set offset for the green channel", OFFSET(go), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "br", "set the red gain for the blue channel", OFFSET(br), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "bg", "set the green gain for the blue channel", OFFSET(bg), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "bb", "set the blue gain for the blue channel", OFFSET(bb), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -10, 10, FLAGS }, + { "ba", "set the alpha gain for the blue channel", OFFSET(ba), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "bo", "set offset for the blue channel", OFFSET(bo), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "ar", "set the red gain for the alpha channel", OFFSET(ar), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "ag", "set the green gain for the alpha channel", OFFSET(ag), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "ab", "set the blue gain for the alpha channel", OFFSET(ab), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "aa", "set the alpha gain for the alpha channel", OFFSET(aa), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -10, 10, FLAGS }, + { "ao", "set offset for the alpha channel", OFFSET(ao), AV_OPT_TYPE_DOUBLE, {.dbl=0}, -10, 10, FLAGS }, + { "mix", "mix effect with original", OFFSET(mix), AV_OPT_TYPE_DOUBLE, {.dbl=1}, -5, 5, FLAGS }, + + { "pc", "set the preserve color mode", OFFSET(preserve_color), AV_OPT_TYPE_INT, {.i64=0}, 0, NB_PRESERVE-1, FLAGS, .unit = "preserve" }, + { "none", "disabled", 0, AV_OPT_TYPE_CONST, {.i64=P_NONE}, 0, 0, FLAGS, .unit = "preserve" }, + { "lum", "luminance", 0, AV_OPT_TYPE_CONST, {.i64=P_LUM}, 0, 0, FLAGS, .unit = "preserve" }, + { "max", "max", 0, AV_OPT_TYPE_CONST, {.i64=P_MAX}, 0, 0, FLAGS, .unit = "preserve" }, + { "avg", "average", 0, AV_OPT_TYPE_CONST, {.i64=P_AVG}, 0, 0, FLAGS, .unit = "preserve" }, + { "sum", "sum", 0, AV_OPT_TYPE_CONST, {.i64=P_SUM}, 0, 0, FLAGS, .unit = "preserve" }, + { "nrm", "norm", 0, AV_OPT_TYPE_CONST, {.i64=P_NRM}, 0, 0, FLAGS, .unit = "preserve" }, + { "pwr", "power", 0, AV_OPT_TYPE_CONST, {.i64=P_PWR}, 0, 0, FLAGS, .unit = "preserve" }, + { "pa", "set the preserve color amount", OFFSET(preserve_amount), AV_OPT_TYPE_DOUBLE, {.dbl=0}, 0, 1, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(divecorrector); + +static const enum AVPixelFormat pix_fmts[] = { + AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, + AV_PIX_FMT_RGBA, AV_PIX_FMT_BGRA, + AV_PIX_FMT_ARGB, AV_PIX_FMT_ABGR, + AV_PIX_FMT_0RGB, AV_PIX_FMT_0BGR, + AV_PIX_FMT_RGB0, AV_PIX_FMT_BGR0, + AV_PIX_FMT_RGB48, AV_PIX_FMT_BGR48, + AV_PIX_FMT_RGBA64, AV_PIX_FMT_BGRA64, + AV_PIX_FMT_GBRP, AV_PIX_FMT_GBRAP, + AV_PIX_FMT_GBRP9, + AV_PIX_FMT_GBRP10, AV_PIX_FMT_GBRAP10, + AV_PIX_FMT_GBRP12, AV_PIX_FMT_GBRAP12, + AV_PIX_FMT_GBRP14, + AV_PIX_FMT_GBRP16, AV_PIX_FMT_GBRAP16, + AV_PIX_FMT_GBRPF32, AV_PIX_FMT_GBRAPF32, + AV_PIX_FMT_NONE +}; + +static int filter_slice_gbrp(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_8(ctx, arg, jobnr, nb_jobs, 0, 8, 0); +} + +static int filter_slice_gbrap(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_8(ctx, arg, jobnr, nb_jobs, 1, 8, 0); +} + +static int filter_slice_gbrp_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_8(ctx, arg, jobnr, nb_jobs, 0, 8, 1); +} + +static int filter_slice_gbrap_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_8(ctx, arg, jobnr, nb_jobs, 1, 8, 1); +} + +static int filter_slice_gbrp9(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 9, 0); +} + +static int filter_slice_gbrp10(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 10, 0); +} + +static int filter_slice_gbrap10(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 1, 10, 0); +} + +static int filter_slice_gbrp12(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 12, 0); +} + +static int filter_slice_gbrap12(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 1, 12, 0); +} + +static int filter_slice_gbrp14(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 14, 0); +} + +static int filter_slice_gbrp16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 16, 0); +} + +static int filter_slice_gbrap16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 1, 16, 0); +} + +static int filter_slice_gbrp9_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 9, 1); +} + +static int filter_slice_gbrp10_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 10, 1); +} + +static int filter_slice_gbrap10_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 1, 10, 1); +} + +static int filter_slice_gbrp12_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 12, 1); +} + +static int filter_slice_gbrap12_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 1, 12, 1); +} + +static int filter_slice_gbrp14_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 14, 1); +} + +static int filter_slice_gbrp16_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 0, 16, 1); +} + +static int filter_slice_gbrap16_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_16(ctx, arg, jobnr, nb_jobs, 1, 16, 1); +} + +static int filter_slice_rgba64(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_16(ctx, arg, jobnr, nb_jobs, 1, 4, 0, 16); +} + +static int filter_slice_rgb48(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_16(ctx, arg, jobnr, nb_jobs, 0, 3, 0, 16); +} + +static int filter_slice_rgba64_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_16(ctx, arg, jobnr, nb_jobs, 1, 4, 1, 16); +} + +static int filter_slice_rgb48_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_16(ctx, arg, jobnr, nb_jobs, 0, 3, 1, 16); +} + +static int filter_slice_rgba(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_8(ctx, arg, jobnr, nb_jobs, 1, 4, 0, 8); +} + +static int filter_slice_rgb24(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_8(ctx, arg, jobnr, nb_jobs, 0, 3, 0, 8); +} + +static int filter_slice_rgb0(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_8(ctx, arg, jobnr, nb_jobs, -1, 4, 0, 8); +} + +static int filter_slice_rgba_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_8(ctx, arg, jobnr, nb_jobs, 1, 4, 1, 8); +} + +static int filter_slice_rgb24_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_8(ctx, arg, jobnr, nb_jobs, 0, 3, 1, 8); +} + +static int filter_slice_rgb0_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_packed_8(ctx, arg, jobnr, nb_jobs, -1, 4, 1, 8); +} + +static int filter_slice_gbrp32(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_32(ctx, arg, jobnr, nb_jobs, 0, 1, 0); +} + +static int filter_slice_gbrap32(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_32(ctx, arg, jobnr, nb_jobs, 1, 1, 0); +} + +static int filter_slice_gbrp32_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_32(ctx, arg, jobnr, nb_jobs, 0, 1, 1); +} + +static int filter_slice_gbrap32_pl(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + return filter_slice_rgba_planar_32(ctx, arg, jobnr, nb_jobs, 1, 1, 1); +} + +static int config_output(AVFilterLink *outlink) +{ + AVFilterContext *ctx = outlink->src; + DiveCorrectorContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(outlink->format); + const int depth = desc->comp[0].depth; + int i, j, size, *buffer = s->buffer; + + ff_fill_rgba_map(s->rgba_map, outlink->format); + + size = 1 << depth; + if (!s->buffer) { + s->buffer = buffer = av_malloc(16 * size * sizeof(*s->buffer)); + if (!s->buffer) + return AVERROR(ENOMEM); + + for (i = 0; i < 4; i++) + for (j = 0; j < 4; j++, buffer += size) + s->lut[i][j] = buffer; + } + + s->offsets[R] = lrint(size * s->ro * s->mix); + s->offsets[G] = lrint(size * s->go * s->mix); + s->offsets[B] = lrint(size * s->bo * s->mix); + s->offsets[A] = lrint(size * s->ao * s->mix); + + for (i = 0; i < size; i++) { + s->lut[R][R][i] = lrint(i * ((s->rr - 1.) * s->mix + 1.)); + s->lut[R][G][i] = lrint(i * s->rg * s->mix); + s->lut[R][B][i] = lrint(i * s->rb * s->mix); + s->lut[R][A][i] = lrint(i * s->ra * s->mix); + + s->lut[G][R][i] = lrint(i * s->gr * s->mix); + s->lut[G][G][i] = lrint(i * ((s->gg - 1.) * s->mix + 1.)); + s->lut[G][B][i] = lrint(i * s->gb * s->mix); + s->lut[G][A][i] = lrint(i * s->ga * s->mix); + + s->lut[B][R][i] = lrint(i * s->br * s->mix); + s->lut[B][G][i] = lrint(i * s->bg * s->mix); + s->lut[B][B][i] = lrint(i * ((s->bb - 1.) * s->mix + 1.)); + s->lut[B][A][i] = lrint(i * s->ba * s->mix); + + s->lut[A][R][i] = lrint(i * s->ar * s->mix); + s->lut[A][G][i] = lrint(i * s->ag * s->mix); + s->lut[A][B][i] = lrint(i * s->ab * s->mix); + s->lut[A][A][i] = lrint(i * ((s->aa - 1.) * s->mix + 1.)); + } + + switch (outlink->format) { + case AV_PIX_FMT_BGR24: + case AV_PIX_FMT_RGB24: + s->filter_slice[0] = filter_slice_rgb24; + s->filter_slice[1] = filter_slice_rgb24_pl; + break; + case AV_PIX_FMT_0BGR: + case AV_PIX_FMT_0RGB: + case AV_PIX_FMT_BGR0: + case AV_PIX_FMT_RGB0: + s->filter_slice[0] = filter_slice_rgb0; + s->filter_slice[1] = filter_slice_rgb0_pl; + break; + case AV_PIX_FMT_ABGR: + case AV_PIX_FMT_ARGB: + case AV_PIX_FMT_BGRA: + case AV_PIX_FMT_RGBA: + s->filter_slice[0] = filter_slice_rgba; + s->filter_slice[1] = filter_slice_rgba_pl; + break; + case AV_PIX_FMT_BGR48: + case AV_PIX_FMT_RGB48: + s->filter_slice[0] = filter_slice_rgb48; + s->filter_slice[1] = filter_slice_rgb48_pl; + break; + case AV_PIX_FMT_BGRA64: + case AV_PIX_FMT_RGBA64: + s->filter_slice[0] = filter_slice_rgba64; + s->filter_slice[1] = filter_slice_rgba64_pl; + break; + case AV_PIX_FMT_GBRP: + s->filter_slice[0] = filter_slice_gbrp; + s->filter_slice[1] = filter_slice_gbrp_pl; + break; + case AV_PIX_FMT_GBRAP: + s->filter_slice[0] = filter_slice_gbrap; + s->filter_slice[1] = filter_slice_gbrap_pl; + break; + case AV_PIX_FMT_GBRP9: + s->filter_slice[0] = filter_slice_gbrp9; + s->filter_slice[1] = filter_slice_gbrp9_pl; + break; + case AV_PIX_FMT_GBRP10: + s->filter_slice[0] = filter_slice_gbrp10; + s->filter_slice[1] = filter_slice_gbrp10_pl; + break; + case AV_PIX_FMT_GBRAP10: + s->filter_slice[0] = filter_slice_gbrap10; + s->filter_slice[1] = filter_slice_gbrap10_pl; + break; + case AV_PIX_FMT_GBRP12: + s->filter_slice[0] = filter_slice_gbrp12; + s->filter_slice[1] = filter_slice_gbrp12_pl; + break; + case AV_PIX_FMT_GBRAP12: + s->filter_slice[0] = filter_slice_gbrap12; + s->filter_slice[1] = filter_slice_gbrap12_pl; + break; + case AV_PIX_FMT_GBRP14: + s->filter_slice[0] = filter_slice_gbrp14; + s->filter_slice[1] = filter_slice_gbrp14_pl; + break; + case AV_PIX_FMT_GBRP16: + s->filter_slice[0] = filter_slice_gbrp16; + s->filter_slice[1] = filter_slice_gbrp16_pl; + break; + case AV_PIX_FMT_GBRAP16: + s->filter_slice[0] = filter_slice_gbrap16; + s->filter_slice[1] = filter_slice_gbrap16_pl; + break; + case AV_PIX_FMT_GBRPF32: + s->filter_slice[0] = filter_slice_gbrp32; + s->filter_slice[1] = filter_slice_gbrp32_pl; + break; + case AV_PIX_FMT_GBRAPF32: + s->filter_slice[0] = filter_slice_gbrap32; + s->filter_slice[1] = filter_slice_gbrap32_pl; + break; + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + DiveCorrectorContext *s = ctx->priv; + AVFilterLink *outlink = ctx->outputs[0]; + const int pc = s->preserve_color > 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); + } + + td.in = in; + td.out = out; + ff_filter_execute(ctx, s->filter_slice[pc], &td, NULL, + FFMIN(outlink->h, ff_filter_get_nb_threads(ctx))); + + if (in != out) + av_frame_free(&in); + return ff_filter_frame(outlink, out); +} + +static int process_command(AVFilterContext *ctx, const char *cmd, const char *args, + char *res, int res_len, int flags) +{ + int ret = ff_filter_process_command(ctx, cmd, args, res, res_len, flags); + + if (ret < 0) + return ret; + + return config_output(ctx->outputs[0]); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + DiveCorrectorContext *s = ctx->priv; + + av_freep(&s->buffer); +} + +static const AVFilterPad divecorrector_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .filter_frame = filter_frame, + }, +}; + +static const AVFilterPad divecorrector_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_output, + }, +}; + +const AVFilter ff_vf_divecorrector = { + .name = "divecorrector", + .description = NULL_IF_CONFIG_SMALL("Adjust colors by mixing color channels."), + .priv_size = sizeof(DiveCorrectorContext), + .priv_class = &divecorrector_class, + .uninit = uninit, + FILTER_INPUTS(divecorrector_inputs), + FILTER_OUTPUTS(divecorrector_outputs), + FILTER_PIXFMTS_ARRAY(pix_fmts), + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, + .process_command = process_command, +};