diff options
author | Paul B Mahol <onemda@gmail.com> | 2021-02-08 11:11:46 +0100 |
---|---|---|
committer | Paul B Mahol <onemda@gmail.com> | 2021-02-12 00:08:39 +0100 |
commit | edc45d1bd50dd51e11e453f60ecc3770482cd544 (patch) | |
tree | fdb3785cfa8ee72ba3aceb1736ac01c65b06303e /libavfilter | |
parent | 58e59396f5fe93f0606dc458d84c609b5d23ea1c (diff) | |
download | ffmpeg-edc45d1bd50dd51e11e453f60ecc3770482cd544.tar.gz |
avfilter: add monochrome video filter
Diffstat (limited to 'libavfilter')
-rw-r--r-- | libavfilter/Makefile | 1 | ||||
-rw-r--r-- | libavfilter/allfilters.c | 1 | ||||
-rw-r--r-- | libavfilter/version.h | 2 | ||||
-rw-r--r-- | libavfilter/vf_monochrome.c | 314 |
4 files changed, 317 insertions, 1 deletions
diff --git a/libavfilter/Makefile b/libavfilter/Makefile index a34683e306..682d368bf5 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -332,6 +332,7 @@ OBJS-$(CONFIG_METADATA_FILTER) += f_metadata.o OBJS-$(CONFIG_MIDEQUALIZER_FILTER) += vf_midequalizer.o framesync.o OBJS-$(CONFIG_MINTERPOLATE_FILTER) += vf_minterpolate.o motion_estimation.o OBJS-$(CONFIG_MIX_FILTER) += vf_mix.o framesync.o +OBJS-$(CONFIG_MONOCHROME_FILTER) += vf_monochrome.o OBJS-$(CONFIG_MPDECIMATE_FILTER) += vf_mpdecimate.o OBJS-$(CONFIG_NEGATE_FILTER) += vf_lut.o OBJS-$(CONFIG_NLMEANS_FILTER) += vf_nlmeans.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index bc40cc04a9..f1131a923b 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -317,6 +317,7 @@ extern AVFilter ff_vf_metadata; extern AVFilter ff_vf_midequalizer; extern AVFilter ff_vf_minterpolate; extern AVFilter ff_vf_mix; +extern AVFilter ff_vf_monochrome; extern AVFilter ff_vf_mpdecimate; extern AVFilter ff_vf_negate; extern AVFilter ff_vf_nlmeans; diff --git a/libavfilter/version.h b/libavfilter/version.h index 47d73f89f8..9c8e224fc6 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFILTER_VERSION_MAJOR 7 -#define LIBAVFILTER_VERSION_MINOR 105 +#define LIBAVFILTER_VERSION_MINOR 106 #define LIBAVFILTER_VERSION_MICRO 100 diff --git a/libavfilter/vf_monochrome.c b/libavfilter/vf_monochrome.c new file mode 100644 index 0000000000..8faa068dbe --- /dev/null +++ b/libavfilter/vf_monochrome.c @@ -0,0 +1,314 @@ +/* + * Copyright (c) 2021 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 <float.h> + +#include "libavutil/opt.h" +#include "libavutil/imgutils.h" +#include "avfilter.h" +#include "formats.h" +#include "internal.h" +#include "video.h" + +typedef struct MonochromeContext { + const AVClass *class; + + float b, r; + float size; + float high; + + int depth; + int subw, subh; + + int (*do_slice)(AVFilterContext *s, void *arg, + int jobnr, int nb_jobs); + int (*clear_uv)(AVFilterContext *s, void *arg, + int jobnr, int nb_jobs); +} MonochromeContext; + +static float envelope(const float x) +{ + const float beta = 0.6f; + + if (x < beta) { + const float tmp = fabsf(x / beta - 1.f); + + return 1.f - tmp * tmp; + } else { + const float tmp = (1.f - x) / (1.f - beta); + + return tmp * tmp * (3.f - 2.f * tmp); + } +} + +static float filter(float b, float r, float u, float v, float size) +{ + return expf(-av_clipf(((b - u) * (b - u) + + (r - v) * (r - v)) * + size, 0.f, 1.f)); +} + +#define PROCESS() \ + const int cx = x >> subw; \ + float y = yptr[x] * imax; \ + float u = uptr[cx] * imax - .5f; \ + float v = vptr[cx] * imax - .5f; \ + float tt, t, ny; \ + \ + ny = filter(b, r, u, v, size); \ + tt = envelope(y); \ + t = tt + (1.f - tt) * ihigh; \ + ny = (1.f - t) * y + t * ny * y; + +static int monochrome_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + MonochromeContext *s = ctx->priv; + AVFrame *frame = arg; + const int depth = s->depth; + const int subw = s->subw; + const int subh = s->subh; + const float max = (1 << depth) - 1; + const float imax = 1.f / max; + const int width = frame->width; + const int height = frame->height; + const int slice_start = (height * jobnr) / nb_jobs; + const int slice_end = (height * (jobnr + 1)) / nb_jobs; + const int ylinesize = frame->linesize[0]; + const int ulinesize = frame->linesize[1]; + const int vlinesize = frame->linesize[2]; + uint8_t *yptr = frame->data[0] + slice_start * ylinesize; + const float ihigh = 1.f - s->high; + const float size = 1.f / s->size; + const float b = s->b * .5f; + const float r = s->r * .5f; + + for (int y = slice_start; y < slice_end; y++) { + const int cy = y >> subh; + uint8_t *uptr = frame->data[1] + cy * ulinesize; + uint8_t *vptr = frame->data[2] + cy * vlinesize; + + for (int x = 0; x < width; x++) { + PROCESS() + + yptr[x] = av_clip_uint8(ny * max); + } + + yptr += ylinesize; + } + + return 0; +} + +static int monochrome_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + MonochromeContext *s = ctx->priv; + AVFrame *frame = arg; + const int depth = s->depth; + const int subw = s->subw; + const int subh = s->subh; + const float max = (1 << depth) - 1; + const float imax = 1.f / max; + const int width = frame->width; + const int height = frame->height; + const int slice_start = (height * jobnr) / nb_jobs; + const int slice_end = (height * (jobnr + 1)) / nb_jobs; + const int ylinesize = frame->linesize[0] / 2; + const int ulinesize = frame->linesize[1] / 2; + const int vlinesize = frame->linesize[2] / 2; + uint16_t *yptr = (uint16_t *)frame->data[0] + slice_start * ylinesize; + const float ihigh = 1.f - s->high; + const float size = 1.f / s->size; + const float b = s->b * .5f; + const float r = s->r * .5f; + + for (int y = slice_start; y < slice_end; y++) { + const int cy = y >> subh; + uint16_t *uptr = (uint16_t *)frame->data[1] + cy * ulinesize; + uint16_t *vptr = (uint16_t *)frame->data[2] + cy * vlinesize; + + for (int x = 0; x < width; x++) { + PROCESS() + + yptr[x] = av_clip_uintp2_c(ny * max, depth); + } + + yptr += ylinesize; + } + + return 0; +} + +static int clear_slice8(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + MonochromeContext *s = ctx->priv; + AVFrame *frame = arg; + const int depth = s->depth; + const int half = 1 << (depth - 1); + const int subw = s->subw; + const int subh = s->subh; + const int width = AV_CEIL_RSHIFT(frame->width, subw); + const int height = AV_CEIL_RSHIFT(frame->height, subh); + const int slice_start = (height * jobnr) / nb_jobs; + const int slice_end = (height * (jobnr + 1)) / nb_jobs; + const int ulinesize = frame->linesize[1]; + const int vlinesize = frame->linesize[2]; + + for (int y = slice_start; y < slice_end; y++) { + uint8_t *uptr = frame->data[1] + y * ulinesize; + uint8_t *vptr = frame->data[2] + y * vlinesize; + + memset(uptr, half, width); + memset(vptr, half, width); + } + + return 0; +} + +static int clear_slice16(AVFilterContext *ctx, void *arg, int jobnr, int nb_jobs) +{ + MonochromeContext *s = ctx->priv; + AVFrame *frame = arg; + const int depth = s->depth; + const int half = 1 << (depth - 1); + const int subw = s->subw; + const int subh = s->subh; + const int width = AV_CEIL_RSHIFT(frame->width, subw); + const int height = AV_CEIL_RSHIFT(frame->height, subh); + const int slice_start = (height * jobnr) / nb_jobs; + const int slice_end = (height * (jobnr + 1)) / nb_jobs; + const int ulinesize = frame->linesize[1] / 2; + const int vlinesize = frame->linesize[2] / 2; + + for (int y = slice_start; y < slice_end; y++) { + uint16_t *uptr = (uint16_t *)frame->data[1] + y * ulinesize; + uint16_t *vptr = (uint16_t *)frame->data[2] + y * vlinesize; + + for (int x = 0; x < width; x++) { + uptr[x] = half; + vptr[x] = half; + } + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *frame) +{ + AVFilterContext *ctx = inlink->dst; + MonochromeContext *s = ctx->priv; + + ctx->internal->execute(ctx, s->do_slice, frame, NULL, + FFMIN(frame->height, ff_filter_get_nb_threads(ctx))); + ctx->internal->execute(ctx, s->clear_uv, frame, NULL, + FFMIN(frame->height >> s->subh, ff_filter_get_nb_threads(ctx))); + + return ff_filter_frame(ctx->outputs[0], frame); +} + +static av_cold int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pixel_fmts[] = { + AV_PIX_FMT_YUV410P, AV_PIX_FMT_YUV411P, + AV_PIX_FMT_YUV420P, AV_PIX_FMT_YUV422P, + AV_PIX_FMT_YUV440P, AV_PIX_FMT_YUV444P, + AV_PIX_FMT_YUVJ420P, AV_PIX_FMT_YUVJ422P, + AV_PIX_FMT_YUVJ440P, AV_PIX_FMT_YUVJ444P, + AV_PIX_FMT_YUVJ411P, + AV_PIX_FMT_YUV420P9, AV_PIX_FMT_YUV422P9, AV_PIX_FMT_YUV444P9, + AV_PIX_FMT_YUV420P10, AV_PIX_FMT_YUV422P10, AV_PIX_FMT_YUV444P10, + AV_PIX_FMT_YUV440P10, + AV_PIX_FMT_YUV444P12, AV_PIX_FMT_YUV422P12, AV_PIX_FMT_YUV420P12, + AV_PIX_FMT_YUV440P12, + AV_PIX_FMT_YUV444P14, AV_PIX_FMT_YUV422P14, AV_PIX_FMT_YUV420P14, + AV_PIX_FMT_YUV420P16, AV_PIX_FMT_YUV422P16, AV_PIX_FMT_YUV444P16, + AV_PIX_FMT_YUVA420P, AV_PIX_FMT_YUVA422P, AV_PIX_FMT_YUVA444P, + AV_PIX_FMT_YUVA444P9, AV_PIX_FMT_YUVA444P10, AV_PIX_FMT_YUVA444P12, AV_PIX_FMT_YUVA444P16, + AV_PIX_FMT_YUVA422P9, AV_PIX_FMT_YUVA422P10, AV_PIX_FMT_YUVA422P12, AV_PIX_FMT_YUVA422P16, + AV_PIX_FMT_YUVA420P9, AV_PIX_FMT_YUVA420P10, AV_PIX_FMT_YUVA420P16, + AV_PIX_FMT_NONE + }; + + AVFilterFormats *formats = NULL; + + formats = ff_make_format_list(pixel_fmts); + if (!formats) + return AVERROR(ENOMEM); + + return ff_set_common_formats(ctx, formats); +} + +static av_cold int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + MonochromeContext *s = ctx->priv; + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(inlink->format); + + s->depth = desc->comp[0].depth; + s->do_slice = s->depth <= 8 ? monochrome_slice8 : monochrome_slice16; + s->clear_uv = s->depth <= 8 ? clear_slice8 : clear_slice16; + s->subw = desc->log2_chroma_w; + s->subh = desc->log2_chroma_h; + + return 0; +} + +static const AVFilterPad monochrome_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .needs_writable = 1, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad monochrome_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + }, + { NULL } +}; + +#define OFFSET(x) offsetof(MonochromeContext, x) +#define VF AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_RUNTIME_PARAM + +static const AVOption monochrome_options[] = { + { "cb", "set the chroma blue spot", OFFSET(b), AV_OPT_TYPE_FLOAT, {.dbl=0},-1, 1, VF }, + { "cr", "set the chroma red spot", OFFSET(r), AV_OPT_TYPE_FLOAT, {.dbl=0},-1, 1, VF }, + { "size", "set the color filter size", OFFSET(size), AV_OPT_TYPE_FLOAT, {.dbl=1},.1,10, VF }, + { "high", "set the highlights strength", OFFSET(high), AV_OPT_TYPE_FLOAT, {.dbl=0}, 0, 1, VF }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(monochrome); + +AVFilter ff_vf_monochrome = { + .name = "monochrome", + .description = NULL_IF_CONFIG_SMALL("Convert video to gray using custom color filter."), + .priv_size = sizeof(MonochromeContext), + .priv_class = &monochrome_class, + .query_formats = query_formats, + .inputs = monochrome_inputs, + .outputs = monochrome_outputs, + .flags = AVFILTER_FLAG_SUPPORT_TIMELINE_GENERIC | AVFILTER_FLAG_SLICE_THREADS, + .process_command = ff_filter_process_command, +}; |